} scale
116 | * @param {number} root
117 | * @param {number} len
118 | */
119 | SoundEffects.prototype.makeScale = function (scale, root, len) {
120 | var s, current, position;
121 | s = [];
122 | current = root;
123 | for (position = 0; s.length < len; position++) {
124 | if (_.includes(scale, position % 12)) {
125 | s.push(current);
126 | }
127 | current = current * semitone;
128 | }
129 | return s;
130 | };
131 |
132 | /**
133 | * @param {string} username
134 | */
135 | SoundEffects.prototype.join = function (username) {
136 | var i, note;
137 | i = _.reduce(utils.md5(username), function (memo, c) {
138 | return memo + c.charCodeAt(0);
139 | }, 0);
140 | note = this.scale[i % _.size(this.scale)];
141 | this.playNote(this.waveform, this.scale[0], duration.quarter, 0);
142 | this.playNote(this.waveform, note, duration.quarter, duration.quarter);
143 | };
144 |
145 | /**
146 | * @param {string} username
147 | */
148 | SoundEffects.prototype.leave = function (username) {
149 | var i, note;
150 | i = _.reduce(utils.md5(username), function (memo, c) {
151 | return memo + c.charCodeAt(0);
152 | }, 0);
153 | note = this.scale[i % _.size(this.scale)];
154 | this.playNote(this.waveform, note, duration.quarter, 0);
155 | this.playNote(this.waveform, this.scale[0], duration.quarter, duration.quarter);
156 | };
157 |
158 | module.exports = new SoundEffects();
159 |
--------------------------------------------------------------------------------
/lib/common/terminal_model.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // const _ = require("lodash");
4 | const flux = require("flukes");
5 |
6 | // const Socket = require("./floop");
7 | // const editorAction = require("./editor_action");
8 |
9 | const TerminalModel = flux.createModel({
10 | modelName: "Terminal",
11 | fieldTypes: {
12 | id: flux.FieldTypes.number,
13 | local: flux.FieldTypes.bool.ephemeral(),
14 | term: flux.FieldTypes.object.ephemeral(),
15 | username: flux.FieldTypes.string,
16 | title: flux.FieldTypes.string,
17 | deleted: flux.FieldTypes.bool.ephemeral(),
18 | }
19 | });
20 |
21 | const Terminals = flux.createCollection({
22 | model: TerminalModel,
23 | modelName: "Terminals",
24 | users: null,
25 | getTerm_: function (id) {
26 | var term = this.get(id);
27 | return term && term.term;
28 | },
29 | init: function (args, args2) {
30 | this.users = args2.users;
31 |
32 | // editorAction.onCLOSE_TERM(function (term) {
33 | // if (!term.deleted) {
34 | // return;
35 | // }
36 | // this.remove(term.id);
37 | // term.term.destroy();
38 | // }, this);
39 |
40 | // Socket.onCREATE_TERM(function (term) {
41 | // var user = this.users.getByConnectionID(term.owner);
42 | // // this.addTerminal(term.id, term.term_name, term.size[0], term.size[1], user.id);
43 | // }, this);
44 |
45 | // Socket.onDELETE_TERM(function (data) {
46 | // const term = this.get(data.id);
47 | // if (!term || !term.term) {
48 | // return;
49 | // }
50 | // term.term.write("\r\n *** TERMINAL CLOSED *** \r\n");
51 | // term.deleted = true;
52 | // }, this);
53 |
54 | // Socket.onTERM_STDOUT(function (data) {
55 | // var term = this.getTerm_(data.id);
56 | // if (!term || !term.children) {
57 | // return;
58 | // }
59 | // try {
60 | // term.write(new Buffer(data.data, "base64").toString("utf-8"));
61 | // } catch (e) {
62 | // console.warn(e);
63 | // }
64 | // }, this);
65 |
66 | // Socket.onUPDATE_TERM(function (data) {
67 | // var term = this.getTerm_(data.id);
68 | // console.log("update term", data);
69 | // if (term && data.size) {
70 | // term.resize(data.size[0], data.size[1]);
71 | // }
72 | // }, this);
73 |
74 | // // XXX Clean this up.
75 | // Socket.onSYNC(function (terms) {
76 | // console.log("Attempting to sync...");
77 | // _.each(terms, function (data, id) {
78 | // var term = this.getTerm_(id);
79 | // if (!term) {
80 | // return;
81 | // }
82 | // term.resize(data.cols, data.rows);
83 | // }, this);
84 | // }, this);
85 | },
86 | });
87 |
88 |
89 | module.exports = {
90 | Terminals: Terminals
91 | };
92 |
--------------------------------------------------------------------------------
/lib/common/transport.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var util = require("util");
4 | var messageAction = require("./message_action");
5 | var Emitter = require("./emitter");
6 |
7 | var STARTING_TIMEOUT = 200;
8 | var MAX_TIMEOUT = 10000;
9 |
10 | function Transport() {
11 | Emitter.call(this);
12 |
13 | this.timeout = STARTING_TIMEOUT;
14 | this.reconnectTimeout = null;
15 | this.on("connected", this.reset_timeout.bind(this));
16 | }
17 |
18 | util.inherits(Transport, Emitter);
19 |
20 | Transport.prototype.connect = function () {
21 | throw new Error("not implemented");
22 | };
23 |
24 | Transport.prototype.disconnect = function () {
25 | this.reconnectTimeout = clearTimeout(this.reconnectTimeout);
26 | };
27 |
28 | Transport.prototype.write = function (name, msg, cb, context) {
29 | console.debug(name, msg, cb, context);
30 | throw new Error("not implemented");
31 | };
32 |
33 | Transport.prototype.reset_timeout = function () {
34 | this.timeout = STARTING_TIMEOUT;
35 | this.reconnectTimeout = clearTimeout(this.reconnectTimeout);
36 | };
37 |
38 | Transport.prototype.reconnect_ = function () {
39 | if (this.reconnectTimeout) {
40 | return;
41 | }
42 |
43 | this.reconnectTimeout = setTimeout(function () {
44 | this.reconnectTimeout = null;
45 | this.connect();
46 | }.bind(this), this.timeout);
47 | messageAction.log(`Reconnecting in ${this.timeout}ms...`);
48 | this.timeout = Math.min(this.timeout * 2, MAX_TIMEOUT);
49 | };
50 |
51 | module.exports = Transport;
52 |
--------------------------------------------------------------------------------
/lib/common/userPref_model.js:
--------------------------------------------------------------------------------
1 | /* @flow weak */
2 | /** @jsx React.DOM */
3 | /*global self, Notification */
4 | /** @fileOverview User preferences. */
5 | "use strict";
6 |
7 | const flux = require("flukes");
8 | const _ = require("lodash");
9 | let fieldTypes = flux.FieldTypes;
10 | var prefs;
11 |
12 | var UserPref = flux.createModel({
13 | // TODO: post prefs back to django
14 | backend: flux.backends.local,
15 | followPaused: false,
16 | modelName: "UserPref",
17 | fieldTypes: {
18 | id: fieldTypes.string.defaults("UserPref"),
19 | theme: fieldTypes.string,
20 | sound: fieldTypes.bool.defaults(true),
21 | path: fieldTypes.string,
22 | following: fieldTypes.bool,
23 | followUsers: fieldTypes.list,
24 | logLevel: fieldTypes.number,
25 | showNotifications: fieldTypes.bool,
26 | showImages: fieldTypes.bool.defaults(true),
27 | dnd: fieldTypes.bool,
28 | mugshots: fieldTypes.bool.defaults(true),
29 | audioOnly: fieldTypes.bool,
30 | canNotify: fieldTypes.bool.ephemeral(),
31 | source_audio_id: fieldTypes.string.defaults(""),
32 | source_audio_name: fieldTypes.string.defaults("Default"),
33 | source_video_id: fieldTypes.string.defaults(""),
34 | source_video_name: fieldTypes.string.defaults("Default"),
35 | },
36 | // TODO: save as a user pref in django-land
37 | // getDefaultFields: function () {
38 | // // var editorSettings = fl.editor_settings;
39 | // return {
40 | // theme: editorSettings.theme,
41 | // sound: !!editorSettings.sound,
42 | // following: !!editorSettings.follow_mode,
43 | // logLevel: editorSettings.logLevel,
44 | // };
45 | // },
46 | isFollowing: function (username) {
47 | if (this.followPaused) {
48 | return false;
49 | }
50 | if (_.isUndefined(username)) {
51 | if (this.followUsers.length > 0) {
52 | return true;
53 | }
54 | return this.following;
55 | }
56 | if(this.followUsers.indexOf(username) !== -1) {
57 | return true;
58 | }
59 | return this.following;
60 | },
61 | pauseFollowMode: function (duration) {
62 | var that = this;
63 |
64 | function resetFollowMode () {
65 | that.followPaused = false;
66 | delete that.pauseFollowTimeout;
67 | }
68 |
69 | if (!this.following && !this.followUsers.length) {
70 | return;
71 | }
72 |
73 | clearTimeout(this.pauseFollowTimeout);
74 | this.followPaused = true;
75 | this.pauseFollowTimeout = setTimeout(resetFollowMode, duration);
76 | },
77 | didUpdate: function (field) {
78 | if (_.isString(field) && field !== "showNotifications") {
79 | return;
80 | }
81 | if (_.isArray(field) && field.indexOf("showNotifications") === -1) {
82 | return;
83 | }
84 | this.requestNotificationPermission();
85 | },
86 | onNotificationPermission_: function (permission) {
87 | var canNotify = permission === "granted";
88 |
89 | if (canNotify === this.canNotify) {
90 | return;
91 | }
92 | this.set({canNotify: canNotify}, {silent: true});
93 | try {
94 | this.save();
95 | } catch (ignore) {
96 | // Squelch
97 | }
98 | },
99 | requestNotificationPermission: function () {
100 | if (!self.Notification || !_.isFunction(Notification.requestPermission) || !this.showNotifications) {
101 | return;
102 | }
103 | Notification.requestPermission(this.onNotificationPermission_.bind(this));
104 | }
105 | });
106 |
107 | // prefs = new UserPref(fl.editor_settings);
108 | prefs = new UserPref();
109 | module.exports = prefs;
110 |
--------------------------------------------------------------------------------
/lib/common/user_model.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const _ = require("lodash");
4 | const flux = require("flukes");
5 |
6 | const floop = require("./floop");
7 | const utils = require("./utils");
8 | const editorAction = require("./editor_action");
9 |
10 | let context = null;
11 | try {
12 | context = window.AudioContext && new window.AudioContext();
13 | } catch (e) {
14 | console.log("Unable to create context.", e);
15 | }
16 | const Visualizer = flux.createActions({
17 | visualize: function (width) { return width; }
18 | });
19 |
20 |
21 | const Connection = flux.createModel({
22 | modelName: "Connection",
23 | fieldTypes: {
24 | id: flux.FieldTypes.number,
25 | path: flux.FieldTypes.string,
26 | bufId: flux.FieldTypes.number,
27 | client: flux.FieldTypes.string,
28 | platform: flux.FieldTypes.string,
29 | version: flux.FieldTypes.string,
30 | connected: flux.FieldTypes.bool,
31 | isMe: flux.FieldTypes.bool,
32 | // B&W thumbnail
33 | image: flux.FieldTypes.object.defaults(null),
34 | // Video or audio stream if chatting
35 | streamURL: flux.FieldTypes.string,
36 | audioOnly: flux.FieldTypes.bool,
37 | inVideoChat: flux.FieldTypes.bool,
38 | screenStreamURL: flux.FieldTypes.string
39 | },
40 | init: function () {
41 | this.visualizer = new Visualizer();
42 | },
43 | visualizer: null,
44 | jsProcessor: null,
45 | stream: null,
46 | screenStream: null,
47 | isWeb: function () {
48 | return _.includes(utils.BROWSER_CLIENTS, this.client);
49 | },
50 | onSoundEvent: function (analyser) {
51 | var data = new Uint8Array(analyser.frequencyBinCount),
52 | max = 0;
53 | analyser.getByteFrequencyData(data);
54 | if (!data.length) {
55 | return;
56 | }
57 | _.each(data, function (frame) {
58 | max = Math.max(max, frame);
59 | });
60 | max = max / 7.96875; // 255 / 32
61 | this.visualizer.visualize(Math.min(max, 32));
62 | },
63 | stopStream: function () {
64 | if (this.jsProcessor) {
65 | this.jsProcessor.disconnect();
66 | }
67 | this.jsProcessor = null;
68 | },
69 | processStream: function (stream) {
70 | if (!context || !context.createAnalyser || !context.createScriptProcessor) {
71 | console.log("Browser has no audio analyser.");
72 | return;
73 | }
74 | console.log("begin audio thing");
75 | let audioSource = null;
76 | try {
77 | audioSource = context.createMediaStreamSource(stream);
78 | } catch (e) {
79 | console.log("Unable to create media stream source.", e);
80 | return;
81 | }
82 | const analyser = context.createAnalyser();
83 | analyser.fftSize = 32;
84 | analyser.smoothingTimeConstant = 0.3;
85 | const jsProcessor = context.createScriptProcessor(2048, 1, 1); // buffer size, input channels, output channels
86 | jsProcessor.onaudioprocess = this.onSoundEvent.bind(this, analyser);
87 | audioSource.connect(analyser);
88 | analyser.connect(jsProcessor);
89 | jsProcessor.connect(context.destination);
90 | this.stopStream();
91 | this.jsProcessor = jsProcessor;
92 | }
93 | });
94 |
95 | const Connections = flux.createCollection({
96 | modelName: "Connections",
97 | model: Connection,
98 | sort: function (a, b) {
99 | // Web clients (ones with video chat) at the top, otherwise highest conn id wins
100 | var aIsWeb = a.isWeb(),
101 | bIsWeb = b.isWeb();
102 |
103 | if (a.image && !b.image) {
104 | return -1;
105 | }
106 | if (b.image && !a.image) {
107 | return 1;
108 | }
109 | if (aIsWeb && !bIsWeb) {
110 | return -1;
111 | }
112 | if (bIsWeb && !aIsWeb) {
113 | return 1;
114 | }
115 | if (a.id > b.id) {
116 | return -1;
117 | }
118 | return 1;
119 | }
120 | });
121 |
122 | function User () {
123 | User.super_.apply(this, arguments);
124 | }
125 |
126 |
127 | flux.inherit(User, flux.createModel({
128 | modelName: "User",
129 | fieldTypes: {
130 | connections: Connections,
131 | id: flux.FieldTypes.string,
132 | permissions: flux.FieldTypes.list,
133 | isMe: flux.FieldTypes.bool,
134 | client: flux.FieldTypes.string,
135 | platform: flux.FieldTypes.string,
136 | isAnon: flux.FieldTypes.bool,
137 | version: flux.FieldTypes.string,
138 | can_contract: flux.FieldTypes.bool,
139 | rate: flux.FieldTypes.number,
140 | gravatar: flux.FieldTypes.string,
141 | },
142 | init: function (args) {
143 | if (args && args.color_) {
144 | this.color_ = args.color_;
145 | }
146 | },
147 | color_: null,
148 | getDefaultFields: function () {
149 | return {
150 | isAnon: true,
151 | permissions: new flux.List(),
152 | connections: new Connections()
153 | };
154 | }
155 | }));
156 |
157 | Object.defineProperty(User.prototype, "color", {
158 | get: function id() {
159 | if (this.color_) {
160 | return this.color_;
161 | }
162 | this.color_ = utils.user_color(this.id);
163 | return this.color_;
164 | }
165 | });
166 |
167 | Object.defineProperty(User.prototype, "isAdmin", {
168 | get: function isAdmin () {
169 | return this.permissions.indexOf("kick") !== -1;
170 | }
171 | });
172 |
173 | Object.defineProperty(User.prototype, "username", {
174 | get: function getUsername() {
175 | return this.id;
176 | }
177 | });
178 |
179 | User.prototype.getConnectionID = function () {
180 | var conn = this.connections.valueOf()[0];
181 | return conn && conn.id;
182 | };
183 |
184 | User.prototype.createConnection = function (connectionId, client, platform, version, isMe) {
185 | var conn = new Connection({
186 | id: connectionId,
187 | path: "",
188 | bufId: 0,
189 | client: client,
190 | platform: platform,
191 | version: version,
192 | connected: true,
193 | isMe: isMe,
194 | inVideoChat: false,
195 | });
196 | this.connections.add(conn);
197 | return conn;
198 | };
199 |
200 | User.prototype.kick = function () {
201 | this.connections.forEach(function (conn) {
202 | console.log("kicking", conn);
203 | editorAction.kick(conn.id);
204 | });
205 | };
206 |
207 | User.prototype.getMyConnection = function () {
208 | var conn = _.find(this.connections.valueOf(), function (c) {
209 | return c.isMe;
210 | });
211 | return conn && this.connections.get(conn.id);
212 | };
213 |
214 | function Users () {
215 | Users.super_.apply(this, arguments);
216 | floop.onDATAMSG(function (msg) {
217 | if (msg.data.name === "user_image") {
218 | const user = this.getByConnectionID(msg.user_id);
219 | if (!user) {
220 | return;
221 | }
222 | user.connections.get(msg.user_id).image = msg.data.image;
223 | }
224 | }, this);
225 | }
226 |
227 | flux.inherit(Users, flux.createCollection({
228 | modelName: "Users",
229 | model: User
230 | }));
231 |
232 | Users.prototype.getByConnectionID = function (connectionId) {
233 | return _.find(this.data.collection, function (user) {
234 | return user.connections.get(connectionId);
235 | });
236 | };
237 |
238 | Users.prototype.getConnectionByConnectionID = function (connectionId) {
239 | var user = this.getByConnectionID(connectionId);
240 | return user && user.connections.get(connectionId);
241 | };
242 |
243 | Users.prototype.broadcast_data_message_for_perm = function (datamsg, perm) {
244 | const ids = [];
245 | this.forEach(function (user) {
246 | user.connections.forEach(function (conn) {
247 | if (!conn.isMe && _.includes(utils.BROWSER_CLIENTS, conn.client) && user.permissions.indexOf(perm) > -1) {
248 | ids.push(conn.id);
249 | }
250 | });
251 | });
252 |
253 | if (!ids.length) {
254 | return;
255 | }
256 |
257 | floop.emitDataMessage(datamsg, ids);
258 | };
259 |
260 | module.exports = {
261 | User,
262 | Users,
263 | };
264 |
--------------------------------------------------------------------------------
/lib/common/webrtc_action.js:
--------------------------------------------------------------------------------
1 | /* @flow weak */
2 | /*global chrome, self */
3 | "use strict";
4 |
5 | var canShareScreen = false,
6 | cantStartVideoChat = false;
7 |
8 | const _ = require("lodash");
9 | const flux = require("flukes");
10 |
11 | const Modal = require("../modal");
12 | const editorAction = require("./editor_action");
13 | const messageAction = require("./message_action");
14 | const perms = require("./permission_model");
15 | const prefs = require("./userPref_model");
16 |
17 |
18 | const Actions = flux.createActions({
19 | start_video_chat: function (connId) {
20 | if (cantStartVideoChat) {
21 | return new Error("Can't start video yet.");
22 | }
23 | if (prefs.dnd) {
24 | editorAction.pref("dnd", false);
25 | messageAction.info("Do not disturb disabled.");
26 | }
27 | if (perms.indexOf("patch") === -1) {
28 | messageAction.info("You need edit permissions to video chat.");
29 | return new Error("No permission to video chat.");
30 | }
31 | // Kinda hacky but whatever.
32 | if (prefs.audioOnly) {
33 | this.start_audio_chat(connId);
34 | return new Error("Starting audio chat.");
35 | }
36 | return connId;
37 | },
38 | stop_video_chat: function (connId) {
39 | // Kinda hacky but whatever.
40 | if (prefs.audioOnly) {
41 | this.stop_audio_chat(connId);
42 | return new Error("Stopping audio chat.");
43 | }
44 | return connId;
45 | },
46 | start_audio_chat: function (connId) {
47 | return connId;
48 | },
49 | stop_audio_chat: function (connId) {
50 | return connId;
51 | },
52 | start_screen: function (connId) {
53 | var errMsg;
54 | if (canShareScreen) {
55 | if (prefs.dnd) {
56 | editorAction.pref("dnd", false);
57 | messageAction.info("Do not disturb disabled.");
58 | }
59 | return connId;
60 | }
61 |
62 | if (!self.chrome || !chrome.app) {
63 | errMsg = "Screen sharing requires Google Chrome and the Floobits screen sharing extension.";
64 | Modal.showWithText(errMsg, "Can't Share Screen");
65 | messageAction.warn(errMsg);
66 | return new Error(errMsg);
67 | }
68 |
69 | try {
70 | chrome.webstore.install("https://chrome.google.com/webstore/detail/lmojaknpofhmdnbpanagbbeinbjmbodo", function () {
71 | self.location.reload();
72 | });
73 | } catch (e) {
74 | self.open("https://chrome.google.com/webstore/detail/lmojaknpofhmdnbpanagbbeinbjmbodo");
75 | }
76 | return new Error("User may install extension");
77 | },
78 | stop_screen: function (connId) {
79 | return connId;
80 | },
81 | can_share_screen: function (can) {
82 | canShareScreen = can;
83 | return can;
84 | },
85 | get_constraints: function (type, baseContraints, cb) {
86 | const constraints = _.extend({}, baseContraints);
87 |
88 | const getSource = function (sources, _type) {
89 | const id = prefs["source_" + _type + "_id"];
90 |
91 | let source = _.find(sources, function (s) {
92 | return s.id === id && s.kind === _type;
93 | });
94 |
95 | if (source) {
96 | return source.id;
97 | }
98 |
99 | const name = prefs["source_" + _type + "_name"];
100 |
101 | console.warn("Could not find video source", id);
102 | source = _.find(sources, function (s) {
103 | return s.label === name && s.kind === _type;
104 | });
105 |
106 | if (source) {
107 | console.warn("Found source with the same name", name);
108 | return source.id;
109 | }
110 | return null;
111 | };
112 |
113 | if (!MediaStreamTrack || !MediaStreamTrack.getSources || type === "screen") {
114 | return cb(null, constraints);
115 | }
116 |
117 | MediaStreamTrack.getSources(function (sources) {
118 | let getAudioDevice = type === "audio";
119 | if (type === "video") {
120 | if (_.isBoolean(baseContraints.audio)) {
121 | getAudioDevice = baseContraints.audio;
122 | } else {
123 | getAudioDevice = _.isObject(baseContraints.audio);
124 | }
125 | }
126 | if (getAudioDevice) {
127 | const id = getSource(sources, "audio");
128 | if (id) {
129 | let c = constraints.audio;
130 | if (_.isBoolean(c)) {
131 | c = constraints.audio = {};
132 | }
133 | if (!c.optional) {
134 | c.optional = [];
135 | }
136 | c.optional.push({sourceId: id});
137 | }
138 | }
139 | if (type === "video") {
140 | const id = getSource(sources, type);
141 | if (id) {
142 | constraints[type].optional.push({sourceId: id});
143 | }
144 | }
145 | console.log("constraints for", type, constraints);
146 | return cb(null, constraints);
147 | });
148 | },
149 | closedStreams: function () {
150 | if (cantStartVideoChat) {
151 | return;
152 | }
153 | cantStartVideoChat = true;
154 | _.delay(function () {
155 | cantStartVideoChat = false;
156 | }, 2000);
157 | },
158 | });
159 |
160 | module.exports = new Actions();
161 |
--------------------------------------------------------------------------------
/lib/floodmp.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const util = require("util");
4 |
5 | const dmp = require("dmp");
6 |
7 | function FlooDMP() {
8 | dmp.call(this);
9 | }
10 |
11 | util.inherits(FlooDMP, dmp);
12 |
13 | FlooDMP.prototype.patch_apply = function (patches, text) {
14 | if (patches.length === 0) {
15 | return [text, []];
16 | }
17 |
18 | // Deep copy the patches so that no changes are made to originals.
19 | patches = this.patch_deepCopy(patches);
20 |
21 | let nullPadding = this.patch_addPadding(patches);
22 | text = nullPadding + text + nullPadding;
23 |
24 | this.patch_splitMax(patches);
25 | // delta keeps track of the offset between the expected and actual location
26 | // of the previous patch. If there are patches expected at positions 10 and
27 | // 20, but the first patch was found at 12, delta is 2 and the second patch
28 | // has an effective expected position of 22.
29 | let delta = 0;
30 | let results = [];
31 | let positions = [];
32 | for (let x = 0; x < patches.length; x++) {
33 | let position = [3, 0, ""];
34 | let expected_loc = patches[x].start2 + delta;
35 | let text1 = this.diff_text1(patches[x].diffs);
36 | let start_loc;
37 | let replacement_str;
38 | let end_loc = -1;
39 | if (text1.length > this.Match_MaxBits) {
40 | // patch_splitMax will only provide an oversized pattern in the case of
41 | // a monster delete.
42 | start_loc = this.match_main(text, text1.substring(0, this.Match_MaxBits),
43 | expected_loc);
44 | if (start_loc !== -1) {
45 | end_loc = this.match_main(text,
46 | text1.substring(text1.length - this.Match_MaxBits),
47 | expected_loc + text1.length - this.Match_MaxBits);
48 | if (end_loc === -1 || start_loc >= end_loc) {
49 | // Can't find valid trailing context. Drop this patch.
50 | start_loc = -1;
51 | }
52 | }
53 | } else {
54 | start_loc = this.match_main(text, text1, expected_loc);
55 | }
56 | if (start_loc === -1) {
57 | // No match found. :(
58 | results[x] = false;
59 | // Subtract the delta for this failed patch from subsequent patches.
60 | delta -= patches[x].length2 - patches[x].length1;
61 | } else {
62 | // Found a match. :)
63 | results[x] = true;
64 | delta = start_loc - expected_loc;
65 | let text2;
66 | if (end_loc === -1) {
67 | text2 = text.substring(start_loc, start_loc + text1.length);
68 | } else {
69 | text2 = text.substring(start_loc, end_loc + this.Match_MaxBits);
70 | }
71 | if (text1 === text2) {
72 | // Perfect match, just shove the replacement text in.
73 | replacement_str = this.diff_text2(patches[x].diffs);
74 | text = text.substring(0, start_loc) +
75 | replacement_str +
76 | text.substring(start_loc + text1.length);
77 | position = [start_loc, text1.length, replacement_str];
78 | } else {
79 | // Imperfect match. Run a diff to get a framework of equivalent
80 | // indices.
81 | let diffs = this.diff_main(text1, text2, false);
82 | if (text1.length > this.Match_MaxBits &&
83 | this.diff_levenshtein(diffs) / text1.length >
84 | this.Patch_DeleteThreshold) {
85 | // The end points match, but the content is unacceptably bad.
86 | results[x] = false;
87 | } else {
88 | this.diff_cleanupSemanticLossless(diffs);
89 | let index1 = 0;
90 | let index2;
91 | let delete_len = 0;
92 | let inserted_text = "";
93 | for (let y = 0; y < patches[x].diffs.length; y++) {
94 | let mod = patches[x].diffs[y];
95 | if (mod[0] !== dmp.DIFF_EQUAL) {
96 | index2 = this.diff_xIndex(diffs, index1);
97 | }
98 | if (mod[0] === dmp.DIFF_INSERT) { // Insertion
99 | text = text.substring(0, start_loc + index2) + mod[1] +
100 | text.substring(start_loc + index2);
101 | inserted_text += mod[1];
102 | } else if (mod[0] === dmp.DIFF_DELETE) { // Deletion
103 | let diff_index = this.diff_xIndex(diffs,
104 | index1 + mod[1].length);
105 | // self.diff_xIndex(diffs, index1 + len(data));
106 | text = text.substring(0, start_loc + index2) +
107 | text.substring(start_loc + diff_index);
108 | delete_len += (diff_index - index2);
109 | }
110 | if (mod[0] !== dmp.DIFF_DELETE) {
111 | index1 += mod[1].length;
112 | }
113 | }
114 | position = [start_loc, delete_len, inserted_text];
115 | }
116 | }
117 | }
118 | let np_len = nullPadding.length;
119 | if (position[0] < np_len){
120 | position[1] -= np_len - position[0];
121 | position[2] = position[2].substring(np_len - position[0]);
122 | position[0] = 0;
123 | }else{
124 | position[0] -= np_len;
125 | }
126 |
127 | let too_close = (position[0] + position[2].length) - (text.length - 2 * np_len);
128 | if (too_close > 0) {
129 | position[2] = position[2].substring(0, position[2].length - too_close);
130 | }
131 | positions.push(position);
132 | }
133 | // Strip the padding off.
134 | text = text.substring(nullPadding.length, text.length - nullPadding.length);
135 | return [text, results, positions];
136 | };
137 |
138 | exports.FlooDMP = FlooDMP;
139 |
--------------------------------------------------------------------------------
/lib/floourl.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var util = require("util");
4 |
5 | function FlooUrl(owner, workspace, host, port) {
6 | this.owner = owner;
7 | this.workspace = workspace;
8 | this.host = host;
9 | this.port = port;
10 | }
11 |
12 | FlooUrl.prototype.toAPIString = function () {
13 | return util.format("https://%s/api/room/%s/%s", this.host, this.owner, this.workspace);
14 | };
15 |
16 | FlooUrl.prototype.toString = function () {
17 | let port = parseInt(this.port, 10);
18 | return util.format("https://%s%s/%s/%s", this.host, port === 3448 ? "" : ":" + this.port, this.owner, this.workspace);
19 | };
20 |
21 | exports.FlooUrl = FlooUrl;
22 |
--------------------------------------------------------------------------------
/lib/follow_view.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | "use babel";
3 |
4 | const $$ = require("atom-space-pen-views").$$;
5 |
6 | const editorAction = require("./common/editor_action");
7 | const prefs = require("./common/userPref_model");
8 | const SelectListView = require("./select_list_view");
9 | const utils = require("./common/utils");
10 | const EVERYONE = "all changes";
11 |
12 | var FollowView = function (me, users) {
13 | this.items_ = {};
14 | this.users = users;
15 | this.me = me;
16 | SelectListView.call(this);
17 | };
18 |
19 | utils.inherits(FollowView, SelectListView);
20 |
21 | FollowView.prototype.initialize = function () {
22 | SelectListView.prototype.initialize.apply(this, arguments);
23 | this.addClass("modal overlay from-top");
24 | let myUsername = this.me.username;
25 | let items = this.users.map(function (u) {
26 | let username = u.username;
27 | this.items_[username] = prefs.followUsers.indexOf(username) !== -1;
28 | return u.username;
29 | }, this).filter(function (username) {
30 | return username !== myUsername;
31 | });
32 | items.unshift(EVERYONE);
33 | this.items_[EVERYONE] = prefs.following;
34 | this.setItems(items);
35 | };
36 |
37 | FollowView.prototype.viewForItem = function (name) {
38 | var items = this.items_;
39 | return $$(function () {
40 | var that = this;
41 | this.li({"class": ""}, function () {
42 | that.div({"class": "primary-line icon icon-" + (items[name] ? "mute" : "unmute")}, (items[name] ? "Stop following " : "Follow ") + name);
43 | });
44 | });
45 | };
46 |
47 | FollowView.prototype.confirmed = function (name) {
48 | SelectListView.prototype.confirmed.call(this, name);
49 | if (name === EVERYONE) {
50 | name = "";
51 | }
52 | editorAction.follow(name);
53 | };
54 |
55 | module.exports = FollowView;
56 |
--------------------------------------------------------------------------------
/lib/media_sources.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const $$ = require("atom-space-pen-views").$$;
4 | const _ = require("lodash");
5 |
6 | const utils = require("./common/utils");
7 | const SelectListView = require("./select_list_view");
8 |
9 | function MediaSources (sources, cb) {
10 | SelectListView.call(this, cb);
11 | this.sources = sources;
12 | const items = _.map(this.sources, function (source) {
13 | return source.label;
14 | });
15 | this.setItems(items);
16 | }
17 |
18 | utils.inherits(MediaSources, SelectListView);
19 |
20 | MediaSources.prototype.viewForItem = function (label) {
21 | return $$(function () {
22 | this.li(label);
23 | });
24 | };
25 |
26 | MediaSources.prototype.confirmed = function (label) {
27 | const source = _.filter(this.sources, {label: label})[0];
28 | SelectListView.prototype.confirmed.call(this, source);
29 | };
30 |
31 | module.exports = MediaSources;
32 |
--------------------------------------------------------------------------------
/lib/modal.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | const atomUtils = require("./atom_utils");
3 |
4 | module.exports = {
5 | showView: function (view) {
6 | atomUtils.addModalPanel("handle-request-perm", view);
7 | },
8 | showWithText: function (title, msg) {
9 | atom.confirm({
10 | message: "Floobits: " + title,
11 | detailedMessage: msg,
12 | buttons: ["OK"]
13 | });
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/lib/react_wrapper.js:
--------------------------------------------------------------------------------
1 | /* global HTMLElement */
2 | "use strict";
3 | "use babel";
4 |
5 | const _ = require("lodash");
6 | const React = require("react-atom-fork");
7 |
8 | const ELEMENTS = {};
9 |
10 | const create_wrapper = function (name) {
11 | var Proto = Object.create(HTMLElement.prototype);
12 |
13 | Proto.init = function (component, styles) {
14 | this.reactNode = null;
15 | this.component = component;
16 | _.each(styles, (v, k) => {
17 | this.style[k] = v;
18 | });
19 | return this;
20 | };
21 |
22 | Proto.createdCallback = function () {
23 | };
24 |
25 | Proto.attachedCallback = function () {
26 | this.reactNode = React.renderComponent(this.component, this);
27 | };
28 |
29 | // Proto.attributeChangedCallback = function (attrName, oldVal, newVal) {
30 | // return;
31 | // };
32 |
33 | Proto.detachedCallback = function () {
34 | return;
35 | };
36 |
37 | Proto.onDestroy = function (pane) {
38 | this.pane = pane;
39 | };
40 |
41 | Proto.destroy = function () {
42 | if (!this.pane) {
43 | return;
44 | }
45 | this.pane.destroy();
46 | this.pane = null;
47 | };
48 |
49 | return document.registerElement(name, {prototype: Proto});
50 | };
51 |
52 | // Warning! If you choose the same node name as another plugin, this will throw!
53 | const create_node_unsafe = function (name, component, style) {
54 | let node;
55 | if (name in ELEMENTS) {
56 | node = new ELEMENTS[name]();
57 | } else {
58 | let Element = create_wrapper(name);
59 | ELEMENTS[name] = Element;
60 | node = new Element();
61 | }
62 | node.init(component, style);
63 | return node;
64 | };
65 |
66 | module.exports = {
67 | create_node: function (name, component, style) {
68 | return create_node_unsafe("floobits-" + name, component, style);
69 | },
70 | create_node_unsafe: create_node_unsafe,
71 | create_wrapper: create_wrapper,
72 | };
73 |
--------------------------------------------------------------------------------
/lib/recentworkspaceview.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const $$ = require("atom-space-pen-views").$$;
4 | const _ = require("lodash");
5 |
6 | const utils = require("./common/utils");
7 | const PersistentJson = require("./common/persistentjson");
8 | const SelectListView = require("./select_list_view");
9 |
10 | function RecentWorkspaceView () {
11 | SelectListView.call(this);
12 | }
13 |
14 | utils.inherits(RecentWorkspaceView, SelectListView);
15 |
16 | RecentWorkspaceView.prototype.initialize = function () {
17 | SelectListView.prototype.initialize.apply(this, arguments);
18 |
19 | this.addClass("overlay from-top");
20 | const pj = new PersistentJson().load();
21 | const recent_workspaces = _.pluck(pj.recent_workspaces, "url");
22 | this.items_ = {};
23 | const items = recent_workspaces.map((workspace) => {
24 | const stuff = utils.parse_url(workspace);
25 | let p;
26 | try{
27 | p = pj.workspaces[stuff.owner][stuff.workspace].path;
28 | } catch(e) {
29 | p = "?";
30 | }
31 | this.items_[p] = workspace;
32 | return p;
33 | });
34 | this.setItems(items);
35 | };
36 |
37 | RecentWorkspaceView.prototype.viewForItem = function (path) {
38 | const items = this.items_;
39 | return $$(function () {
40 | const that = this;
41 | this.li({"class": "two-lines"}, function () {
42 | that.div({"class": "primary-line file icon icon-file-text"}, path);
43 | that.div({"class": "secondary-line path no-icon"}, items[path]);
44 | });
45 | });
46 | };
47 |
48 | RecentWorkspaceView.prototype.confirmed = function (path) {
49 | // if (d && d.getRealPathSync() === path) {
50 | // return require("./floobits").join_workspace(this.items_[path]);
51 | // }
52 |
53 | atom.config.set("floobits.atoms-api-sucks-url", this.items_[path]);
54 | atom.config.set("floobits.atoms-api-sucks-path", path);
55 | atom.open({pathsToOpen: [path], newWindow: true});
56 | };
57 |
58 | exports.RecentWorkspaceView = RecentWorkspaceView;
59 |
--------------------------------------------------------------------------------
/lib/select_directory_to_share.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const $$ = require("atom-space-pen-views").$$;
4 |
5 | const utils = require("./common/utils");
6 | const SelectListView = require("./select_list_view");
7 |
8 | function DirectorySelectorView (directories, cb) {
9 | SelectListView.call(this, cb);
10 | this.setItems(directories);
11 | }
12 |
13 | utils.inherits(DirectorySelectorView, SelectListView);
14 |
15 | DirectorySelectorView.prototype.viewForItem = function (directory) {
16 | return $$(function () {
17 | this.li(directory.getBaseName());
18 | });
19 | };
20 |
21 | module.exports = DirectorySelectorView;
22 |
--------------------------------------------------------------------------------
/lib/select_list_view.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | "use babel";
3 |
4 | const AtomSelectListView = require("atom-space-pen-views").SelectListView;
5 |
6 | const utils = require("./common/utils");
7 |
8 |
9 | function SelectListView (cb) {
10 | this.cb = cb;
11 | AtomSelectListView.call(this);
12 | // this nextTick is needed for some reason... :(
13 | process.nextTick(function () {
14 | this.panel = atom.workspace.addModalPanel({item: this});
15 | this.storeFocusedElement();
16 | this.focusFilterEditor();
17 | }.bind(this));
18 | }
19 |
20 | utils.inherits(SelectListView, AtomSelectListView);
21 |
22 | SelectListView.prototype.initialize = function () {
23 | AtomSelectListView.prototype.initialize.apply(this, arguments);
24 | this.addClass("overlay from-top");
25 | };
26 |
27 | SelectListView.prototype.cancel = function () {
28 | AtomSelectListView.prototype.cancel.apply(this);
29 | this.panel && this.panel.destroy();
30 | };
31 |
32 | SelectListView.prototype.confirmed = function (arg) {
33 | console.debug(`${arg} selected`);
34 | this.cb && this.cb(null, arg);
35 | this.cancel();
36 | };
37 |
38 | module.exports = SelectListView;
39 |
--------------------------------------------------------------------------------
/lib/terminal_manager.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | const Socket = require("./common/floop");
3 | const _ = require("lodash");
4 | // const editorAction = require("./common/editor_action");
5 | const CompositeDisposable = require('event-kit').CompositeDisposable;
6 | const prefs = require("./common/userPref_model");
7 |
8 | function TerminalManager () {
9 | // floobits term id to this.Term3 TermialView
10 | this.terms = {};
11 |
12 | this.term3_name_to_ids = {};
13 |
14 | this.term3 = null;
15 | this.username = null;
16 | this.floobits_terms = null;
17 | }
18 |
19 | TerminalManager.prototype.send_create_term = function (t) {
20 | if (!t.getForked()) {
21 | console.log("not sending create event for remotely driven terminal");
22 | return;
23 | }
24 | const d = t.getDimensions();
25 | const name = this.nameForTerm(t);
26 | this.term3_name_to_ids[name] = t.id;
27 | Socket.send_create_term({term_name: name, size: [d.cols, d.rows]});
28 | };
29 |
30 | TerminalManager.prototype.nameForTerm = function (term) {
31 | return `atom-${this.username.replace('.','-')}-${term.id}-${Math.floor(Math.random() * 10000)}`;
32 | };
33 |
34 | TerminalManager.prototype.on_floobits = function(username, floobits_terms) {
35 | this.username = username;
36 | this.floobits_terms = floobits_terms;
37 |
38 | if (!this.term3) {
39 | return;
40 | }
41 | this.start_();
42 | };
43 |
44 | TerminalManager.prototype.on_term3_service = function(term3) {
45 | this.term3 = term3;
46 |
47 | if (!this.floobits_terms || !this.username) {
48 | return;
49 | }
50 | this.start_();
51 | };
52 |
53 | TerminalManager.prototype.start_ = function () {
54 | const that = this;
55 |
56 | this.subs = new CompositeDisposable();
57 |
58 | const disposable = that.term3.onTerm(that.send_create_term.bind(that));
59 |
60 | this.subs.add(disposable);
61 |
62 | that.term3.getTerminals().forEach(that.send_create_term.bind(that));
63 |
64 | _.each(that.floobits_terms, function (t, termID) {
65 | const term = that.term3.newTerm(false, t.size[1], t.size[0], t.owner);
66 | that.terms[termID] = term;
67 |
68 | const subs = new CompositeDisposable();
69 |
70 | subs.add(term.onSTDIN(function (d) {
71 | Socket.send_term_stdin({'data': new Buffer(d).toString("base64"), id: termID});
72 | }));
73 |
74 | subs.add(term.onExit(function () {
75 | if (that.subs) {
76 | that.subs.remove(subs);
77 | }
78 | subs.dispose();
79 | delete that.terms[termID];
80 | }));
81 |
82 | that.subs.add(subs);
83 | });
84 |
85 | Socket.onCREATE_TERM(function (req) {
86 | const name = req.term_name;
87 | const termID = req.id;
88 |
89 | const term3_ID = that.term3_name_to_ids[name];
90 | const term = _.find(that.term3.getTerminals(), function (t) {
91 | return t.id === term3_ID;
92 | });
93 |
94 | if (term) {
95 | that.terms[termID] = term;
96 | } else {
97 | that.terms[termID] = that.term3.newTerm(false);
98 | that.subs.add(that.terms[termID].onSTDIN(function (d) {
99 | Socket.send_term_stdin({'data': new Buffer(d).toString("base64"), id: termID});
100 | }));
101 | return;
102 | }
103 |
104 | const d = term.getDimensions();
105 | Socket.send_update_term({id: termID, size: [d.cols, d.rows]});
106 |
107 | const subs = new CompositeDisposable();
108 |
109 | subs.add(term.onSTDOUT(function (d) {
110 | Socket.send_term_stdout({data: new Buffer(d).toString("base64"), id: termID});
111 | }));
112 |
113 | subs.add(term.onResize(function (size) {
114 | Socket.send_update_term({id: termID, size: [size.cols, size.rows]});
115 | }));
116 |
117 | subs.add(term.onExit(function () {
118 | Socket.send_delete_term({id: termID});
119 | if (that.subs) {
120 | that.subs.remove(subs);
121 | }
122 | subs.dispose();
123 | delete that.terms[termID];
124 | }));
125 |
126 | that.subs.add(subs);
127 | });
128 |
129 | Socket.onTERM_STDIN(function (data) {
130 | const term = that.terms[data.id];
131 | if (!term) {
132 | return;
133 | }
134 |
135 | if (prefs.isFollowing(data.username)) {
136 | term.focusPane();
137 | }
138 |
139 | let term_data = data.data;
140 | if (!term_data.length) {
141 | return;
142 | }
143 |
144 | term_data = new Buffer(term_data, "base64").toString("utf-8");
145 |
146 | // TODO: allow terminals to be unsafe
147 | if (true) {
148 | /*eslint-disable no-control-regex */
149 | term_data = term_data.replace(/[\x04\x07\n\r]/g, "");
150 | /*eslint-enable no-control-regex */
151 | }
152 |
153 | try {
154 | term.input(term_data);
155 | } catch (e) {
156 | console.warn(e);
157 | }
158 | });
159 |
160 | Socket.onDELETE_TERM(function (data) {
161 | const term = that.terms[data.id];
162 |
163 | if (!term) {
164 | return;
165 | }
166 |
167 | term.input("\r\n *** TERMINAL CLOSED *** \r\n");
168 | });
169 |
170 | Socket.onTERM_STDOUT(function (data) {
171 | const term = that.terms[data.id];
172 |
173 | if (!term) {
174 | return;
175 | }
176 |
177 | try {
178 | term.input(new Buffer(data.data, "base64").toString("utf-8"));
179 | } catch (e) {
180 | console.warn(e);
181 | }
182 | });
183 |
184 | Socket.onUPDATE_TERM(function (data) {
185 | const term = that.terms[data.id];
186 |
187 | if (!term) {
188 | return;
189 | }
190 |
191 | console.log("update term", data);
192 | if (data.size) {
193 | term.resize(data.size[0], data.size[1]);
194 | }
195 | });
196 |
197 | Socket.onSYNC(function (terms) {
198 | console.log("Attempting to sync...");
199 | _.each(terms, function (data, id) {
200 | const term = that.terms[id];
201 |
202 | if (!term) {
203 | return;
204 | }
205 |
206 | term.resize(data.cols, data.rows);
207 | });
208 | });
209 | };
210 |
211 | TerminalManager.prototype.stop = function(term3_disposed) {
212 | if (this.subs) {
213 | this.subs.dispose();
214 | this.subs = null;
215 | }
216 | // we may not get another term3...
217 | if (term3_disposed) {
218 | this.term3 = null;
219 | }
220 | this.floobits_terms = null;
221 | this.username = null;
222 | };
223 |
224 | module.exports = new TerminalManager();
225 |
--------------------------------------------------------------------------------
/lib/transport.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const util = require("util");
4 | const tls = require("tls");
5 | const CommonTransport = require("./common/transport");
6 |
7 | function Transport(host, port) {
8 | CommonTransport.call(this);
9 | this.conn_ = null;
10 | this.conn_buf = "";
11 | this.host = host;
12 | this.port = port;
13 | }
14 |
15 | util.inherits(Transport, CommonTransport);
16 |
17 | Transport.prototype.data_handler_ = function (d) {
18 | var msg, newline_index;
19 |
20 | this.conn_buf += d;
21 |
22 | newline_index = this.conn_buf.indexOf("\n");
23 | while (newline_index !== -1) {
24 | msg = this.conn_buf.slice(0, newline_index);
25 | this.conn_buf = this.conn_buf.slice(newline_index + 1);
26 | newline_index = this.conn_buf.indexOf("\n");
27 | msg = JSON.parse(msg);
28 |
29 | if (!msg.name) {
30 | continue;
31 | }
32 |
33 | this.emit("messaged", msg.name, msg);
34 | }
35 | };
36 |
37 | Transport.prototype.reconnect_ = function () {
38 | try {
39 | this.conn_.off();
40 | this.conn_.close();
41 | } catch (ignore) {
42 | // ignore
43 | }
44 |
45 | CommonTransport.prototype.reconnect_.call(this);
46 | };
47 |
48 | Transport.prototype.connect = function () {
49 | var that = this;
50 |
51 | that.conn_buf = "";
52 | that.conn_ = tls.connect({
53 | host: this.host,
54 | port: this.port,
55 | }, function () {
56 | that.emit("connected");
57 | });
58 | that.conn_.setEncoding("utf8");
59 | that.onEnd_ = function () {
60 | console.warn("socket is gone");
61 | that.emit("disconnected");
62 | that.reconnect_();
63 | };
64 | that.conn_.once("end", that.onEnd_);
65 | that.onData_ = function (data) {
66 | that.data_handler_(data);
67 | };
68 | that.conn_.on("data", that.onData_);
69 | that.onError_ = function (err) {
70 | console.error("Connection error:", err);
71 | that.emit("disconnected", err);
72 | that.reconnect_();
73 | };
74 | that.conn_.once("error", that.onError_);
75 | };
76 |
77 | Transport.prototype.disconnect = function (msg) {
78 | if (!this.conn_) {
79 | return msg;
80 | }
81 |
82 | CommonTransport.prototype.disconnect.call(this);
83 |
84 | try {
85 | this.conn_.removeListener("end", this.onEnd_);
86 | this.conn_.removeListener("data", this.onData_);
87 | this.conn_.removeListener("error", this.onError_);
88 | this.onEnd_ = null;
89 | this.onData_ = null;
90 | this.onError_ = null;
91 | } catch (e) {
92 | //ignore
93 | }
94 |
95 | try {
96 | this.conn_.end();
97 | this.conn_.destroy();
98 | } catch (e) {
99 | //ignore
100 | }
101 |
102 | this.conn_buf = "";
103 | this.conn_ = null;
104 | return msg;
105 | };
106 |
107 | Transport.prototype.write = function (name, msg, cb, context) {
108 | var str;
109 |
110 | if (!this.conn_) {
111 | return cb && cb("not connected");
112 | }
113 |
114 | if (name) {
115 | msg.name = name;
116 | }
117 |
118 | str = util.format("%s\n", JSON.stringify(msg));
119 |
120 | if (str.length > 1000) {
121 | console.info("writing to conn:", str.slice(0, 1000) + '...');
122 | } else {
123 | console.info("writing to conn:", str);
124 | }
125 |
126 |
127 | try {
128 | this.conn_.write(str, cb && cb.bind(context));
129 | } catch (e) {
130 | console.error("error writing to client:", e, "disconnecting");
131 | }
132 | };
133 |
134 | module.exports = Transport;
135 |
--------------------------------------------------------------------------------
/menus/floobits.cson:
--------------------------------------------------------------------------------
1 | # See https://atom.io/docs/latest/creating-a-package#menus for more details
2 | 'context-menu':
3 | 'atom-workspace': [
4 | # { 'label': 'Enable Floobits', 'command': 'Floobits: Join Workspace' }
5 | # { 'label': 'Enable Floobits', 'command': 'Floobits: Join Recent Workspace' }
6 | ]
7 |
8 | 'menu': [
9 | {
10 | 'label': 'Packages'
11 | 'submenu': [
12 | 'label': 'Floobits'
13 | 'submenu': [
14 | { 'label': 'Settings', 'command': 'Floobits: Settings' }
15 | { 'label': 'Join Workspace', 'command': 'Floobits: Join Workspace' }
16 | # { 'label': 'Join Recent Workspace', 'command': 'Floobits: Join Recent Workspace' }
17 | { 'label': 'Leave Workspace', 'command': 'Floobits: Leave Workspace' }
18 | { 'label': 'Refresh Workspace', 'command': 'Floobits: Refresh Workspace' }
19 | { 'label': 'Create Workspace', 'command': 'Floobits: Create Workspace' }
20 | { 'label': 'Add Current File', 'command': 'Floobits: Add Current File' }
21 | { 'label': 'Toggle User List', 'command': 'Floobits: Toggle User List Panel' }
22 | { 'label': 'Request Code Review', 'command': 'Floobits: Request Code Review' }
23 | ]
24 | ]
25 | }
26 | ]
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "floobits",
3 | "main": "./lib/floobits",
4 | "version": "0.32.12",
5 | "description": "The Floobits pair programming plugin for Atom. This plugin adds support for real-time collaborative editing, video chatting, and sharing terminals via the Term3 plugin. A Floobits account is required, but the plugin will guide you.",
6 | "maintainers": [
7 | {
8 | "name": "Geoff Greer",
9 | "web": "http://geoff.greer.fm/"
10 | },
11 | {
12 | "name": "Matt Kaniaris",
13 | "web": "https://github.com/kans"
14 | }
15 | ],
16 | "contributors": [
17 | {
18 | "name": "Geoff Greer",
19 | "web": "http://geoff.greer.fm/"
20 | },
21 | {
22 | "name": "Matt Kaniaris",
23 | "web": "https://github.com/kans"
24 | },
25 | {
26 | "name": "Bjorn Tipling",
27 | "web": "https://github.com/btipling"
28 | }
29 | ],
30 | "repository": "https://github.com/Floobits/floobits-atom",
31 | "license": "Apache-2.0",
32 | "engines": {
33 | "atom": ">=1.0"
34 | },
35 | "dependencies": {
36 | "async": "1.x",
37 | "atom-space-pen-views": "2.2.x",
38 | "dmp": "1.x",
39 | "flukes": ">=0.1.4",
40 | "fs-plus": "2.x",
41 | "lodash": "4.x",
42 | "minimatch": "3.x",
43 | "mkdirp": "0.5.x",
44 | "open": "0.x",
45 | "react-atom-fork": "0.11.x",
46 | "react-tools": "0.11.x",
47 | "reactionary-atom-fork": "1.x",
48 | "request": "2.x",
49 | "event-kit": "1.5.x",
50 | "chokidar": "1.x"
51 | },
52 | "consumedServices": {
53 | "term3-service": {
54 | "versions": {
55 | "0.1.3": "term3-service"
56 | }
57 | }
58 | },
59 | "devDependencies": {
60 | "eslint": "3.x",
61 | "eslint-config-floobits": "*",
62 | "eslint-plugin-react": "5.x"
63 | },
64 | "scripts": {
65 | "build": "npm run install",
66 | "lint": "eslint lib templates",
67 | "watch": "jsx --watch --harmony templates lib/build",
68 | "install": "jsx --harmony templates lib/build"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/resources/anonymous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/anonymous.png
--------------------------------------------------------------------------------
/resources/ascii_town.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/ascii_town.png
--------------------------------------------------------------------------------
/resources/dark_noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/dark_noise.png
--------------------------------------------------------------------------------
/resources/disc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/disc.png
--------------------------------------------------------------------------------
/resources/edit_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/edit_icon.png
--------------------------------------------------------------------------------
/resources/email_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/email_icon.png
--------------------------------------------------------------------------------
/resources/fl_logo146_30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fl_logo146_30.png
--------------------------------------------------------------------------------
/resources/fonts/glyphicons-halflings/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/glyphicons-halflings/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/resources/fonts/glyphicons-halflings/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/glyphicons-halflings/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/resources/fonts/glyphicons-halflings/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/glyphicons-halflings/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/resources/fonts/montserrat/Montserrat-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/montserrat/Montserrat-Bold.ttf
--------------------------------------------------------------------------------
/resources/fonts/montserrat/Montserrat-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/montserrat/Montserrat-Regular.ttf
--------------------------------------------------------------------------------
/resources/fonts/montserrat/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2012, Julieta Ulanovsky (julieta.ulanovsky@gmail.com), with Reserved Font Names 'Montserrat'
2 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
3 | This license is copied below, and is also available with a FAQ at:
4 | http://scripts.sil.org/OFL
5 |
6 |
7 | -----------------------------------------------------------
8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
9 | -----------------------------------------------------------
10 |
11 | PREAMBLE
12 | The goals of the Open Font License (OFL) are to stimulate worldwide
13 | development of collaborative font projects, to support the font creation
14 | efforts of academic and linguistic communities, and to provide a free and
15 | open framework in which fonts may be shared and improved in partnership
16 | with others.
17 |
18 | The OFL allows the licensed fonts to be used, studied, modified and
19 | redistributed freely as long as they are not sold by themselves. The
20 | fonts, including any derivative works, can be bundled, embedded,
21 | redistributed and/or sold with any software provided that any reserved
22 | names are not used by derivative works. The fonts and derivatives,
23 | however, cannot be released under any other type of license. The
24 | requirement for fonts to remain under this license does not apply
25 | to any document created using the fonts or their derivatives.
26 |
27 | DEFINITIONS
28 | "Font Software" refers to the set of files released by the Copyright
29 | Holder(s) under this license and clearly marked as such. This may
30 | include source files, build scripts and documentation.
31 |
32 | "Reserved Font Name" refers to any names specified as such after the
33 | copyright statement(s).
34 |
35 | "Original Version" refers to the collection of Font Software components as
36 | distributed by the Copyright Holder(s).
37 |
38 | "Modified Version" refers to any derivative made by adding to, deleting,
39 | or substituting -- in part or in whole -- any of the components of the
40 | Original Version, by changing formats or by porting the Font Software to a
41 | new environment.
42 |
43 | "Author" refers to any designer, engineer, programmer, technical
44 | writer or other person who contributed to the Font Software.
45 |
46 | PERMISSION & CONDITIONS
47 | Permission is hereby granted, free of charge, to any person obtaining
48 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
49 | redistribute, and sell modified and unmodified copies of the Font
50 | Software, subject to the following conditions:
51 |
52 | 1) Neither the Font Software nor any of its individual components,
53 | in Original or Modified Versions, may be sold by itself.
54 |
55 | 2) Original or Modified Versions of the Font Software may be bundled,
56 | redistributed and/or sold with any software, provided that each copy
57 | contains the above copyright notice and this license. These can be
58 | included either as stand-alone text files, human-readable headers or
59 | in the appropriate machine-readable metadata fields within text or
60 | binary files as long as those fields can be easily viewed by the user.
61 |
62 | 3) No Modified Version of the Font Software may use the Reserved Font
63 | Name(s) unless explicit written permission is granted by the corresponding
64 | Copyright Holder. This restriction only applies to the primary font name as
65 | presented to the users.
66 |
67 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
68 | Software shall not be used to promote, endorse or advertise any
69 | Modified Version, except to acknowledge the contribution(s) of the
70 | Copyright Holder(s) and the Author(s) or with their explicit written
71 | permission.
72 |
73 | 5) The Font Software, modified or unmodified, in part or in whole,
74 | must be distributed entirely under this license, and must not be
75 | distributed under any other license. The requirement for fonts to
76 | remain under this license does not apply to any document created
77 | using the Font Software.
78 |
79 | TERMINATION
80 | This license becomes null and void if any of the above conditions are
81 | not met.
82 |
83 | DISCLAIMER
84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
88 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92 | OTHER DEALINGS IN THE FONT SOFTWARE.
93 |
--------------------------------------------------------------------------------
/resources/fonts/proxima_nova/ProximaNova-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/proxima_nova/ProximaNova-Bold.otf
--------------------------------------------------------------------------------
/resources/fonts/proxima_nova/ProximaNova-Extrabold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/proxima_nova/ProximaNova-Extrabold.otf
--------------------------------------------------------------------------------
/resources/fonts/proxima_nova/ProximaNova-Light_0.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/proxima_nova/ProximaNova-Light_0.otf
--------------------------------------------------------------------------------
/resources/fonts/proxima_nova/ProximaNova-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/proxima_nova/ProximaNova-Regular.otf
--------------------------------------------------------------------------------
/resources/fonts/proxima_nova/ProximaNova-Semibold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/proxima_nova/ProximaNova-Semibold.otf
--------------------------------------------------------------------------------
/resources/fonts/proxima_nova/ProximaNovaCond-Light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/proxima_nova/ProximaNovaCond-Light.otf
--------------------------------------------------------------------------------
/resources/fonts/proxima_nova/ProximaNovaCond-RegularIt.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/proxima_nova/ProximaNovaCond-RegularIt.otf
--------------------------------------------------------------------------------
/resources/fonts/proxima_nova/ProximaNovaCond-Semibold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/proxima_nova/ProximaNovaCond-Semibold.otf
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_bold_macroman/RobotoSlab-Bold-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_bold_macroman/RobotoSlab-Bold-webfont.eot
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_bold_macroman/RobotoSlab-Bold-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_bold_macroman/RobotoSlab-Bold-webfont.ttf
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_bold_macroman/RobotoSlab-Bold-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_bold_macroman/RobotoSlab-Bold-webfont.woff
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_bold_macroman/stylesheet.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'roboto_slabbold';
3 | src: url('RobotoSlab-Bold-webfont.eot');
4 | src: url('RobotoSlab-Bold-webfont.eot?#iefix') format('embedded-opentype'),
5 | url('RobotoSlab-Bold-webfont.woff') format('woff'),
6 | url('RobotoSlab-Bold-webfont.ttf') format('truetype'),
7 | url('RobotoSlab-Bold-webfont.svg#roboto_slabbold') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 |
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_light_macroman/RobotoSlab-Light-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_light_macroman/RobotoSlab-Light-webfont.eot
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_light_macroman/RobotoSlab-Light-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_light_macroman/RobotoSlab-Light-webfont.ttf
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_light_macroman/RobotoSlab-Light-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_light_macroman/RobotoSlab-Light-webfont.woff
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_regular_macroman/RobotoSlab-Regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_regular_macroman/RobotoSlab-Regular-webfont.eot
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_regular_macroman/RobotoSlab-Regular-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_regular_macroman/RobotoSlab-Regular-webfont.ttf
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_regular_macroman/RobotoSlab-Regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_regular_macroman/RobotoSlab-Regular-webfont.woff
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_thin_macroman/RobotoSlab-Thin-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_thin_macroman/RobotoSlab-Thin-webfont.eot
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_thin_macroman/RobotoSlab-Thin-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_thin_macroman/RobotoSlab-Thin-webfont.ttf
--------------------------------------------------------------------------------
/resources/fonts/robotoslab_thin_macroman/RobotoSlab-Thin-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/fonts/robotoslab_thin_macroman/RobotoSlab-Thin-webfont.woff
--------------------------------------------------------------------------------
/resources/icon_64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/icon_64x64.png
--------------------------------------------------------------------------------
/resources/owner_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/owner_icon.png
--------------------------------------------------------------------------------
/resources/password_conf_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/password_conf_icon.png
--------------------------------------------------------------------------------
/resources/password_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/password_icon.png
--------------------------------------------------------------------------------
/resources/settings_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/settings_icon.png
--------------------------------------------------------------------------------
/resources/sideways_infinity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/sideways_infinity.png
--------------------------------------------------------------------------------
/resources/sliderbg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/sliderbg.png
--------------------------------------------------------------------------------
/resources/sublime_demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/sublime_demo.png
--------------------------------------------------------------------------------
/resources/supported_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/supported_bg.png
--------------------------------------------------------------------------------
/resources/title_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/title_bg.png
--------------------------------------------------------------------------------
/resources/username_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/username_icon.png
--------------------------------------------------------------------------------
/resources/vim_demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/vim_demo.png
--------------------------------------------------------------------------------
/resources/workspace_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Floobits/floobits-atom/64c044009c1591d686958b5a6af1362528aa038e/resources/workspace_icon.png
--------------------------------------------------------------------------------
/spec/floobits-spec.coffee:
--------------------------------------------------------------------------------
1 | Floobits = require '../lib/floobits'
2 |
3 | # Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs.
4 | #
5 | # To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit`
6 | # or `fdescribe`). Remove the `f` to unfocus the block.
7 |
8 | describe "Floobits", ->
9 | activationPromise = null
10 |
11 | beforeEach ->
12 | atom.workspaceView = new WorkspaceView
13 | activationPromise = atom.packages.activatePackage('floobits')
14 |
15 | describe "when the floobits:toggle event is triggered", ->
16 | it "attaches and then detaches the view", ->
17 | expect(atom.workspaceView.find('.floobits')).not.toExist()
18 |
19 | # This is an activation event, triggering it will cause the package to be
20 | # activated.
21 | atom.workspaceView.trigger 'floobits:toggle'
22 |
23 | waitsForPromise ->
24 | activationPromise
25 |
26 | runs ->
27 | expect(atom.workspaceView.find('.floobits')).toExist()
28 | atom.workspaceView.trigger 'floobits:toggle'
29 | expect(atom.workspaceView.find('.floobits')).not.toExist()
30 |
--------------------------------------------------------------------------------
/spec/floobits-view-spec.coffee:
--------------------------------------------------------------------------------
1 | FloobitsView = require '../lib/floobits-view'
2 | {WorkspaceView} = require 'atom'
3 |
4 | describe "FloobitsView", ->
5 | it "has one valid test", ->
6 | expect("life").toBe "easy"
7 |
--------------------------------------------------------------------------------
/styles/colors.less:
--------------------------------------------------------------------------------
1 | @red: rgb(226, 82, 82);
2 | @red-text: rgb(224, 83, 83);
3 | @dark-red: rgb(210, 80, 83);
4 | @darker-red: rgb(175, 26, 31);
5 | @green: rgb(130, 198, 157);
6 | @teal-green: rgb(86, 172, 130);
7 | @maroon: rgb(117, 84, 84);
8 | @dark-blue: rgb(53, 58, 79);
9 | @darker-blue: rgb(223, 35, 28);
10 | @midnight-blue: rgb(34, 38, 54);
11 | @plan-blue: rgb(177, 223, 255);
12 | @light-blue: rgb(191, 198, 228);
13 | @baby-blue: rgb(178, 250, 255);
14 | @teal-blue: rgb(62, 198, 209);
15 | @red-link: rgb(221, 78, 78);
16 | @light-teal: rgb(61, 154, 162);
17 | @pale-teal: rgb(74, 166, 172);
18 | @lighter-teal: rgb(99, 194, 201);
19 | @dark-teal: rgb(53, 89, 105);
20 | @so-much-teal: rgb(49, 211, 240);
21 | @btn-active-teal: rgb(0, 181, 208);
22 | @tab-teal: rgb(103, 196, 203);
23 | @light-purple: rgb(114, 106, 189);
24 | @btn-active-purple: rgb(150, 142, 222);
25 | @dark-purple: rgb(98, 107, 143);
26 | @pink: rgb(251, 210, 255);
27 | @pastel-pink: rgb(254, 173, 255);
28 | @violet: rgb(184, 180, 219);
29 | @dark-violet: rgb(141, 137, 176);
30 | @light-violet: rgb(159, 154, 200);
31 | @fl-flat-purple: rgb(165, 155, 171);
32 | @fl-white: rgb(255, 255, 255);
33 | @fl-yellow: rgb(222, 212, 194);
34 | @fl-dark-yellow: rgb(255, 236, 45);
35 | @fl-pale-yellow: rgb(254, 255, 208);
36 | @fl-bg-yellow: rgb(255, 251, 222);
37 | @fl-light-yellow: rgb(254, 255, 232);
38 | @fl-tan: rgba(159, 173, 143, .75);
39 | @fl-black: rgb(0, 0, 0);
40 | @fl-bg-black: rgb(49, 46, 52);
41 | @fl-light-black: rgb(51, 51, 51);
42 | @fl-med-black: rgb(39, 36, 41);
43 | @fl-gray: rgb(190, 190, 190);
44 | @fl-light-gray: rgb(234, 235, 237);
45 | @fl-dark-gray: rgb(49, 46, 52);
46 | @fl-gray-text: rgb(102, 102, 102);
47 | @fl-green: rgb(115, 184, 147);
48 | @fl-shaded-green: rgb(88, 173, 129);
49 | @fl-dark-green: rgb(91, 174, 130);
50 | @fl-red: rgb(225, 83, 83);
51 | @fl-brown: rgb(153, 133, 133);
52 |
53 | @sublime-green: rgb(147, 211, 125);
54 | @vim-purple: @violet;
55 | @emacs-blue: @lighter-teal;
56 | @intellij-gold: rgb(213, 205, 125);
57 |
58 | @fl-soft-black-text: rgb(49, 46, 52);
59 | @fl-box-shadow: rgba(0, 0, 0, .31);
60 | @fl-input-border: rgba(0, 0, 0, .23);
61 | @faded-border: rgba(51, 51, 51, .5);
62 | @grad-dark-point: rgba(125, 109, 113, .24);
63 | @grad-light-point: rgba(255, 255, 255, .24);
64 | @text-shadow-color: rgba(186, 155, 125, .75);
65 | @text-shadow-color-dark: rgba(107, 89, 72, .75);
66 | @separator-color: rgb(214, 206, 206);
67 | @light-border: rgba(255, 255, 255, .15);
68 |
69 | @plan-green: rgb(147, 211, 125);
70 | @plan-orange: rgb(252, 186, 71);
71 | @plan-red: rgb(255, 125, 125);
72 | @plan-violet: rgb(202, 173, 255);
73 |
74 |
75 | @lightest_gray: #f5f5f5;
76 | @lighter_gray: #ededed;
77 | @light_gray: #dcdcdc;
78 | @mid_gray: #ccc;
79 | @darkMidGray: rgba(175, 175, 175, 1);
80 | @lighterGray: rgba(240, 240, 240, 1);
81 | @dark_gray: #555;
82 | @darker_gray: #333;
83 |
84 | @thumbnailGray: rgba(228, 228, 228, 0.8);
85 | @thumbnailGrayShadow: rgba(96, 96, 96, 0.5);
86 |
87 | @mid_blue: #6493be;
88 |
89 | @mid_orange: #f70;
90 |
91 | @floobits_color: #d00;
92 |
93 | @red: #f00;
94 |
95 | @dark: rgba(62, 62, 62, 1);
96 | @darkHover: rgba(40, 40, 40, 1);
97 | @darker: rgba(10, 10, 10, 1);
98 | @light: rgba(255, 255, 255, 1);
99 | @white: white;
100 | @black: black;
101 |
--------------------------------------------------------------------------------
/styles/edit_permissions_wizard.less:
--------------------------------------------------------------------------------
1 | @import "colors.less";
2 | @import "util.less";
3 |
4 | input.autocomplete {
5 | margin-bottom: 0;
6 | width: 20em;
7 | }
8 |
9 | ul.autocomplete_results {
10 | border: 1px solid @fl-gray;
11 | list-style: none;
12 | margin-top: -2px;
13 | margin-left: 0;
14 | padding-left: 0;
15 | li {
16 | cursor: pointer;
17 | margin: 2px 0 0 0;
18 | .syntax--ellipsis;
19 | padding: 2px 2px 2px 4px;
20 | }
21 | li:first-child{
22 | padding-top: 4px;
23 | }
24 | li:last-child{
25 | padding-bottom: 4px;
26 | }
27 | li:hover, li.selected {
28 | color: @fl-light-gray;
29 | background-color: @dark-blue;
30 | }
31 | }
32 |
33 | .workspace-wizard {
34 | text-align: center;
35 | font-size: 15px;
36 | a {
37 | text-decoration: underline;
38 | color: @floobits_color;
39 | }
40 | .wizard-section {
41 | padding-bottom: 20px;
42 | margin-bottom: 20px;
43 | border: 0px solid @lighter_gray;
44 | border-bottom-width: 1px;
45 | }
46 | .wizard-section-no-border {
47 | .wizard-section;
48 | border: 0;
49 | }
50 | table, label {
51 | font-size: 15px;
52 | }
53 | label {
54 | display: inline-block;
55 | margin-right: 10px;
56 | }
57 | }
58 | .wizard-button-bar {
59 | margin-top: 20px;
60 | text-align: right;
61 | }
62 |
63 | #edit-perms-content-wizard {
64 | .everyone-can {
65 | text-align: center;
66 | table {
67 | margin: 0 auto;
68 | }
69 | }
70 | td {
71 | padding: 5px;
72 | text-align: left;
73 | label {
74 | font-size: 12px;
75 | }
76 | }
77 | #all-perms-content {
78 | label {
79 | font-size: 12px;
80 | }
81 | }
82 | input[type=text] {
83 | border: 1px solid @darkMidGray;
84 | width: 200px;
85 | outline: none;
86 | box-shadow: none;
87 | &:focus {
88 | box-shadow: none;
89 | outline: none;
90 | }
91 | }
92 | .autocomplete-content {
93 | position: relative;
94 | .edit-perms-autocomplete-results {
95 | position: absolute;
96 | top: 0px;
97 | left: 0px;
98 | width: 200px;
99 | background-color: #FFF;
100 | border: 1px solid @darkMidGray;
101 | max-height: 200px;
102 | overflow-y: auto;
103 | overflow-x: hidden;
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/styles/floobits.atom-text-editor.less:
--------------------------------------------------------------------------------
1 | // The ui-variables file is provided by base themes provided by Atom.
2 | //
3 | // See https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less
4 | // for a full listing of what's available.
5 | @import "ui-variables";
6 | @import "./edit_permissions_wizard";
7 |
8 | @transition-time: 0.5s;
9 | @size: 2px;
10 |
11 | @-webkit-keyframes floobits-highlight-animation {
12 | 0% {opacity: .5;}
13 | 100% {opacity: 0;}
14 | }
15 |
16 | .make_highlight_dark(@hlcolor) {
17 | .floobits-highlight-dark_@{hlcolor} .region {
18 | background-color: @hlcolor;
19 | opacity: .3;
20 | }
21 | .floobits-border-dark_@{hlcolor} .region {
22 | background-color: @hlcolor;
23 | -webkit-animation: floobits-highlight-animation 2s ease 1;
24 | opacity: 0;
25 | }
26 | .floobits-highlight-dark_@{hlcolor}.line-number:before {
27 | border-right: @size solid @hlcolor;
28 | content: " ";
29 | }
30 | }
31 |
32 | .floobits-highlight-light(@hlcolor) {
33 | .floobits-highlight_@{hlcolor}.region {
34 | background-color: @hlcolor;
35 | opacity: .8;
36 | }
37 | .floobits-border-light_@{hlcolor} .region {
38 | background-color: @hlcolor;
39 | -webkit-animation: floobits-highlight-animation 2s ease 1;
40 | opacity: 0;
41 | }
42 | .floobits-highlight_@{hlcolor}.line-number:before {
43 | border-right: @size solid @hlcolor;
44 | content: " ";
45 | }
46 | }
47 | .floobits {
48 | a {
49 | //color: #428BCA;
50 | outline: none !important;
51 | }
52 | }
53 |
54 | .make_highlight_dark(e("black"));
55 | .make_highlight_dark(e("blue"));
56 | .make_highlight_dark(e("darkblue"));
57 | .make_highlight_dark(e("fuchsia"));
58 | .make_highlight_dark(e("gray"));
59 | .make_highlight_dark(e("green"));
60 | .make_highlight_dark(e("greenyellow"));
61 | .make_highlight_dark(e("indigo"));
62 | .make_highlight_dark(e("lime"));
63 | .make_highlight_dark(e("magenta"));
64 | .make_highlight_dark(e("maroon"));
65 | .make_highlight_dark(e("midnightblue"));
66 | .make_highlight_dark(e("orange"));
67 | .make_highlight_dark(e("orangered"));
68 | .make_highlight_dark(e("purple"));
69 | .make_highlight_dark(e("red"));
70 | .make_highlight_dark(e("teal"));
71 | .make_highlight_dark(e("yellow"));
72 |
73 | floobits-conflicts div {
74 | padding: 10px;
75 | // color: white;
76 | }
77 |
78 | input.floobits-submit {
79 | color: black;
80 | display: inline-block;
81 | min-width: 125px;
82 | content: 'Select Directory';
83 | display: inline-block;
84 | background: -webkit-linear-gradient(top, #f9f9f9, #e3e3e3);
85 | border: 1px solid #999;
86 | border-radius: 3px;
87 | padding: 5px 8px;
88 | outline: none;
89 | white-space: nowrap;
90 | -webkit-user-select: none;
91 | cursor: pointer;
92 | text-shadow: 1px 1px #fff;
93 | font-weight: 700;
94 | font-size: 10pt;
95 | float: right;
96 | }
97 |
--------------------------------------------------------------------------------
/styles/fonts.less:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'RobotoSlabRegular';
3 | src: url('atom://floobits/resources/fonts/robotoslab_regular_macroman/RobotoSlab-Regular-webfont.eot?iefix') format('eot'),
4 | url('atom://floobits/resources/fonts/robotoslab_regular_macroman/RobotoSlab-Regular-webfont.woff') format('woff'),
5 | url('atom://floobits/resources/fonts/robotoslab_regular_macroman/RobotoSlab-Regular-webfont.ttf') format('truetype'),
6 | url('atom://floobits/resources/fonts/robotoslab_regular_macroman/RobotoSlab-Regular-webfont.svg#webfont') format('svg');
7 | }
8 | @font-face {
9 | font-family: 'RobotoSlabBold';
10 | src: url('atom://floobits/resources/fonts/robotoslab_bold_macroman/RobotoSlab-Bold-webfont.eot?iefix') format('eot'),
11 | url('atom://floobits/resources/fonts/robotoslab_bold_macroman/RobotoSlab-Bold-webfont.woff') format('woff'),
12 | url('atom://floobits/resources/fonts/robotoslab_bold_macroman/RobotoSlab-Bold-webfont.ttf') format('truetype'),
13 | url('atom://floobits/resources/fonts/robotoslab_bold_macroman/RobotoSlab-Bold-webfont.svg#webfont') format('svg');
14 | }
15 | @font-face {
16 | font-family: 'RobotoSlabLight';
17 | src: url('atom://floobits/resources/fonts/robotoslab_light_macroman/RobotoSlab-Light-webfont.eot?iefix') format('eot'),
18 | url('atom://floobits/resources/fonts/robotoslab_light_macroman/RobotoSlab-Light-webfont.woff') format('woff'),
19 | url('atom://floobits/resources/fonts/robotoslab_light_macroman/RobotoSlab-Light-webfont.ttf') format('truetype'),
20 | url('atom://floobits/resources/fonts/robotoslab_light_macroman/RobotoSlab-Light-webfont.svg#webfont') format('svg');
21 | }
22 | @font-face {
23 | font-family: 'ProximaNovaExtraBold';
24 | src: url('atom://floobits/resources/fonts/proxima_nova/ProximaNova-Extrabold.otf') format('opentype');
25 | }
26 | @font-face {
27 | font-family: 'ProximaNova';
28 | src: url('atom://floobits/resources/fonts/proxima_nova/ProximaNova-Semibold.otf') format('opentype');
29 | }
30 | @font-face {
31 | font-family: 'ProximaNovaLight';
32 | src: url('atom://floobits/resources/fonts/proxima_nova/ProximaNova-Light_0.otf') format('opentype');
33 | }
34 |
--------------------------------------------------------------------------------
/styles/icons.less:
--------------------------------------------------------------------------------
1 | @import "octicon-mixins";
2 |
3 | .make_icon(@octiconicon, @size: 16px) {
4 | .octicon(@octiconicon, @size);
5 | margin: 0 auto;
6 | text-align: center;
7 | z-index: 100000;
8 | cursor: default;
9 | }
10 |
11 | .floobits-close-icon {
12 | .make_icon(x, 24px);
13 | position: absolute;
14 | top: 0px;
15 | right: 0px;
16 | cursor: pointer;
17 | }
18 |
19 | .floobits-close-icon-small {
20 | .make_icon(x, 16px);
21 | position: absolute;
22 | top: 4px;
23 | right: 2px;
24 | cursor: pointer;
25 | }
26 |
27 | .icon-floobits-conflicts {
28 | padding-left: 22px;
29 | background: transparent url("atom://floobits/resources/icon_64x64.png") no-repeat;
30 | background-size: 20px;
31 | background-position-y: center;
32 | }
33 |
34 | .floobits-icon-remove {
35 | .make_icon(x);
36 | }
37 |
38 | .floobits-arrow-up-icon {
39 | .make_icon(three-bars);
40 | }
41 |
42 | .floobits-arrow-down-icon {
43 | .make_icon(three-bars);
44 | }
45 |
46 | .floobits-eject-icon {
47 | .make_icon(jump-down);
48 | }
49 |
50 | .floobits-video-icon {
51 | .make_icon(device-camera-video);
52 | }
53 |
54 | .floobits-follow-icon {
55 | .make_icon(radio-tower);
56 | }
57 |
58 | .floobits-info-icon {
59 | .make_icon(info);
60 | }
61 |
62 | .floobits-permissions-icon {
63 | .make_icon(gear);
64 | }
--------------------------------------------------------------------------------
/styles/join.less:
--------------------------------------------------------------------------------
1 | floobits-join-workspace {
2 | h2 {
3 | text-align: center;
4 | width: 100%;
5 | //color: white;
6 | font-size: 1.5em;
7 | padding-bottom: 10px;
8 | }
9 |
10 | .well {
11 | background-color: rgba(170, 170, 170, 0.15);
12 | padding: 15px;
13 | overflow: auto;
14 | }
15 |
16 | .input-group {
17 | margin-bottom: 15px;
18 | }
19 |
20 | input.floobits-file {
21 | //color: white;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/styles/messages.less:
--------------------------------------------------------------------------------
1 | @import "colors.less";
2 | @import "util.less";
3 |
4 | @time-width: 60px;
5 |
6 |
7 | .floobits-messages-container {
8 | .chat-input-container {
9 | form {
10 | input.chat-input {
11 | width: 100%;
12 | border: none;
13 | color: black;
14 | padding: 5px;
15 | }
16 | }
17 | }
18 | .messages-list {
19 | overflow-x: hidden;
20 | overflow-y: auto;
21 | .message {
22 | font-size: 13px;
23 | border: 0px solid;
24 | &.alert {
25 | .nomar_nopad;
26 | background-color: inherit;
27 | }
28 | .message-content {
29 | font-size: 13px;
30 | padding: 8px 0;
31 | margin: 0px;
32 | text-align: left;
33 | a {
34 | text-decoration: underline;
35 | }
36 | img {
37 | max-width: 100%;
38 | max-height: 400px;
39 | display: block;
40 | }
41 | .floobits-square {
42 | display: inline-block;
43 | height: 13px;
44 | width: 13px;
45 | margin: 0 5px 1px 0px;
46 | vertical-align: middle;
47 | }
48 | .messages-image-container {
49 | position: relative;
50 | .messages-remove-image {
51 | position: absolute;
52 | top: 0px;
53 | left: 0px;
54 | //color: @black;
55 | opacity: 0.45;
56 | }
57 | img {
58 | position: relative;
59 | left: 20px;
60 | }
61 | }
62 | }
63 | .message-log-repeat {
64 | font-size: 9px;
65 | font-weight: bold;
66 | }
67 | .message-text {
68 | margin-left: @time-width;
69 | line-height: 18px;
70 | }
71 | .message-username {
72 | font-weight: bold;
73 | }
74 | .message-timestamp {
75 | width: @time-width;
76 | margin-left: 2px;
77 | float: left;
78 | line-height: 18px;
79 | font-size: 11px;
80 | color: #919191;
81 | }
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/styles/modal.less:
--------------------------------------------------------------------------------
1 | .btn-group {
2 | button {
3 | height: 35px;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/styles/permissions.less:
--------------------------------------------------------------------------------
1 | #floobits-permissions {
2 | label {
3 | padding-right: 10px;
4 | }
5 | .field_wrapper {
6 | padding-bottom: 20px;
7 | }
8 | h2 {
9 | text-align: center;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/styles/signin.less:
--------------------------------------------------------------------------------
1 | @import "./util.less";
2 |
3 | .fl-signin-form-container {
4 | font-family: "ProximaNova", sans-serif;
5 | a {
6 | color: @fl-green;
7 | outline: none !important;
8 | &:hover {
9 | color: @red-text;
10 | }
11 | button.btn {
12 | color: @fl-white;
13 | }
14 | text-decoration: none;
15 | }
16 | text-align: center;
17 | .signin-title {
18 | color: @fl-green;
19 | font-size: 30px;
20 | font-family: "RobotoSlabRegular", serif;
21 | text-align: center;
22 | margin: 25px 0 5px;
23 | }
24 | form {
25 | width: 325px;
26 | div.signup-input-container {
27 | text-align: left;
28 | display: inline-block;
29 | width: 275px;
30 | height: 42px;
31 | &:nth-child(2) {
32 | margin: 15px 0 0;
33 | }
34 | padding-top: 0px !important;
35 | padding: 0px;
36 | }
37 | div.signin-other-container {
38 | .signin-btn {
39 | margin: 10px 0;
40 | display: inline-block;
41 | height: 45px;
42 | width: 275px;
43 | border: 1px solid @dark-red;
44 | border-bottom-color: @darker-red;
45 | .border-image(@dark-red, @darker-red);
46 | &:hover {
47 | border-color: @dark-teal;
48 | border-image: none;
49 | }
50 | &:focus {
51 | border-color: @dark-teal;
52 | border-image: none;
53 | }
54 | }
55 | }
56 | .signin-github {
57 | margin: 15px 0 70px;
58 | }
59 | }
60 | .signin-forgot-password {
61 | position: absolute;
62 | left: 0px;
63 | bottom: 0px;
64 | right: 0px;
65 | background-color: @fl-light-gray;
66 | text-align: center;
67 | padding: 15px 0;
68 | a {
69 | color: @fl-light-black;
70 | font-size: 12px;
71 | }
72 | }
73 | .modal-content {
74 | border-radius: 0;
75 | width: 325px;
76 | .signin-close {
77 | color: @fl-gray;
78 | position: absolute;
79 | top: 10px;
80 | right: 10px;
81 | cursor: pointer;
82 | }
83 | }
84 | }
85 | div.signup-input-container, div.signup-github {
86 | background-color: @fl-white;
87 | padding: 5px 20px;
88 | span.signup-icon {
89 | display: inline-block;
90 | background-image: -webkit-linear-gradient(top, rgba(255, 255, 255, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
91 | background-image: -o-linear-gradient(top, rgba(255, 255, 255, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
92 | background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
93 | background-repeat: repeat-x;
94 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#1affffff', endColorstr='#1a000000', GradientType=0);
95 | width: 40px;
96 | height: 40px;
97 | vertical-align: top;
98 | border: 0px solid @fl-gray;
99 | border-right-width: 1px;
100 | position: relative;
101 | &::after {
102 | opacity: 0.5;
103 | content: " ";
104 | position: absolute;
105 | top: 0px;
106 | left: 0px;
107 | right: 0px;
108 | bottom: 0px;
109 | }
110 | &.signup-username-icon::after {
111 | background: transparent url("atom://floobits/resources/username_icon.png") no-repeat 11px 10px;
112 | }
113 | &.signup-email-icon::after {
114 | background: transparent url("atom://floobits/resources/email_icon.png") no-repeat 10px 11px;
115 | }
116 | &.signup-password-icon::after {
117 | background: transparent url("atom://floobits/resources/password_icon.png") no-repeat 10px 11px;
118 | }
119 | &.signup-password-conf-icon::after {
120 | background: transparent url("atom://floobits/resources/password_conf_icon.png") no-repeat 10px 9px;
121 | }
122 |
123 | }
124 | &.active-signup-icon {
125 | div {
126 | border-color: @fl-dark-gray;
127 | span.signup-icon {
128 | border-color: @fl-dark-gray;
129 | &::after {
130 | opacity: 1 !important;
131 | }
132 | }
133 | }
134 | }
135 | input {
136 | display: inline-block;
137 | height: 40px;
138 | width: 80%;
139 | border: 1px;
140 | line-height: 150%;
141 | font-size: 12px;
142 | padding: 10px 5px;
143 | }
144 | @inputBorderPadding: 30px;
145 | &:nth-child(2) {
146 | padding-top: @inputBorderPadding;
147 | }
148 | &:nth-child(5) {
149 | padding-bottom: @inputBorderPadding;
150 | }
151 | div {
152 | border: 1px solid @fl-input-border;
153 | white-space: nowrap;
154 | overflow: hidden;
155 | }
156 | div > input:focus {
157 | border-color: @fl-light-black;
158 | }
159 | }
160 | .red-btn {
161 | color: @fl-white;
162 | border-radius: 1px;
163 | /*#gradient > .vertical(@end-color: rgb(211, 65, 65); @start-color: rgb(231, 92, 92));*/
164 | &:hover {
165 | color: @fl-white;
166 | border-color: rgb(44, 167, 177);
167 | /*#gradient > .vertical(@end-color: rgb(44, 167, 177); @start-color: rgb(53, 188, 198));*/
168 | }
169 | &:focus {
170 | color: @fl-white;
171 | border-color: rgb(44, 167, 177);
172 | /*#gradient > .vertical(@end-color: rgb(44, 167, 177); @start-color: rgb(53, 188, 198));*/
173 | }
174 | }
175 | .signup-btn {
176 | font-size: 18px;
177 | display: block;
178 | width: 100%;
179 | border: 0px solid rgb(231, 92, 92);
180 | border-top-width: 1px;
181 | height: 65px;
182 | .red-btn;
183 | background-image: linear-gradient(to bottom, #e75c5c 0%, #d34141 100%);
184 | background-repeat: repeat-x;
185 |
186 | &:hover {
187 | color: #ffffff;
188 | border-color: #2ca7b1;
189 | background-image: -webkit-linear-gradient(top, #35bcc6 0%, #2ca7b1 100%);
190 | background-image: -o-linear-gradient(top, #35bcc6 0%, #2ca7b1 100%);
191 | background-image: linear-gradient(to bottom, #35bcc6 0%, #2ca7b1 100%);
192 | background-repeat: repeat-x;
193 | }
194 | }
195 | a.signup-btn {
196 | display: inline-block;
197 | vertical-align: middle;
198 | padding-top: 17px;
199 | &:hover {
200 | color: @fl-white;
201 | text-decoration: none;
202 | }
203 | width: 250px;
204 | margin: 70px 0 50px 0;
205 | font-size: 20px;
206 | }
207 |
--------------------------------------------------------------------------------
/styles/status_bar.less:
--------------------------------------------------------------------------------
1 | @import "ui-variables";
2 |
3 | floobits-status-bar {
4 | .floobits-status-bar {
5 | font-size: 11px;
6 | // This fixes styling with many popular themes.
7 | padding: 2px @component-line-height/2;
8 | background: inherit;
9 | }
10 | // This fixes styling with many popular themes.
11 | padding-left: 0 !important;
12 | display: inline-block;
13 | width: 100%;
14 | }
15 |
--------------------------------------------------------------------------------
/styles/terminal.less:
--------------------------------------------------------------------------------
1 | @import "./util.less";
2 |
3 | .floobits-terminal {
4 | padding: 8px;
5 | .syntax--monospace;
6 | div {
7 | white-space: nowrap;
8 | }
9 | }
10 |
11 | floobits-terminal-list-view {
12 | display: flex;
13 | margin-top: 10px;
14 | .header {
15 | img {
16 | width: 22px;
17 | padding-right: 5px;
18 | }
19 | margin-left:20px;
20 | }
21 | ul {
22 | margin-left: -10px;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/styles/user_list_pane.less:
--------------------------------------------------------------------------------
1 | #user-list-pane {
2 | height: 100%;
3 | overflow-x: hidden;
4 | overflow-y: auto;
5 | ::-webkit-scrollbar {
6 | display: none;
7 | }
8 | background-color: black;
9 | #user-list-pane-header {
10 | width: 100%;
11 | background-color: black;
12 | border: 1px solid black;
13 | height: 30px;
14 | img {
15 | width: 25px;
16 | height: 25px;
17 | margin: 3px;
18 | }
19 | span {
20 | margin: 0 5px 0 0;
21 | }
22 | }
23 | #user-list-close {
24 | cursor: pointer;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/styles/userlist.less:
--------------------------------------------------------------------------------
1 | @import "./util.less";
2 | @import "./icons.less";
3 |
4 | @userBarHeight: 32px;
5 | @iconFontSize: 20px;
6 | @iconPadding: 3px;
7 |
8 | .userlist-icon {
9 | border: 1px solid;
10 | opacity: 0.45;
11 | margin-right: 5px;
12 | }
13 |
14 | @userBarHeight: 32px;
15 | @iconFontSize: 20px;
16 |
17 | .user-thumbnail-background {
18 | background-color: rgba(0, 0, 0, 0.36);
19 | }
20 |
21 | .user-thumbnail-text {
22 | color: @thumbnailGray;
23 | }
24 |
25 | .user-list {
26 | width: @userThumbWidth;
27 | overflow-x: hidden;
28 | .user {
29 | height: @userThumbHeight;
30 | width: @userThumbWidth;
31 | vertical-align: top;
32 | display: inline-block;
33 | position: relative;
34 | overflow: hidden;
35 | i.user-indicator {
36 | position: absolute;
37 | cursor: pointer;
38 | font-size: @iconFontSize;
39 | color: @white;
40 | opacity: 0.5;
41 | &.enabled, &:hover {
42 | opacity: 1;
43 | }
44 | border: 1px solid @thumbnailGrayShadow;
45 | background-color: @thumbnailGrayShadow;
46 | }
47 | .visualizer-container {
48 | position: absolute;
49 | top: -2px;
50 | left: -2px;
51 | height: @userBarHeight;
52 | width: @userBarHeight;
53 | display: flex;
54 | align-items: center;
55 | justify-content: center;
56 | .visualizer {
57 | background-color: @white;
58 | border-radius: 50%;
59 | opacity: 0.45;
60 | z-index: @zPlane1;
61 | }
62 | }
63 | i.user-stop {
64 | top: 3px;
65 | right: 3px;
66 | z-index: @zPlane1;
67 | .floobits-icon-remove;
68 | }
69 | i.in-video {
70 | top: auto;
71 | bottom: @userBarHeight + 3;
72 | left: 3px;
73 | .floobits-video-icon;
74 | }
75 | i.user-following {
76 | top: auto;
77 | bottom: @userBarHeight + 3;
78 | right: 3px;
79 | .floobits-follow-icon;
80 | }
81 | .user-thumb {
82 | cursor: pointer;
83 | background-color: black;
84 | height: @userThumbHeight;
85 | width: @userThumbWidth;
86 | &.user-my-conn {
87 | transform: scaleX(-1);
88 | }
89 | &.audio-only {
90 | transform: scaleX(1);
91 | }
92 | }
93 | .user-face {
94 | .click-to-video {
95 | .noselect;
96 | .pos(0, 0, @userBarHeight, 0);
97 | .user-thumbnail-background;
98 | .user-thumbnail-text;
99 | display: none;
100 | font-size: @iconFontSize;
101 | padding-top: (@userThumbHeight - @userBarHeight) / 2;
102 | text-align: center;
103 | cursor: pointer;
104 | // In Chrome, transform properties mess with z-index. This is a workaround.
105 | z-index: @zPlane1;
106 | .glyphicon {
107 | top: @iconPadding;
108 | }
109 | }
110 | &:hover .click-to-video {
111 | display: block;
112 | }
113 | }
114 | &.opened {
115 | .user-thumb {
116 | .blur(5px);
117 | }
118 | .user-info {
119 | top: 0;
120 | opacity: 1;
121 | }
122 | .user-bar i.user-arrow {
123 | .floobits-arrow-down-icon;
124 | }
125 | }
126 | .user-bar {
127 | cursor: pointer;
128 | padding: 4px 5px;
129 | .user-thumbnail-text;
130 | .user-thumbnail-background;
131 | .pos_bottom;
132 | .noselect;
133 | font-size: 18px;
134 | height: @userBarHeight;
135 | i.user-arrow {
136 | position: absolute;
137 | top: 7px;
138 | right: 7px;
139 | .floobits-arrow-up-icon;
140 | }
141 | }
142 | .user-info {
143 | opacity: 0;
144 | hr {
145 | margin: 5px 0;
146 | border-color: #333;
147 | }
148 | .stack-up {
149 | padding: 3px 5px;
150 | .stack-up-content {
151 | padding: 0 3px;
152 | a {
153 | color: #73B893;
154 | }
155 | }
156 | }
157 | .user-conn {
158 | clear: both;
159 | }
160 | .user-thumbnail-text;
161 | .user-thumbnail-background;
162 | position: absolute;
163 | left: 0;
164 | right: 0;
165 | top: @userThumbHeight - @userBarHeight;
166 | bottom: @userBarHeight;
167 | transition: top 0.25s, opacity 0s;
168 | -webkit-transition: top 0.25s, opacity 0s;
169 | height: @userThumbHeight - @userBarHeight;
170 | text-align: left;
171 | overflow-x: hidden;
172 | overflow-y: auto;
173 | width: 100%;
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/styles/util.less:
--------------------------------------------------------------------------------
1 | //@import "bootstrap_v3.2.0/variables.less";
2 | //@import "bootstrap_v3.2.0/mixins/alerts.less";
3 | //@import "bootstrap_v3.2.0/alerts.less";
4 | @import "./colors.less";
5 |
6 | //.alert-error {
7 | // .alert-danger;
8 | //}
9 |
10 | .fl-progress {
11 | background-color: inherit;
12 | border: 1px solid;
13 | box-shadow: inset 0 1px 2px rgba(0,0,0,.1);
14 | color: #fff;
15 | height: 20px;
16 | margin: 20px 0;
17 | overflow: hidden;
18 | padding: 0;
19 | text-align: center;
20 | position: relative;
21 | .fl-progress-bar {
22 | background-color: #337ab7;
23 | box-shadow: inset 0 -1px 0 rgba(0,0,0,.15);
24 | color: #fff;
25 | float: left;
26 | font-size: 12px;
27 | height: 100%;
28 | line-height: 20px;
29 | padding: 0;
30 | text-align: center;
31 | transition: width .6s ease;
32 | width: 0%;
33 | }
34 | .fl-progress-text {
35 | position: absolute;
36 | display: block;
37 | width: 100%;
38 | }
39 | }
40 |
41 | .nomar_nopad() {
42 | margin: 0;
43 | padding: 0;
44 | }
45 |
46 | .pos(@top: 0, @right: 0, @bottom: 0, @left: 0) {
47 | position: absolute;
48 | top: @top;
49 | right: @right;
50 | bottom: @bottom;
51 | left: @left;
52 | }
53 |
54 | .pos_bottom {
55 | position: absolute;
56 | bottom: 0;
57 | left: 0;
58 | right: 0;
59 | }
60 |
61 | .sans {
62 | font-family: "HelveticaNeue", Helvetica, Verdana, Arial, sans-serif;
63 | }
64 |
65 | .header_font() {
66 | .sans;
67 | font-weight: 500;
68 | .a_hover;
69 | }
70 |
71 | .syntax--monospace {
72 | font-family: Menlo, "Deja Vu Sans Mono", Consolas, monospace;
73 | }
74 |
75 | #toc {
76 | background-color: @lightest_gray;
77 | padding: 0.5em 1em;
78 | width: 12em;
79 | }
80 |
81 | .a_nocolor {
82 | a {
83 | color: inherit;
84 | }
85 | }
86 |
87 | .a_stealth {
88 | .a_nocolor;
89 | a, a:hover {
90 | text-decoration: none;
91 | }
92 | }
93 |
94 | .a_hover {
95 | a {
96 | text-decoration: none;
97 | }
98 | a:hover {
99 | text-decoration: underline;
100 | }
101 | }
102 |
103 | .syntax--ellipsis {
104 | overflow: hidden;
105 | text-overflow: ellipsis;
106 | white-space: nowrap;
107 | }
108 |
109 | .noselect {
110 | -webkit-touch-callout: none;
111 | -webkit-user-select: none;
112 | -khtml-user-select: none;
113 | -moz-user-select: none;
114 | -ms-user-select: none;
115 | user-select: none;
116 | }
117 |
118 | .blur(@radius) {
119 | filter: blur(@radius);
120 | -webkit-filter: blur(@radius);
121 | }
122 |
123 | hr.syntax--separator {
124 | padding-bottom: 2em;
125 | }
126 | div.syntax--separator{
127 | padding-bottom: 4em;
128 | }
129 |
130 | @zPlane1: 5;
131 | @zPlane2: 90;
132 | @zPlane3: 100;
133 | @zPlane4: 200;
134 | @zPlane5: 300;
135 | @zPlaneTop: 1000;
136 |
137 | @userThumbHeight: 228px;
138 | @userThumbWidth: 228px;
139 | @panelHandleWidth: 7px;
140 | @tabs_height: 30px;
141 |
142 | .dropdown {
143 | &:hover > ul.dropdown-menu {
144 | display: block;
145 | }
146 | ul.dropdown-menu {
147 | margin-top: -1px;
148 | }
149 | }
150 |
151 | .user-color-square {
152 | display: inline-block;
153 | height: 12px;
154 | width: 12px;
155 | margin: 0 5px 1px 2px;
156 | vertical-align: middle;
157 | border: 1px solid @thumbnailGrayShadow;
158 | opacity: 0.6;
159 | }
160 |
161 | .border-image (@color1, @color2) {
162 | border-image: -webkit-linear-gradient(-90deg, @color1, @color2) 20 stretch;
163 | border-image: -moz-linear-gradient(-90deg, @color1, @color2) 20 stretch;
164 | border-image: -ms-linear-gradient(-90deg, @color1, @color2) 20 stretch;
165 | border-image: -linear-gradient(-90deg, @color1, @color2) 20 stretch;
166 | }
167 |
--------------------------------------------------------------------------------
/templates/chat.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | "use strict";
3 |
4 | const React = require('react-atom-fork');
5 | const utils = require("../common/utils");
6 | const floop = require("../common/floop");
7 | const flux = require("flukes");
8 | const message_action = require("../common/message_action");
9 |
10 | module.exports = React.createClass({
11 | mixins: [flux.createAutoBinder(['msgs'])],
12 | handleMessage_: function (event) {
13 | event.preventDefault();
14 | const input = this.refs.newMessage.getDOMNode();
15 | let txt = input.value;
16 | input.value = "";
17 | const ret = floop.send_msg({data: txt});
18 | if (ret) {
19 | const error = ret.message || ret.toString();
20 | console.error(error);
21 | txt = error;
22 | }
23 | message_action.user(this.props.username, txt, Date.now());
24 | },
25 | componentDidMount: function () {
26 | this.focus();
27 | },
28 | focus: function () {
29 | this.refs.newMessage.getDOMNode().focus();
30 | },
31 | render: function () {
32 | const msgs = this.props.msgs.map(function (msg) {
33 | const userColor = utils.user_color(msg.username);
34 | return (
35 |
36 |
37 |
{msg.prettyTime}
38 |
39 |
40 |
41 | {msg.username}:
42 |
43 | {msg.data}
44 |
45 |
46 |
47 | );
48 | });
49 | return (
50 |
54 |
55 |
58 |
59 |
60 | {msgs}
61 |
62 |
63 | );
64 | }
65 | });
66 |
--------------------------------------------------------------------------------
/templates/code_review.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 |
3 | "use strict";
4 |
5 | const React = require('react-atom-fork');
6 | const mixins = require("./mixins");
7 | const atomUtils = require("../atom_utils");
8 |
9 | const CodeReview = React.createClass({
10 | mixins: [mixins.ReactUnwrapper, mixins.FormMixin],
11 | onSubmit: function (state) {
12 | const cb = this.props.cb.bind({}, null, state, this.refs.description.getDOMNode().value);
13 | setTimeout(cb, 0);
14 | this.destroy();
15 | },
16 | componentDidMount: function () {
17 | this.refs.description.getDOMNode().focus();
18 | },
19 | render: function () {
20 | return (
21 |
42 | );
43 | }
44 | });
45 |
46 | module.exports = function (cb) {
47 | const view = CodeReview({cb: cb});
48 | atomUtils.addModalPanel('code-review', view);
49 | };
--------------------------------------------------------------------------------
/templates/conflicts.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | /*global fl */
3 | "use strict";
4 | const fs = require("fs");
5 | const path = require("path");
6 |
7 | const _ = require("lodash");
8 | const $ = require('atom-space-pen-views').$;
9 | const React = require('react-atom-fork');
10 |
11 | const floop = require("../common/floop");
12 | const utils = require("../common/utils");
13 |
14 | module.exports = React.createClass({
15 | treeize_: function (obj) {
16 | let node = {};
17 | let tree = {};
18 | _.each(obj, function (p) {
19 | node = tree;
20 | p.split(path.sep).forEach(function (p) {
21 | if (p in node) {
22 | node = node[p];
23 | return;
24 | }
25 | node[p] = {};
26 | node = node[p];
27 | });
28 | });
29 | return tree;
30 | },
31 | getInitialState: function () {
32 | return {
33 | enabled: true,
34 | clicked: "",
35 | totalFiles: _.size(this.props.different) + _.size(this.props.newFiles) + _.size(this.props.missing),
36 | missing: new Set(),
37 | different: new Set(),
38 | newFiles: new Set(),
39 | };
40 | },
41 | componentDidMount: function () {
42 | if (this.props.justUpload) {
43 | const upload = this.remote_;
44 | setTimeout(function () {
45 | upload();
46 | }, 0);
47 | }
48 |
49 | const local = this.refs.local;
50 | if (!local) {
51 | return;
52 | }
53 | $(local.getDOMNode()).focus();
54 | },
55 | onClick: function (id) {
56 | console.log(id);
57 | },
58 | remote_: function () {
59 | this.setState({enabled: false});
60 | _.each(this.props.different, (b, id) => {
61 | let encoding = b.encoding || "utf8";
62 | floop.send_set_buf({
63 | id, encoding,
64 | buf: b.txt.toString(encoding),
65 | md5: b.md5,
66 | }, null, (err) => {
67 | if (!err) {
68 | this.setState({different: this.state.different.add(id)});
69 | floop.send_saved({id: id});
70 | }
71 | });
72 | });
73 |
74 | _.each(this.props.missing, (b, id) => {
75 | floop.send_delete_buf({id}, null, () => {
76 | // TODO: check err
77 | this.setState({missing: this.state.missing.add(id)});
78 | });
79 | });
80 |
81 | _.each(this.props.newFiles, (b, rel) => {
82 | fs.readFile(b.path, (err, data) => {
83 | if (err) {
84 | console.log(err);
85 | return;
86 | }
87 |
88 | const encoding = utils.is_binary(data, data.length) ? "base64" : "utf8";
89 | floop.send_create_buf({
90 | path: rel,
91 | buf: data.toString(encoding),
92 | encoding: encoding,
93 | md5: utils.md5(data),
94 | }, null, () => {
95 | this.setState({newFiles: this.state.newFiles.add(rel)});
96 | });
97 | });
98 | });
99 | this.props.onHandledConflicts({});
100 | },
101 | local_: function () {
102 | this.setState({
103 | enabled: false,
104 | newFiles: new Set(_.keys(this.props.newFiles)),
105 | });
106 | _.each(this.props.missing, (b, id) => {
107 | floop.send_get_buf(id, () => this.setState({missing: this.state.missing.add(id)}));
108 | });
109 | _.each(this.props.different, (b, id) => {
110 | floop.send_get_buf(id, () => this.setState({different: this.state.different.add(id)}));
111 | });
112 | const toFetch = _.merge({}, this.props.missing, this.props.different);
113 | this.props.onHandledConflicts(toFetch);
114 | },
115 | cancel_: function () {
116 | this.setState({enabled: false});
117 | require("../floobits").leave_workspace();
118 | },
119 | render_: function (title, name) {
120 | const items = this.props[name];
121 | const completed = this.state[name];
122 | if (!_.size(items)) {
123 | return "";
124 | }
125 | return (
126 |
127 |
{title}
128 |
129 | {
130 | _.map(items, (b, id) => {
131 | const path = b.path;
132 | const checked = completed.has(id) ? "✓" : "";
133 | return (- {path} {checked}
);
134 | })
135 | }
136 |
137 |
138 | );
139 | },
140 | render_progress: function () {
141 | if (this.state.enabled) {
142 | return false;
143 | }
144 | const state = this.state;
145 | const width = parseInt((state.different.size + state.newFiles.size + state.missing.size) / state.totalFiles * 100, 10);
146 | const progressWidth = `${width}%`;
147 | return (
148 |
149 |
150 |
{progressWidth}
151 |
152 |
153 |
154 | All done syncing files!
155 |
156 |
157 | );
158 | },
159 | render_created_workspace: function () {
160 | const newFiles = this.render_("Uploading:", "newFiles");
161 | const progress = this.render_progress();
162 | return (
163 |
Created {fl.floourl ? fl.floourl.toString() : "the workspace"}
164 | { progress }
165 | { newFiles }
166 | );
167 | },
168 | render_conflicts: function () {
169 | const missing = this.render_("Missing", "missing");
170 | const different = this.render_("Different", "different");
171 | const newFiles = this.render_("New", "newFiles");
172 | const ignored = _.map(this.props.ignored, function (p) {
173 | return {p};
174 | });
175 |
176 | const tooBig = _.map(this.props.tooBig, function (size, p) {
177 | return {p} {utils.formatBytes(size)};
178 | });
179 |
180 | const state = this.state;
181 | const progress = this.render_progress();
182 |
183 | return (
184 |
Your local files are different from the workspace.
185 |
186 |
187 |
188 |
189 | {progress}
190 |
191 | {missing}
192 | {different}
193 | {newFiles}
194 | {!this.props.ignored.length ? "" :
195 |
196 |
Ignored
197 |
198 | { ignored }
199 |
200 |
201 | }
202 | {!tooBig.length ? "" :
203 |
204 |
Too Big
205 |
206 | { tooBig }
207 |
208 |
209 | }
210 |
);
211 | },
212 | render: function () {
213 | const body = this.props.justUpload ? this.render_created_workspace() : this.render_conflicts();
214 | return (
215 |
216 | {body}
217 |
218 | );
219 | }
220 | });
221 |
--------------------------------------------------------------------------------
/templates/handle_request_perm.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 |
3 | "use strict";
4 |
5 | const React = require('react-atom-fork');
6 | const floop = require("../common/floop");
7 | const permsEvent = {};
8 |
9 | const HandleRequestPermView = React.createClass({
10 | destroy: function () {
11 | this.getDOMNode().parentNode.destroy();
12 | },
13 | grant: function () {
14 | permsEvent.action = "add";
15 | this.send();
16 | },
17 | deny: function () {
18 | permsEvent.action = "reject";
19 | this.send();
20 | },
21 | send: function () {
22 | floop.send_perms(permsEvent);
23 | this.destroy();
24 | },
25 | render: function () {
26 | permsEvent.user_id = this.props.userId;
27 | permsEvent.perms = this.props.perms;
28 | return (
29 |
30 |
31 |
{this.props.username} wants to edit this workspace
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | }
45 | });
46 |
47 | module.exports = HandleRequestPermView;
48 |
--------------------------------------------------------------------------------
/templates/join.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 |
3 | "use strict";
4 |
5 | const $ = require('atom-space-pen-views').$;
6 | const React = require('react-atom-fork');
7 | const utils = require("../common/utils");
8 | const mixins = require("./mixins");
9 |
10 | const JoinWorkspace = React.createClass({
11 | mixins: [mixins.ReactUnwrapper, mixins.FormMixin],
12 | getInitialState: function () {
13 | return {
14 | path: this.props.path,
15 | url: this.props.url,
16 | };
17 | },
18 | onSubmit: function (event) {
19 | if (event) {
20 | event.preventDefault();
21 | }
22 | setTimeout(function () {
23 | this.props.on_url(this.state.path, this.refs.url.getDOMNode().value);
24 | }.bind(this), 0);
25 | this.destroy();
26 | },
27 | onChange_: function (event) {
28 | const files = event.target.files;
29 | if (!files.length) {
30 | return;
31 | }
32 | const path = files[0].path;
33 | const state = {path: path};
34 | if (this.refs.url.getDOMNode().value === this.props.url) {
35 | const dotFloo = utils.load_floo(path);
36 | if (dotFloo.url && dotFloo.url.length) {
37 | state.url = dotFloo.url;
38 | }
39 | }
40 | this.setState(state);
41 | },
42 | onDidStuff: function () {
43 | if (this.state.path) {
44 | return;
45 | }
46 | $("#ultra-secret-hidden-file-input").attr("webkitdirectory", true);
47 | },
48 | componentDidUpdate: function () {
49 | this.onDidStuff();
50 | },
51 | componentDidMount: function () {
52 | this.onDidStuff();
53 |
54 | const that = this;
55 | setTimeout(function () {
56 | const url = that.refs.url.getDOMNode();
57 | const length = url.value.length;
58 | url.setSelectionRange(length, length);
59 | url.focus();
60 | }, 0);
61 |
62 | },
63 | onTyping: function (event) {
64 | const path = event.target.value;
65 | if (path === this.state.path) {
66 | return;
67 | }
68 | this.setState({path: path});
69 | },
70 | focusFileInput: function () {
71 | if (this.state.path) {
72 | return;
73 | }
74 | $("#ultra-secret-hidden-file-input").trigger('click');
75 | this.setState({showMessage: true});
76 | },
77 | updateURL: function (e) {
78 | this.setState({url: e.target.value});
79 | },
80 | render: function () {
81 | return (
82 |
83 |
Join Workspace
84 |
85 |
124 |
125 |
126 | );
127 | }
128 | });
129 |
130 | module.exports = JoinWorkspace;
131 |
--------------------------------------------------------------------------------
/templates/messages_view.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | "use strict";
3 |
4 | const flux = require("flukes");
5 | const React = require('react-atom-fork');
6 | const _ = require("lodash");
7 | const utils = require("../common/utils");
8 | const floop = require("../common/floop");
9 | const messageAction = require("../common/message_action");
10 |
11 |
12 | const LogMessageView = React.createClass({
13 | render: function () {
14 | const message = this.props.message;
15 | let repeatCountHTML = "";
16 | if (this.props.repeatCount > 0) {
17 | repeatCountHTML = (
18 | x{this.props.repeatCount + 1}
19 | );
20 | }
21 | return (
22 |
23 |
24 |
{message.prettyTime}
25 |

{message.msg} {repeatCountHTML}
26 |
27 |
28 | );
29 | }
30 | });
31 |
32 | const UserMessageView = React.createClass({
33 | getInitialState: function () {
34 | return {
35 | ignoredURLs: [],
36 | };
37 | },
38 | ignoreURL: function (url, event) {
39 | if (event) {
40 | event.stopPropagation();
41 | event.preventDefault();
42 | }
43 | const ignoredURLs = this.state.ignoredURLs;
44 | ignoredURLs.push(url);
45 | this.setState({ignoredURLs: ignoredURLs});
46 | },
47 | render: function () {
48 | var message = this.props.message,
49 | urlRegexp = /https?:\/\/\S+/g,
50 | userColor = utils.user_color(message.username),
51 | result,
52 | msgTxt = [],
53 | before,
54 | after,
55 | key = 0,
56 | prevIndex = 0;
57 |
58 | while (true) {
59 | result = urlRegexp.exec(message.msg);
60 | if (!result) {
61 | msgTxt.push(message.msg.slice(prevIndex));
62 | break;
63 | }
64 | before = message.msg.slice(prevIndex, result.index);
65 | prevIndex = result.index + result[0].length;
66 | after = message.msg.slice(prevIndex, urlRegexp.lastIndex);
67 | let imgOrTxt = result[0];
68 | if (this.state.ignoredURLs.indexOf(imgOrTxt) === -1 && utils.image_mime_from_extension(imgOrTxt)) {
69 | imgOrTxt = (
70 |
71 |
72 |

73 |
);
74 | }
75 |
76 | msgTxt.push(
77 |
78 | {before}
79 | {imgOrTxt}
80 | {after}
81 |
82 | );
83 | }
84 |
85 | return (
86 |
87 |
88 |
{message.prettyTime}
89 |
90 |
91 |
92 | {message.username || message.type}:
93 |
94 | {msgTxt}
95 |
96 |
97 |
98 | );
99 | }
100 | });
101 |
102 | const InteractiveMessageView = React.createClass({
103 | getInitialState: function () {
104 | return {
105 | clicked: null
106 | };
107 | },
108 | onClick: function (button) {
109 | if (this.state.clicked !== null) {
110 | return;
111 | }
112 | button.action();
113 | this.setState({clicked: button.id});
114 | },
115 | render: function () {
116 | var message = this.props.message,
117 | buttons = message.buttons || [];
118 |
119 | buttons = buttons.map(function (b) {
120 | var classes = "btn ",
121 | clicked = this.state.clicked;
122 |
123 | if (clicked === null || clicked === b.id) {
124 | classes += b.classNames.join(" ");
125 | }
126 | if (clicked === b.id) {
127 | classes += " dim";
128 | }
129 | return (
130 |
131 | );
132 | }, this);
133 |
134 | return (
135 |
136 |
137 |
{message.prettyTime}
138 |
139 | {message.msg}
140 | {buttons.length &&
141 |
{buttons}
142 | }
143 |
144 |
145 |
146 | );
147 | }
148 | });
149 |
150 | const MessagesView = React.createClass({
151 | mixins: [flux.createAutoBinder(["messages"])],
152 | handleMessage_: function (event) {
153 | event.preventDefault();
154 | const input = this.refs.newMessage.getDOMNode();
155 | const value = input.value;
156 | let ret = floop.send_msg({data: value});
157 | if (ret) {
158 | ret = ret.message || ret.toString();
159 | messageAction.error(ret, false);
160 | return;
161 | }
162 | input.value = "";
163 | messageAction.user(this.props.username, value, Date.now() / 1000);
164 | },
165 | componentDidMount: function () {
166 | // focus in chat but not editor proxy :(
167 | if (this.props.focus && this.refs.newMessage) {
168 | this.focus();
169 | }
170 | },
171 | getMessages: function () {
172 | const messages = [];
173 | let prevLogMessage = null;
174 | this.props.messages.forEach(function (message) {
175 | if (message.type !== "log") {
176 | prevLogMessage = null;
177 | messages.push({message});
178 | return;
179 | }
180 | if (prevLogMessage === message.msg) {
181 | _.last(messages).repeatCount += 1;
182 | return;
183 | }
184 | messages.push({message, repeatCount: 0});
185 | prevLogMessage = message.msg;
186 | });
187 | return messages;
188 | },
189 | focus: function () {
190 | this.refs.newMessage.getDOMNode().focus();
191 | },
192 | render: function () {
193 | let chatInput = "";
194 | const nodes = this.getMessages().map(function (messageObj) {
195 | const message = messageObj.message;
196 | switch (message.type) {
197 | case "user":
198 | return ;
199 | case "log":
200 | return ;
201 | case "interactive":
202 | return ;
203 | default:
204 | console.error("Unknown message type:", message.type);
205 | break;
206 | }
207 | }, this);
208 | if (!this.props.hideChat) {
209 | chatInput = (
210 |
211 |
214 |
215 | );
216 | }
217 |
218 | return (
219 |
223 |
224 | {chatInput}
225 |
226 | {nodes}
227 |
228 |
229 |
230 | );
231 | }
232 | });
233 |
234 | module.exports = {
235 | InteractiveMessageView,
236 | LogMessageView,
237 | MessagesView,
238 | UserMessageView,
239 | };
240 |
--------------------------------------------------------------------------------
/templates/mixins.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const ReactUnwrapper = {
4 | destroy: function (e) {
5 | if (e) {
6 | e.preventDefault();
7 | }
8 | if (!this.isMounted()) {
9 | return;
10 | }
11 | this.getDOMNode().parentNode.destroy();
12 | }
13 | };
14 |
15 | const $ = require('atom-space-pen-views').$;
16 |
17 | const FormMixin = {
18 | componentDidMount: function () {
19 | const that = this;
20 | $(this.getDOMNode()).keydown(function (k) {
21 | switch (k.keyCode) {
22 | case 27: // escape
23 | that.destroy(k);
24 | return;
25 | case 13: // enter
26 | that.onSubmit(k);
27 | return;
28 | default:
29 | break;
30 | }
31 | });
32 | },
33 | componentWillUnmount: function () {
34 | $(this.getDOMNode()).off("keydown");
35 | },
36 | };
37 |
38 | module.exports = {
39 | ReactUnwrapper: ReactUnwrapper,
40 | FormMixin: FormMixin,
41 | };
42 |
--------------------------------------------------------------------------------
/templates/pane.coffee:
--------------------------------------------------------------------------------
1 | {$, View} = require 'atom-space-pen-views'
2 |
3 | class PaneView extends View
4 | focus: -> @input.focus()
5 | initialize: (@pane) =>
6 | @pane.setView @
7 | this[0].appendChild(@pane.inner)
8 |
9 | detached: ->
10 | console.log "detached"
11 | @pane.onDetached?()
12 |
13 | @content: (params) ->
14 | @div 'style': "overflow: auto;", "class": "native-key-bindings"
15 |
16 | class Pane
17 | constructor: (@title, @iconName, @inner, @onDetached) ->
18 | getViewClass: -> PaneView
19 | getView: -> @view
20 | setView: (@view) ->
21 | getTitle: () =>
22 | @title
23 | getIconName: () =>
24 | @iconName
25 |
26 | module.exports = Pane
27 |
--------------------------------------------------------------------------------
/templates/permission_view.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | "use strict";
3 |
4 | var React = require('react-atom-fork');
5 | var floop = require("../common/floop");
6 | var mixins = require("./mixins");
7 | var _ = require("lodash");
8 |
9 | var PermissionView = React.createClass({
10 | mixins: [mixins.ReactUnwrapper, mixins.FormMixin],
11 | getInitialState: function () {
12 | var permissions;
13 | permissions = this.props.user.permissions;
14 | return {
15 | admin_room: permissions.indexOf("kick") !== -1,
16 | view_room: permissions.indexOf("get_buf") !== -1,
17 | edit_room: permissions.indexOf("set_buf") !== -1,
18 | request_perms: permissions.indexOf("request_perms") !== -1,
19 | };
20 | },
21 | componentDidMount: function () {
22 | // TODO. this is a bootstrap-ism
23 | // this.props.user.on("change:connected", modal.killModal);
24 | return;
25 | },
26 | renderField: function (label, name, description) {
27 | return (
28 |
29 |
30 |
31 |
32 | {description}
33 |
34 |
35 | );
36 | },
37 | /**
38 | * @param {Event} event
39 | * @private
40 | */
41 | onSubmit: function (event) {
42 | var newPerms;
43 | event.preventDefault();
44 | newPerms = ["view_room", "edit_room", "request_perms", "admin_room"].filter(function (perm) {
45 | return this.state[perm];
46 | }, this);
47 | floop.send_perms({
48 | user_id: this.props.user.getConnectionID(),
49 | perms: newPerms,
50 | action: "set",
51 | });
52 | this.destroy();
53 | },
54 | /**
55 | * @param {string} type
56 | * @param {Object} event
57 | * @private
58 | */
59 | handleChange_: function (type, event) {
60 | var index, permissions, state, checked;
61 | permissions = ["view_room", "request_perms", "edit_room", "admin_room"];
62 | checked = event.target.checked;
63 | index = permissions.indexOf(type);
64 | state = {};
65 |
66 | permissions.forEach(function (perm, i) {
67 | if (i < index && checked) {
68 | state[perm] = checked;
69 | } else if (i > index && !checked) {
70 | state[perm] = checked;
71 | } else if (i === index) {
72 | state[perm] = checked;
73 | } else {
74 | state[perm] = this.state[perm];
75 | }
76 | }, this);
77 | this.setState(state);
78 | },
79 | render: function () {
80 | var fields = [
81 | ["View", "view_room", "view files and terminals in this workspace"],
82 | ["Request permissions", "request_perms", "ask admins for permission to edit files in this workspace"],
83 | ["Edit", "edit_room", "edit files in this workspace"],
84 | ["Administer", "admin_room", "set permissions and type in all terminals"],
85 | ].map(function (perm) {
86 | return this.renderField.apply(this, perm);
87 | }, this);
88 |
89 |
90 | return (
91 |
92 |
Permissions for {this.props.user.id}
93 |
94 |
99 |
100 |
106 |
107 | );
108 | }
109 | });
110 |
111 | module.exports = PermissionView;
112 |
113 |
--------------------------------------------------------------------------------
/templates/status_bar.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | "use strict";
3 |
4 | const React = require("react-atom-fork");
5 | const flux = require("flukes");
6 | const prefs = require("../common/userPref_model");
7 |
8 | const floop = require("../common/floop");
9 |
10 | const StatusBarView = React.createClass({
11 | mixins: [flux.createAutoBinder(["me"], [prefs])],
12 | getInitialState: function () {
13 | return {
14 | conn_status: "Connecting...",
15 | };
16 | },
17 | componentWillMount: function () {
18 | const that = this;
19 | floop.onROOM_INFO(function () {
20 | that.setState({conn_status: "Connected."});
21 | });
22 | floop.onDISCONNECT(function () {
23 | that.setState({conn_status: "Disconnected."});
24 | });
25 | },
26 | render: function () {
27 | let workspace = `${this.props.floourl.owner}/${this.props.floourl.workspace}`;
28 | let following_status;
29 | if (prefs.following) {
30 | following_status = " Following changes.";
31 | } else if (prefs.followUsers.length) {
32 | following_status = `Following ${prefs.followUsers.join(", ")}`;
33 | }
34 | return (
35 |
36 | {this.props.me.id}@{workspace}: {this.state.conn_status} {following_status}
37 |
38 | );
39 | }
40 | });
41 |
42 | module.exports = StatusBarView;
43 |
--------------------------------------------------------------------------------
/templates/user_list_pane.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 | "use strict";
3 | "use babel";
4 |
5 | const React = require("react-atom-fork");
6 | const mixins = require("./mixins");
7 | const UserlistView = require("./user_view").UserlistView;
8 |
9 | const UserlistPane = React.createClass({
10 | mixins: [mixins.ReactUnwrapper],
11 | render: function () {
12 | return (
13 |
14 |
18 |
19 |
20 | );
21 | }
22 | });
23 |
24 | module.exports = UserlistPane;
25 |
--------------------------------------------------------------------------------
/templates/webview.js:
--------------------------------------------------------------------------------
1 | /*global HTMLElement: true */
2 |
3 | "use strict";
4 |
5 | const _ = require("lodash");
6 | const floorc = require("../common/floorc");
7 | const message_action = require("../common/message_action");
8 | const Pane = require("../../templates/pane");
9 |
10 | const Proto = Object.create(HTMLElement.prototype);
11 |
12 | Proto.createdCallback = function () {
13 | const frame = document.createElement("webview");
14 | frame.setAttribute("plugins", "on");
15 | frame.setAttribute("disablewebsecurity", "on");
16 | frame.setAttribute("allowPointerLock", 'on');
17 | frame.setAttribute("allowfileaccessfromfiles", 'on');
18 | frame.style.border = "0 none";
19 | frame.style.width = "100%";
20 | frame.style.height = "100%";
21 | frame.className = "native-key-bindings";
22 |
23 | frame.addEventListener("console-message",function (e) {
24 | let m = e.message;
25 | const prefix = "::atom-floobits-ipc::";
26 | if (!m.startsWith(prefix)) {
27 | return;
28 | }
29 | m = m.slice(prefix.length);
30 | console.log(m);
31 | try {
32 | m = JSON.parse(m);
33 | } catch (e) {
34 | return;
35 | }
36 | this.onCredentials(m.auth);
37 | return;
38 | }.bind(this));
39 | this.frame = frame;
40 | };
41 |
42 | Proto.onCredentials = function (blob) {
43 | if (!_.has(floorc, "auth")) {
44 | floorc.auth = {};
45 | }
46 |
47 | const host = _.keys(blob)[0];
48 | const auth_data = blob[host];
49 |
50 | if (!_.has(floorc.auth, host)) {
51 | floorc.auth[host] = {};
52 | }
53 |
54 | const floorc_auth = floorc.auth[host];
55 |
56 | floorc_auth.username = auth_data.username;
57 | floorc_auth.api_key = auth_data.api_key;
58 | floorc_auth.secret = auth_data.secret;
59 |
60 | try {
61 | floorc.__write();
62 | } catch (e) {
63 | return message_action.error(e);
64 | }
65 | };
66 |
67 | Proto.load = function (host) {
68 | this.host = host;
69 | this.pane = new Pane("Floobits", "", this);
70 | atom.workspace.getActivePane().activateItem(this.pane);
71 | this.frame.src = `https://${host}/signup/atom`;
72 | };
73 |
74 | Proto.attachedCallback = function () {
75 | this.className = "floobits-nativize";
76 | this.appendChild(this.frame);
77 | };
78 |
79 | Proto.detachedCallback = function () {
80 | this.destroy();
81 | };
82 |
83 | Proto.destroy = function () {
84 | if (!this.pane) {
85 | return;
86 | }
87 | try {
88 | this.pane.destroy();
89 | } catch (e) {
90 | console.warn(e);
91 | }
92 | this.pane = null;
93 | };
94 |
95 | module.exports = document.registerElement("floobits-welcome_web_view", {prototype: Proto});
96 |
--------------------------------------------------------------------------------
/templates/yes_no_cancel.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 |
3 | "use strict";
4 |
5 | const React = require('react-atom-fork');
6 | const mixins = require("./mixins");
7 | const atomUtils = require("../atom_utils");
8 |
9 | const YesNoCancel = React.createClass({
10 | mixins: [mixins.ReactUnwrapper, mixins.FormMixin],
11 | onSubmit: function (type) {
12 | if (!type) {
13 | type = "yes";
14 | } else if (type.target) {
15 | type = type.target.name;
16 | }
17 | const cb = this.props.cb.bind({}, null, type);
18 | setTimeout(cb, 0);
19 | this.destroy();
20 | },
21 | componentDidMount: function () {
22 | this.refs.yes.getDOMNode().focus();
23 | },
24 | render: function () {
25 | const yes = this.props.yes || "Yes";
26 | const no = this.props.no || "No";
27 | const cancel = this.props.cancel || "Cancel";
28 | return (
29 |
52 | );
53 | }
54 | });
55 |
56 | module.exports = function (title, body, opts, cb) {
57 | if (!cb) {
58 | cb = opts;
59 | opts = {};
60 | }
61 |
62 | opts.title = title;
63 | opts.body = body;
64 | opts.cb = cb;
65 |
66 | const view = YesNoCancel(opts);
67 | atomUtils.addModalPanel('yes-no-cancel', view);
68 | };
--------------------------------------------------------------------------------