1
0
Fork 0

Again with the commit messages...

master
Matthieu Lalonde 12 years ago committed by xSmurf
parent 859758ced1
commit b6883d2f45

3
.gitignore vendored

@ -1,5 +1,4 @@
._* ._*
.* .*
.Apple* .Apple*
./old* old/*

@ -17,6 +17,7 @@
@import "/resources/css/player.css"; @import "/resources/css/player.css";
</style> </style>
<script src="resources/js/libs/utils.js">/**/</script>
<script data-main="resources/js/main" src="resources/vendors/require/require.min.js">/**/</script> <script data-main="resources/js/main" src="resources/vendors/require/require.min.js">/**/</script>
</head> </head>
<body> <body>
@ -46,13 +47,14 @@
<div class="ui-layout-west"> <div class="ui-layout-west">
<div id="wrapperSideBar"> <div id="wrapperSideBar">
<ul> <ul>
<li class="droppable selected"><span><i class="icon-list"></i>&nbsp;Playlist</span></li>
<li> <li>
<span> <span>
<i class="icon-home"></i>&nbsp;<span>Servers</span> <i class="icon-home"></i>&nbsp;<span>Servers</span>
<a href="javascript:void(0);" class="sidebar-list-action"><i class="icon-plus-sign"></i></a> <a href="javascript:void(0);" class="sidebar-list-action"><i class="icon-plus-sign"></i></a>
</span> </span>
<ul> <ul class="siderbar-server-list">
<li> <!--li>
<span> <span>
<i class="icon-folder-close"></i>&nbsp;<span>Bigthug</span> <i class="icon-folder-close"></i>&nbsp;<span>Bigthug</span>
<a href="javascript:void(0);" class="sidebar-list-action"><i class="icon-minus-sign"></i></a> <a href="javascript:void(0);" class="sidebar-list-action"><i class="icon-minus-sign"></i></a>
@ -75,10 +77,9 @@
<i class="icon-folder-close"></i>&nbsp;<span>Really Long Name Breaks</span> <i class="icon-folder-close"></i>&nbsp;<span>Really Long Name Breaks</span>
<a href="javascript:void(0);" class="sidebar-list-action"><i class="icon-minus-sign"></i></a> <a href="javascript:void(0);" class="sidebar-list-action"><i class="icon-minus-sign"></i></a>
</span> </span>
</li> </li-->
</ul> </ul>
</li> </li>
<li class="droppable"><span><i class="icon-list"></i>&nbsp;Playlist</span></li>
</ul> </ul>
</div> </div>
</div> </div>
@ -144,27 +145,6 @@
<li><a href="javascript:void(0);">Genre</a></li> <li><a href="javascript:void(0);">Genre</a></li>
</ul> </ul>
<script type="text/html" id="_tmplModalNewServer">
<div id="listItemDetails" data-closable="true">
<div class="overlay"></div>
<div class="modal" style="width: auto;margin-left: auto;margin-right: auto;" data-show="true" tabindex="-1" role="dialog" aria-labelledby="listItemDetailsLabel" aria-hidden="true">
<div class="modal-body" style="padding: 10px 10px 0 10px;">
<form>
<fieldset>
<legend>Add Server</legend>
<label>Address</label>
<input type="text" placeholder="daap://domain.tld">
</fieldset>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-mini" data-dismiss="modal" aria-hidden="true"><i class="icon-remove"></i>&nbsp;Cancel</button>
<button class="btn btn-mini btn-primary" data-dismiss="modal" aria-hidden="true"><i class="icon-ok"></i>&nbsp;Add&nbsp;&amp;&nbsp;Connect</button>
</div>
</div>
</div>
</script>
<script type="text/html" id="_tmplModalServerLogin"> <script type="text/html" id="_tmplModalServerLogin">
<div id="modalServerLogin"> <div id="modalServerLogin">
<div class="overlay"></div> <div class="overlay"></div>

@ -136,6 +136,10 @@ body > .ui-layout-west > #wrapperSideBar ul li {
} }
body > .ui-layout-west > #wrapperSideBar ul li > span:hover { body > .ui-layout-west > #wrapperSideBar ul li > span:hover {
color: #000;
}
body > .ui-layout-west > #wrapperSideBar ul li.selected > span {
color: #FFF; color: #FFF;
background-color: #4966B1;/* Old browsers */ background-color: #4966B1;/* Old browsers */
background: -moz-linear-gradient(top, #6086e5 0%, #6086e5 39%, #4966b1 100%); /* FF3.6+ */ background: -moz-linear-gradient(top, #6086e5 0%, #6086e5 39%, #4966b1 100%); /* FF3.6+ */
@ -147,6 +151,12 @@ body > .ui-layout-west > #wrapperSideBar ul li {
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#6086e5', endColorstr='#4966b1',GradientType=0 ); /* IE6-9 */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#6086e5', endColorstr='#4966b1',GradientType=0 ); /* IE6-9 */
} }
body > .ui-layout-west > #wrapperSideBar > ul > li:last-child > span:hover {
background-color: transparent;
background: transparent;
color: #333;
}
body > .ui-layout-west > #wrapperSideBar ul li ul { body > .ui-layout-west > #wrapperSideBar ul li ul {
margin-left: 10px; margin-left: 10px;
} }

@ -7,23 +7,27 @@
// Filename: app.js // Filename: app.js
define([ define([
"views/app" "underscore"
, "views/app"
, "views/footer" , "views/footer"
, "views/sidebar" , "views/sidebar"
, "views/player" , "views/player"
, "views/main" , "views/main"
, "models/client" // , "models/client"
, "models/dmap" , "models/dmap"
], ],
function( function(
AppView _
, AppView
, FooterView , FooterView
, SideBarView , SideBarView
, PlayerView , PlayerView
, MainView , MainView
, ClientModel //, ClientModel
, DMAPModel , DMAPModel
) { ) {
"use strict"; "use strict";
@ -31,14 +35,17 @@ AppView
var init = function() { var init = function() {
console.log("app.js::init()"); console.log("app.js::init()");
// Setup underscore templates with mustache styles
_.templateSettings.interpolate = /\{\{(.+?)\}\}/g;
this.Views = {}; this.Views = {};
this.Views.Footer = new FooterView();
this.Views.SideBar = new SideBarView(); this.Views.SideBar = new SideBarView();
this.Views.Player = new PlayerView(); this.Views.Player = new PlayerView();
this.Views.Main = new MainView(); this.Views.Main = new MainView();
this.Views.App = new AppView(); this.Views.App = new AppView();
this.Views.Footer = new FooterView(this.Views.App);
/*
var client = new ClientModel({ var client = new ClientModel({
hostname: "daap.localhost" hostname: "daap.localhost"
, protocol: "http" , protocol: "http"
@ -56,7 +63,7 @@ AppView
var item = collections.databases[collections.databasesInfo.get("dmap_listing").at(0).id].get("dmap_listing").get(107); var item = collections.databases[collections.databasesInfo.get("dmap_listing").at(0).id].get("dmap_listing").get(107);
console.log(item, item.toJSON(), collections.databases[collections.databasesInfo.get("dmap_listing").at(0).id].get("dmap_listing").toJSON()); console.log(item, item.toJSON(), collections.databases[collections.databasesInfo.get("dmap_listing").at(0).id].get("dmap_listing").toJSON());
}); });
*/
//client.init(); //client.init();
return this; return this;

@ -7,16 +7,22 @@
define([ define([
"underscore" "underscore"
, "backbone" , "backbone"
, "models/server"
, "backbone-localstorage" , "backbone-localstorage"
] ]
, function (_, Backbone) { , function (_, Backbone, ServerModel) {
"use strict"; "use strict";
return Backbone.Collection.extend({ return Backbone.Collection.extend({
localStorage: new Backbone.LocalStorage("daap-servers") model: ServerModel
, localStorage: new Backbone.LocalStorage("daap-servers")
/*
, initialize: function () { , initialize: function () {
return this.__super__.initialize.call(this, arguments); return this.__super__.initialize.call(this, arguments);
} }
*/
}); });
}); });

@ -0,0 +1,74 @@
/*jslint laxbreak:true */
/*jslint laxcomma:true */
/*jslint loopfunc:true */
/*jslint strict:true */
/*jslint browser:true */
/*jslint devel:true */
// Filename: app.js
define([
"underscore"
]
, function (_) {
"use strict";
var Config = {
debug: false
, verbose: false
, docRoot: ""
, daap: {
hostname: "daap.lalonde.me"
, protocol: "https"
, port: 443
}
, player: {
defaultVolume: 0.5
}
, dev: {
enabled: true
, debug: true
, verbose: 6
, docRoot: ""
, daap: {
hostname: "daap.localhost"
, protocol: "http"
, port: 80
}
}
};
/**
* NOTHING TO EDIT BELOW THIS LINE
**/
if (typeof Config.dev === "object"
&& typeof Config.dev.enabled !== "undefined"
&& Config.dev.enabled === true) {
var devConfig = Config.dev || {}
;
if (Object.size(devConfig) > 0) {
delete Config.dev;
_.extend(Config, devConfig);
}
}
if ( (typeof Config.debug === "undefined" || Config.debug !== true) && console.debug ) {
console.debug = function() {};
}
if ( (typeof Config.verbose === "undefined" || Config.verbose <= 5) && console.log ) {
console.log = function() {};
}
return Config;
});

@ -1 +1,17 @@
//if (typeof String.addLeadingChar) //if (typeof String.addLeadingChar)
if (typeof Object.size !== "function") {
var fnSize = function (obj) { return Object.keys(obj).length; };
if (typeof Object.keys !== "function") {
fnSize = function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
};
}
Object.size = fnSize;
}

@ -42,6 +42,10 @@ require.config({
, "toolbox-extras": { , "toolbox-extras": {
deps: ["toolbox"] deps: ["toolbox"]
} }
//, "config": {
// exports: "Config"
//}
} }
, paths: { , paths: {
@ -60,6 +64,11 @@ require.config({
, "backbone-localstorage": "../vendors/backbone/localstorage/8651291560/backbone.localStorage-min" , "backbone-localstorage": "../vendors/backbone/localstorage/8651291560/backbone.localStorage-min"
, "toolbox": "../vendors/backbone/toolbox/e0cac9f/toolbox" , "toolbox": "../vendors/backbone/toolbox/e0cac9f/toolbox"
, "toolbox-extras": "../vendors/backbone/toolbox/e0cac9f/toolbox.extra" , "toolbox-extras": "../vendors/backbone/toolbox/e0cac9f/toolbox.extra"
, "text": "../vendors/require/plugins/text/f5816b565b/text"
, "async": "../vendors/async"
//, "config": "config"
} }
, baseUrl: "./resources/js" , baseUrl: "./resources/js"
@ -72,13 +81,13 @@ require(
, "jquery" , "jquery"
] ]
, function ( App, $ ) { , function __fnMainRequireLoader( App, $ ) {
"use strict"; "use strict";
// JQuery Backwords compatibility fix // JQuery Backwords compatibility fix
$.curCSS = $.css; $.curCSS = $.css;
$("body").ready(function(){ $("body").ready(function __fnAppRequireLoader(){
App.init(); App.init();
}); });
} }

@ -16,348 +16,408 @@ define([
], function (_, Backbone, Toolbox, DMAPType, DMAPModel) { ], function (_, Backbone, Toolbox, DMAPType, DMAPModel) {
"use strict"; "use strict";
var that var Client = Toolbox.Base.extend(_.extend({
// TODO: Could be interesting to set a userage, could possibly affect transcoding on the server side constructor: function(attributes, options) {
, xhr = function (url, responseType, callback) { _.bindAll(this
var xhr = new XMLHttpRequest(); , "setAuth"
var contentType = "text/plain; charset=utf-8"; , "init"
, "deinit"
, "logout"
, "buildRequestUid"
, "url"
, "urlHost"
, "xhr"
, "fetchXHR"
, "fetchContentTypes"
, "fetchServerInfo"
, "fetchLogin"
, "fetchDatabases"
, "fetchDatabase"
, "fetchPlaylists"
, "fetchPlaylist"
, "fetchBrowse"
, "fetchgenres"
, "fetchArtists"
, "fetchAlbums"
, "on"
, "off"
, "trigger"
);
_.extend(this, Client.defaults);
this.attributes = _.extend({}, Client.prototype.attributes, attributes);
this.setAuth();
if (typeof responseType === "function") { return this;
callback = responseType;
responseType = "arraybuffer";
} }
xhr.open("GET", url, true); , setAuth: function(username, password) {
xhr.responseType = responseType; if (!_.isUndefined(username)) {
xhr.timeout = 2000; this.attributes.username = username;
xhr.setRequestHeader("Content-type", contentType); }
xhr.overrideMimeType("text/plain; charset=x-user-defined"); // Technically not need with v2 and responseType but for good measures
if (!_.isEmpty(that.attributes.basicauth)) {
xhr.withCredentials = true;
xhr.setRequestHeader("Authorization", "Basic " + that.attributes.basicauth);
}
try { if (!_.isUndefined(password)) {
xhr.onerror = function () {console.log("Error", arguments, this);}; this.attributes.password = password;
xhr.onabort = function () {console.log("Abort", arguments, this);}; }
xhr.ontimeout = function () {console.log("Timeout", arguments, this);};
xhr.onload = function (e) {
if (this.status === 200) {
callback(null, this.response);
} else if (this.status === 401) {
that.trigger("unauthorized", [this, e]);
} else {
callback(e.status);
}
};
xhr.send(); if (!_.isEmpty(this.attributes.password) || !_.isEmpty(this.attributes.username)) {
} catch (e) { this.attributes.basicauth = window.btoa((this.attributes.username || "") + ":" + (this.attributes.password || ""));
console.error(e); }
callback(e, null);
} }
}
, fetchXHR = function (path, success, error) {
xhr(that.url(path), function __fetchXHRCallback(err, content) {
if (err) {
if (!_.isFunction(error)) {
error = function (err, cont) {
throw [err, cont];
};
}
error(err, content); , init: function () {
} else { var that = this
success(content); ;
}
});
}
, fetchContentTypes = function (success, error) {
fetchXHR("/content-codes", function __fetchContentTypesCallback(content) {
that.collections.contentCodes = new DMAPModel(content);
if (_.isFunction(success)) {
success(content);
}
}, error);
}
, fetchServerInfo = function (success, error) { console.debug("Fetching content types");
fetchXHR("/server-info", function __fetchServerInfoCallback(content) { that.fetchContentTypes(function __fnClientCbFetchContentTypes(contentTypes) {
that.collections.serverInfo = new DMAPModel(content, { console.debug("Fetching server info");
contentCodes: that.collections.contentCodes that.fetchServerInfo(function __fnClientCbFetchServerInfo(serverInfo) {
console.debug("Fetching login");
that.fetchLogin(function __fnClientCbFetchLogin(sessionInfo) {
// TODO: Here we need to request /update for the database version
//console.debug("Fetching databases info");
//that.fetchDatabases(function () {
//console.debug("Fetching database 1");
//that.fetchDatabase(function () {
//console.debug("Fetching playlists info");
//that.fetchPlaylists(function () {
//console.debug("Fetching playlist 1 of db 1");
//that.fetchPlaylist(function () {
//console.debug("Fetching genres of db 1");
//that.fetchGenres(function () {
//console.debug("Fetching artists of db 1");
//that.fetchArtists(function () {
//console.debug("Fetching albums of db 1");
//that.fetchAlbums(function () {
that.trigger("inited", serverInfo);
//});
//});
//});
//});
//});
//});
//});
});
});
}); });
if (_.isFunction(success)) { return this;
success(content); }
}
}, error);
}
, fetchLogin = function (success, error) { , deinit: function () {
fetchXHR("/login", success, error); var that = this
} ;
, fetchDatabases = function (success, error) { this.logout();
fetchXHR("/databases", function __fetchDatabasesCallback(content) { }
that.collections.databasesInfo = new DMAPModel(content, {
contentCodes: that.collections.contentCodes
});
if (_.isFunction(success)) { ,logout: function () {
success(content);
}
}, error);
}
, fetchDatabase = function (success, error, dbId) {
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
dbId = 1;
} }
fetchXHR("/databases/" + dbId + "/items", function __fetchDatabaseCallback(content) { , buildRequestUid: function(path, fields, query) {
that.collections.databases[dbId] = new DMAPModel(content, { var that = this
contentCodes: that.collections.contentCodes ;
});
if (_.isFunction(success)) { if (_.isEmpty(path) || !_.isString(path)) {
success(content); throw new Error("INVALID_TYPE_ERROR");
} }
}, error);
}
, fetchPlaylists = function (success, error, dbId) {
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
dbId = 1;
}
fetchXHR("/databases/" + dbId + "/containers", function __fetchPlaylistsCallback(content) { if (!_.isArray(fields)) {
that.collections.playlistsInfo = new DMAPModel(content, { fields = [];
contentCodes: that.collections.contentCodes }
});
if (_.isFunction(success)) { if (!_.isArray(query)) {
success(content); query = [];
} }
}, error);
}
, fetchPlaylist = function (success, error, playlistId, dbId) {
if (_.isUndefined(playlistId) || !_.isNumeric(playlistId)) {
playlistId = 1;
}
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
dbId = 1;
} }
var requestUri = "/databases/" ,url: function (request) {
; var that = this
, uri = ""
requestUri += dbId; ;
requestUri += "/containers/";
requestUri += playlistId;
requestUri += "/items";
requestUri += "?meta=dmap.itemid,dmap.persistentid,dmap.containeritemid";
fetchXHR(requestUri, function __fetchPlaylistCallback(content) { if (typeof request === "string") {
if (!_.isArray(that.collections.playlists[dbId])) { uri = request; // Add leading slash here!
that.collections.playlists[dbId] = [];
} }
that.collections.playlists[dbId][playlistId] = new DMAPModel(content, { if (_.isNumber(this.collections.session.id)) {
contentCodes: that.collections.contentCodes var prefix = "?";
});
if (_.isFunction(success)) { if (uri.indexOf("?") >= 0) {
success(content); prefix = "&";
}
uri += prefix + "session-id=" + this.collections.session.id;
} }
}, error);
} return this.attributes.protocol + "://" + this.urlHost() + uri;
}
, fetchBrowse = function (success, error, dbId, type) { , urlHost: function () {
// TODO: We need to detect if the server supports browsing or not an implement our own otherwise. var that = this
if (_.isEmpty(type) || !_.isString(type) || !_.isArray(that.collections.browser[type])) { ;
throw new Error("INVALID_BROWSE_TYPE");
return this.attributes.hostname + ":" + this.attributes.port;
} }
var requestUri = "/databases/" // TODO: Could be interesting to set a useragent, could possibly affect transcoding on the server side
; , xhr: function (url, responseType, callback) {
var that = this
;
requestUri += dbId; var xhr = new XMLHttpRequest();
requestUri += "/browse/"; var contentType = "text/plain; charset=utf-8";
requestUri += type;
requestUri += "?meta=dmap.itemid,dmap.persistentid,dmap.containeritemid";
if (typeof responseType === "function") {
callback = responseType;
responseType = "arraybuffer";
}
fetchXHR(requestUri, function __fetchBrowseCallback(content) { xhr.open("GET", url, true);
that.collections.browser[type][dbId] = new DMAPModel(content, { xhr.responseType = responseType;
contentCodes: that.collections.contentCodes xhr.timeout = 2000;
}); xhr.setRequestHeader("Content-type", contentType);
xhr.overrideMimeType("text/plain; charset=x-user-defined"); // Technically not need with v2 and responseType but for good measures
if (_.isFunction(success)) { if (!_.isEmpty(that.attributes.basicauth)) {
success(content); xhr.withCredentials = true;
xhr.setRequestHeader("Authorization", "Basic " + that.attributes.basicauth);
} }
}, error);
}
, fetchGenres = function (success, error, dbId) { try {
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) { xhr.onerror = function () {console.log("Error", arguments, this);};
dbId = 1; xhr.onabort = function () {console.log("Abort", arguments, this);};
xhr.ontimeout = function () {console.log("Timeout", arguments, this);};
xhr.onload = function (e) {
if (this.status === 200) {
callback(null, this.response);
} else if (this.status === 401) {
that.trigger("unauthorized", [this, e]);
} else {
callback(e.status);
}
};
xhr.send();
} catch (e) {
console.error(e);
callback(e, null);
}
} }
return fetchBrowse(success, error, dbId, "genres"); , fetchXHR: function (path, success, error) {
} var that = this
;
that.xhr(that.url(path), function __fetchXHRCallback(err, content) {
if (err) {
if (!_.isFunction(error)) {
error = function (err, cont) {
throw [err, cont];
};
}
, fetchArtists = function (success, error, dbId) { error(err, content);
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) { } else {
dbId = 1; success(content);
}
});
} }
return fetchBrowse(success, error, dbId, "artists"); , fetchContentTypes: function (success, error) {
} var that = this
;
, fetchAlbums = function (success, error, dbId) { that.fetchXHR("/content-codes", function __fetchContentTypesCallback(content) {
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) { that.collections.contentCodes = new DMAPModel(content);
dbId = 1; if (_.isFunction(success)) {
success(that.collections.contentCodes);
}
}, error);
} }
return fetchBrowse(success, error, dbId, "albums"); , fetchServerInfo: function (success, error) {
} var that = this
;
that.fetchXHR("/server-info", function __fetchServerInfoCallback(content) {
that.collections.serverInfo = new DMAPModel(content, {
contentCodes: that.collections.contentCodes
});
, Client = Toolbox.Base.extend({ if (_.isFunction(success)) {
success(that.collections.serverInfo);
}
}, error);
}
constructor: function(attributes, options) { , fetchLogin: function (success, error) {
that = this; var that = this
;
_.extend(that, Client.defaults); that.fetchXHR("/login", success, error);
_.extend(that.attributes, attributes); }
that.setAuth(); , fetchDatabases: function (success, error) {
var that = this
;
return this; that.fetchXHR("/databases", function __fetchDatabasesCallback(content) {
that.collections.databasesInfo = new DMAPModel(content, {
contentCodes: that.collections.contentCodes
});
if (_.isFunction(success)) {
success(that.collections.databasesInfo);
}
}, error);
} }
, setAuth: function(username, password) { , fetchDatabase: function (success, error, dbId) {
if (!_.isUndefined(username)) { var that = this
that.attributes.username = username; ;
}
if (!_.isUndefined(password)) { if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
that.attributes.password = password; dbId = 1;
} }
if (!_.isEmpty(that.attributes.password) || !_.isEmpty(that.attributes.username)) { that.fetchXHR("/databases/" + dbId + "/items", function __fetchDatabaseCallback(content) {
that.attributes.basicauth = window.btoa((that.attributes.username || "") + ":" + (that.attributes.password || "")); that.collections.databases[dbId] = new DMAPModel(content, {
} contentCodes: that.collections.contentCodes
});
if (_.isFunction(success)) {
success(that.collections.databases[dbId]);
}
}, error);
} }
, init: function () { , fetchPlaylists: function (success, error, dbId) {
var that = this var that = this
; ;
that.console.debug("Fetching content types"); if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
fetchContentTypes(function () { dbId = 1;
that.console.debug("Fetching server info"); }
fetchServerInfo(function () {
that.console.debug("Fetching login"); that.fetchXHR("/databases/" + dbId + "/containers", function __fetchPlaylistsCallback(content) {
fetchLogin(function () { that.collections.playlistsInfo = new DMAPModel(content, {
// TODO: Here we need to request /update for the database version contentCodes: that.collections.contentCodes
that.console.debug("Fetching databases info");
fetchDatabases(function () {
//that.console.debug("Fetching database 1");
//fetchDatabase(function () {
that.console.debug("Fetching playlists info");
fetchPlaylists(function () {
//that.console.debug("Fetching playlist 1 of db 1");
//fetchPlaylist(function () {
//that.console.debug("Fetching genres of db 1");
//fetchGenres(function () {
//that.console.debug("Fetching artists of db 1");
//fetchArtists(function () {
//that.console.debug("Fetching albums of db 1");
//fetchAlbums(function () {
that.trigger("inited", that.collections);
//});
//});
//});
//});
});
//});
});
});
}); });
});
return this; if (_.isFunction(success)) {
success(that.collections.playlistsInfo);
}
}, error);
} }
,logout: function () { , fetchPlaylist: function (success, error, playlistId, dbId) {
var that = this
} ;
, buildRequestUid: function(path, fields, query) { if (_.isUndefined(playlistId) || !_.isNumeric(playlistId)) {
if (_.isEmpty(path) || !_.isString(path)) { playlistId = 1;
throw new Error("INVALID_TYPE_ERROR");
} }
if (!_.isArray(fields)) { if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
fields = []; dbId = 1;
} }
if (!_.isArray(query)) { var requestUri = "/databases/"
query = []; ;
}
requestUri += dbId;
requestUri += "/containers/";
requestUri += playlistId;
requestUri += "/items";
requestUri += "?meta=dmap.itemid,dmap.persistentid,dmap.containeritemid";
that.fetchXHR(requestUri, function __fetchPlaylistCallback(content) {
if (!_.isArray(that.collections.playlists[dbId])) {
that.collections.playlists[dbId] = [];
}
that.collections.playlists[dbId][playlistId] = new DMAPModel(content, {
contentCodes: that.collections.contentCodes
});
if (_.isFunction(success)) {
success(that.collections.playlists[dbId][playlistId]);
}
}, error);
} }
,url: function (request) { , fetchBrowse: function (success, error, dbId, type) {
var uri = "" var that = this
; ;
if (typeof request === "string") { // TODO: We need to detect if the server supports browsing or not an implement our own otherwise.
uri = request; // Add leading slash here! if (_.isEmpty(type) || !_.isString(type) || !_.isArray(that.collections.browser[type])) {
throw new Error("INVALID_BROWSE_TYPE");
} }
if (_.isNumber(this.collections.session.id)) { var requestUri = "/databases/"
var prefix = "?"; ;
if (uri.indexOf("?") >= 0) { requestUri += dbId;
prefix = "&"; requestUri += "/browse/";
requestUri += type;
requestUri += "?meta=dmap.itemid,dmap.persistentid,dmap.containeritemid";
that.fetchXHR(requestUri, function __fetchBrowseCallback(content) {
that.collections.browser[type][dbId] = new DMAPModel(content, {
contentCodes: that.collections.contentCodes
});
if (_.isFunction(success)) {
success(that.collections.browser[type][dbId]);
} }
}, error);
}
uri += prefix + "session-id=" + this.collections.session.id; , fetchGenres: function (success, error, dbId) {
var that = this
;
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
dbId = 1;
} }
return this.attributes.protocol + "://" + this.attributes.hostname + ":" + this.attributes.port + uri; return that.fetchBrowse(success, error, dbId, "genres");
} }
, console: { , fetchArtists: function (success, error, dbId) {
log: function() { var that = this
var args = arguments; ;
if (_.isString(args[0])) {
args[0] = that.url() + " " + args[0];
}
console.log(args); if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
dbId = 1;
} }
, debug: function() {
var args = arguments;
if (_.isString(args[0])) { return that.fetchBrowse(success, error, dbId, "artists");
args[0] = that.url() + " " + args[0]; }
}
console.debug(args); , fetchAlbums: function (success, error, dbId) {
var that = this
;
if (_.isUndefined(dbId) || !_.isNumeric(dbId)) {
dbId = 1;
} }
return that.fetchBrowse(success, error, dbId, "albums");
} }
}, {
}, Backbone.Events), {
defaults: { defaults: {
attributes: { attributes: {
protocol: window.location.protocol.replace(":", "") protocol: window.location.protocol.replace(":", "")
@ -376,6 +436,7 @@ define([
, timedout: 2 , timedout: 2
, unauthorized: 3 , unauthorized: 3
, connected: 4 , connected: 4
, loaded: 5
} }
, collections: { , collections: {
@ -406,7 +467,7 @@ define([
} }
}); });
_.extend(Client.prototype, Backbone.Events, {}); //_.extend(Client.prototype, Backbone.Events);
return Client; return Client;
}); });

@ -14,9 +14,7 @@ define([
, function (_, Backbone, Toolbox, DMAPType, DMAPCollection) { , function (_, Backbone, Toolbox, DMAPType, DMAPCollection) {
"use strict"; "use strict";
var that var browserTags = {
, browserTags = {
"abal": "abal" "abal": "abal"
, "abar": "abar" , "abar": "abar"
, "abcp": "abcp" , "abcp": "abcp"
@ -28,7 +26,17 @@ define([
idAttribute: "_id" idAttribute: "_id"
, initialize: function (attributes, options) { , initialize: function (attributes, options) {
that = this; _.bindAll(this
, "getAttibutesId"
, "toJSON"
, "update"
, "parse"
, "fetch"
, "save"
, "destroy"
, "unset"
, "clear"
);
var contentCodes = options && options.contentCodes || null; var contentCodes = options && options.contentCodes || null;
@ -40,7 +48,7 @@ define([
delete attributes.byteLength; delete attributes.byteLength;
} }
attributes._id = that.getAttributesId(attributes); attributes._id = this.getAttributesId(attributes);
this.set(attributes); this.set(attributes);
@ -63,43 +71,43 @@ define([
} }
, update: function (attributes, options) { , update: function (attributes, options) {
that.trigger("dmap.model.update", this, arguments); this.trigger("dmap.model.update", this, arguments);
return that.apply(that, arguments); return this.apply(this, arguments);
} }
, parse: function (reponse) { , parse: function (reponse) {
return that.attributes; return this.attributes;
} }
, fetch: function (options) { , fetch: function (options) {
that.trigger("dmap.model.fetch", this, arguments); this.trigger("dmap.model.fetch", this, arguments);
return that; return this;
} }
, save: function (attributes, options) { , save: function (attributes, options) {
that.trigger("dmap.model.save", this, arguments); this.trigger("dmap.model.save", this, arguments);
return that; return this;
} }
, destroy: function (options) { , destroy: function (options) {
that.trigger("dmap.model.destroy", this, arguments); this.trigger("dmap.model.destroy", this, arguments);
return that; return this;
} }
, unset: function (options) { , unset: function (options) {
that.trigger("dmap.model.unset", this, arguments); this.trigger("dmap.model.unset", this, arguments);
return that; return this;
} }
, clear: function (options) { , clear: function (options) {
that.trigger("dmap.model.unset", this, arguments); this.trigger("dmap.model.unset", this, arguments);
return that; return this;
} }
}) })

@ -1,71 +0,0 @@
/*jslint laxbreak:true */
/*jslint laxcomma:true */
/*jslint loopfunc:true */
/*jslint strict:true */
/*jslint browser:true */
/*jslint devel:true */
define([
"underscore"
, "backbone"
, "toolbox"
, "models/dmap-type"
]
, function (_, Backbone, Toolbox, DMAPType) {
"use strict";
var that
, set = function (attributes) {
}
, DMAP = Toolbox.Base.extend({
id: null
, attributes: false
, collection: false
, parentid: null
, index: {}
, items: []
, constructor: function (buffer, parent, options) {
}
, set: function () {
}
, get: function () {
}
, getItem: function () {
}
, getItemByIndex: function () {
}
, parseBinary: function () {
}
, parseContentCode: function () {
}
, addItem: function (item) {
}
, remove: function (items) {
}
});
_.extend(DMAP.prototype, Backbone.Events, {});
return DMAP;
});

@ -0,0 +1,68 @@
/*jslint laxbreak:true */
/*jslint laxcomma:true */
/*jslint loopfunc:true */
/*jslint strict:true */
/*jslint browser:true */
/*jslint devel:true */
define([
"underscore"
, "backbone"
, "models/client"
]
, function (_, Backbone, Client) {
"use strict";
var Model = Backbone.Model.extend({
client: null
, initialize: function (attributes, options) {
_.bindAll(this, "destroy", "getName", "relayEvent");
var that = this
;
this.client = new Client(attributes);
this.client.on("inited", function __fnServerModelClientInitedEvent(serverInfo) {
if (serverInfo && serverInfo.get("dmap_itemname")) {
that.set(_.extend({}, that.attributes, {name: serverInfo.get("dmap_itemname")}));
// We could save here, but it is debatable if we want to leep the name attribute locally
// that.save();
}
});
this.client.on("all", this.relayEvent);
return Model.__super__.initialize.call(this, arguments);
}
, relayEvent: function () {
var args = _.values(arguments)
, eventName = args.shift()
;
this.trigger("client:" + eventName, args);
}
, destroy: function () {
if (this.client && this.client.deinit) {
this.client.deinit();
}
return Model.__super__.destroy.call(this, arguments);
}
, getName: function () {
if (this.get("name")) {
return this.get("name");
} else if (this.client && this.client.urlHost) {
return this.client.urlHost();
} else {
return this.get("hostname");
}
}
});
return Model;
});

@ -1,3 +1,22 @@
/*jslint laxbreak:true */
/*jslint laxcomma:true */
/*jslint loopfunc:true */
/*jslint strict:true */
/*jslint browser:true */
/*jslint devel:true */
define([
"jquery"
, "backbone"
, "jquery-layout"
]
, function ($, Backbone) {
"use strict";
return Backbone.View.extend({
});
});
/* /*
that.layoutItems = $(that.layout.panes.center).layout({ that.layoutItems = $(that.layout.panes.center).layout({

@ -5,10 +5,31 @@
/*jslint browser:true */ /*jslint browser:true */
/*jslint devel:true */ /*jslint devel:true */
define([ define([
"bootstrap" "jquery"
, "backbone"
, "bootstrap"
] ]
, function (Bootstrap) { , function ($, Backbone, Bootstrap) {
"use strict"; "use strict";
return function() {}; return Backbone.View.extend({
el: $("div#wrapperFooter")
, initialize: function (AppView) {
var that = this
;
that.$el.delegate("button[data-toggle='side-bar']", "click", function (event) {
AppView.layout.toggle("west");
var $target = $(event.target);
if (event.target.tagName === "BUTTON") {
$target = $target.find("i");
}
$target.toggleClass("icon-chevron-right");
$target.toggleClass("icon-chevron-left");
});
}
});
}); });

@ -4,10 +4,182 @@
/*jslint strict:true */ /*jslint strict:true */
/*jslint browser:true */ /*jslint browser:true */
/*jslint devel:true */ /*jslint devel:true */
define([ define([
"underscore"
, "jquery"
, "backbone"
, "bootstrap"
, "async"
, "config"
, "collections/server"
, "text!../../templates/sidebar/server-list-item.html"
, "text!../../templates/sidebar/modal-new-server.html"
] ]
, function () { , function (
_
, $
, Backbone
, Bootstrap
, async
, Config
, ServerCollection
, tmplServerListItem
, tmplModalNewServer
) {
"use strict"; "use strict";
return function() {}; return Backbone.View.extend({
el: $("div#wrapperSideBar")
, elServerList: $("div#wrapperSideBar ul.siderbar-server-list")
, events: {
"click > ul > li:last-child > span > a.sidebar-list-action": "_addServerItem"
}
, initialize: function () {
_.bindAll(this, "renderServerItem", "_addServerItem");
var that = this
, serverModel = null
;
this.servers = new ServerCollection();
this.servers.fetch({
success: function (collection, modelsAttributes) {
if (modelsAttributes.length === 0) {
console.info("Adding default daap server");
collection.add(Config.daap);
}
async.forEach(collection.models, that.renderServerItem);
}
});
this.servers.on("add", function (server) {
that.renderServerItem(server);
server.save();
});
}
, _addServerItem: function(event) {
var that = this
, modalEl = $(tmplModalNewServer)
;
modalEl.modal();
modalEl.find("button[data-save='modal']").on("click", function (event) {
modalEl.find("form").submit();
});
modalEl.find("form").on("submit", function (event) {
var $input = $(event.target).find("input")
, value = $input.val()
, protocol = "http"
, port = 3689
, domain = ""
, errors = []
;
if (value.substr(-1) === "/") {
value = value.substr(0, value.length - 1);
}
if (/^(?:http|daap)(?:s)?:\/\//.test(value)) {
protocol = value.substring(0, value.indexOf(":")).replace("daap", "http");
value = value.substr(value.indexOf(":") + 3);
}
if (value.indexOf(":") > 0) {
port = value.substring(value.indexOf(":") + 1);
value = value.substring(0, value.indexOf(":"));
} else if (protocol === "https") {
port = 443;
}
domain = value.toLowerCase();
if (isNaN((port = parseInt(port, 10)))) {
errors.push("Invalid port number: 3689");
}
if (protocol !== "http" && protocol !== "https") {
errors.push("Invalid protocol: http(s)");
}
if (_.isEmpty(domain)
|| /^([a-z0-9]([\-a-z0-9]*[a-z0-9])?\\.)+((a[cdefgilmnoqrstuwxz]|aero|arpa)|(b[abdefghijmnorstvwyz]|biz)|(c[acdfghiklmnorsuvxyz]|cat|com|coop)|d[ejkmoz]|(e[ceghrstu]|edu)|f[ijkmor]|(g[abdefghilmnpqrstuwy]|gov)|h[kmnrtu]|(i[delmnoqrst]|info|int)|(j[emop]|jobs)|k[eghimnprwyz]|l[abcikrstuvy]|(m[acdghklmnopqrstuvwxyz]|mil|mobi|museum)|(n[acefgilopruz]|name|net)|(om|org)|(p[aefghklmnrstwy]|pro)|qa|r[eouw]|s[abcdeghijklmnortvyz]|(t[cdfghjklmnoprtvwz]|travel)|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw])$/.test(domain)) {
errors.push("Invalid server domain");
}
if (errors.length > 0) {
alert("Please correct the following errors:\n" + errors.join("\n"));
} else {
modalEl.modal("hide");
setTimeout(function __fnSaveServerModel() {
that.servers.add({
protocol: protocol
, port: port
, hostname: domain
});
}, 10);
}
return false;
});
modalEl.on("hidden", function (event) {
$(event.target).remove();
});
}
, renderServerItem: function (item) {
var that = this
, itemHtml = _.template(tmplServerListItem, {
server_name: item.getName()
})
, $item = $(itemHtml)
;
this.elServerList.append($item);
item.on("change", function __fnSideBarViewClientItemChanged(event) {
if (event.get("name")) {
$item.find("span > span").text(event.getName());
}
});
item.client.init();
$item.find("a.sidebar-list-action").on("click", function (model) {
return function __fnSideBarClickRemoveAction (event) {
var confirmMessage = "Are you sure you want to delete:\n" + item.getName();
if (window.confirm(confirmMessage)) {
$(event.target).parents("li:first").fadeOut(300, function() { $(this).remove(); });
setTimeout(function () {
model.destroy();
}, 10);
}
};
}(item));
$item.on("dblclick", function (model) {
return function __fnSideBarViewEventDblClickItem(event) {
};
}(item));
}
});
}); });

@ -0,0 +1,19 @@
<div class="modal" style="width: auto;margin-left: auto;margin-right: auto;"
data-backdrop="true"
data-keyboard="true"
data-show="true"
tabindex="-1" role="dialog">
<div class="modal-body" style="padding: 10px 10px 0 10px;">
<form>
<fieldset>
<legend>Add Server</legend>
<label>Address</label>
<input type="text" placeholder="https://domain.tld:3689">
</fieldset>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-mini" data-dismiss="modal" aria-hidden="true"><i class="icon-remove"></i>&nbsp;Cancel</button>
<button class="btn btn-mini btn-primary" data-save="modal" aria-hidden="true"><i class="icon-ok"></i>&nbsp;Add&nbsp;&amp;&nbsp;Connect</button>
</div>
</div>

@ -0,0 +1,6 @@
<li>
<span>
<i class="icon-folder-close"></i>&nbsp;<span>{{server_name}}</span>
<a href="javascript:void(0);" class="sidebar-list-action"><i class="icon-minus-sign"></i></a>
</span>
</li>

@ -0,0 +1,308 @@
/**
* @license RequireJS text 2.0.3 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: false, XMLHttpRequest: false, ActiveXObject: false,
define: false, window: false, process: false, Packages: false,
java: false, location: false */
define(['module'], function (module) {
'use strict';
var text, fs,
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.3',
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 strip = false, index = name.indexOf("."),
modName = name.substring(0, index),
ext = name.substring(index + 1, name.length);
index = ext.indexOf("!");
if (index !== -1) {
//Pull off the strip arg.
strip = ext.substring(index + 1, ext.length);
strip = strip === "strip";
ext = ext.substring(0, index);
}
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,
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),
nonStripName = parsed.moduleName + '.' + parsed.ext,
//Use a '.js' file name so that it indicates it is a
//script that can be loaded across domains.
fileName = req.toUrl(parsed.moduleName + '.' +
parsed.ext) + '.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) {
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);
};
} else if (masterConfig.env === 'xhr' || (!masterConfig.env &&
text.createXhr())) {
text.get = function (url, callback, errback) {
var xhr = text.createXhr();
xhr.open('GET', url, true);
//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);
}
}
};
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);
}
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);
};
}
return text;
});
Loading…
Cancel
Save