Commit edcd20f8 authored by Eugene Shen's avatar Eugene Shen

First commit of all OfficeJS changes in March

parent 39afba4f
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* eslint-env node */
'use strict';
// SDP helpers.
var SDPUtils = {};
// Generate an alphanumeric identifier for cname or mids.
// TODO: use UUIDs instead? https://gist.github.com/jed/982883
SDPUtils.generateIdentifier = function() {
return Math.random().toString(36).substr(2, 10);
};
// The RTCP CNAME used by all peerconnections from the same JS.
SDPUtils.localCName = SDPUtils.generateIdentifier();
// Splits SDP into lines, dealing with both CRLF and LF.
SDPUtils.splitLines = function(blob) {
return blob.trim().split('\n').map(function(line) {
return line.trim();
});
};
// Splits SDP into sessionpart and mediasections. Ensures CRLF.
SDPUtils.splitSections = function(blob) {
var parts = blob.split('\nm=');
return parts.map(function(part, index) {
return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
});
};
// Returns lines that start with a certain prefix.
SDPUtils.matchPrefix = function(blob, prefix) {
return SDPUtils.splitLines(blob).filter(function(line) {
return line.indexOf(prefix) === 0;
});
};
// Parses an ICE candidate line. Sample input:
// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
// rport 55996"
SDPUtils.parseCandidate = function(line) {
var parts;
// Parse both variants.
if (line.indexOf('a=candidate:') === 0) {
parts = line.substring(12).split(' ');
} else {
parts = line.substring(10).split(' ');
}
var candidate = {
foundation: parts[0],
component: parts[1],
protocol: parts[2].toLowerCase(),
priority: parseInt(parts[3], 10),
ip: parts[4],
port: parseInt(parts[5], 10),
// skip parts[6] == 'typ'
type: parts[7]
};
for (var i = 8; i < parts.length; i += 2) {
switch (parts[i]) {
case 'raddr':
candidate.relatedAddress = parts[i + 1];
break;
case 'rport':
candidate.relatedPort = parseInt(parts[i + 1], 10);
break;
case 'tcptype':
candidate.tcpType = parts[i + 1];
break;
default: // Unknown extensions are silently ignored.
break;
}
}
return candidate;
};
// Translates a candidate object into SDP candidate attribute.
SDPUtils.writeCandidate = function(candidate) {
var sdp = [];
sdp.push(candidate.foundation);
sdp.push(candidate.component);
sdp.push(candidate.protocol.toUpperCase());
sdp.push(candidate.priority);
sdp.push(candidate.ip);
sdp.push(candidate.port);
var type = candidate.type;
sdp.push('typ');
sdp.push(type);
if (type !== 'host' && candidate.relatedAddress &&
candidate.relatedPort) {
sdp.push('raddr');
sdp.push(candidate.relatedAddress); // was: relAddr
sdp.push('rport');
sdp.push(candidate.relatedPort); // was: relPort
}
if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
sdp.push('tcptype');
sdp.push(candidate.tcpType);
}
return 'candidate:' + sdp.join(' ');
};
// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
// a=rtpmap:111 opus/48000/2
SDPUtils.parseRtpMap = function(line) {
var parts = line.substr(9).split(' ');
var parsed = {
payloadType: parseInt(parts.shift(), 10) // was: id
};
parts = parts[0].split('/');
parsed.name = parts[0];
parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
// was: channels
parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
return parsed;
};
// Generate an a=rtpmap line from RTCRtpCodecCapability or
// RTCRtpCodecParameters.
SDPUtils.writeRtpMap = function(codec) {
var pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
(codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
};
// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
SDPUtils.parseExtmap = function(line) {
var parts = line.substr(9).split(' ');
return {
id: parseInt(parts[0], 10),
uri: parts[1]
};
};
// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
// RTCRtpHeaderExtension.
SDPUtils.writeExtmap = function(headerExtension) {
return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
' ' + headerExtension.uri + '\r\n';
};
// Parses an ftmp line, returns dictionary. Sample input:
// a=fmtp:96 vbr=on;cng=on
// Also deals with vbr=on; cng=on
SDPUtils.parseFmtp = function(line) {
var parsed = {};
var kv;
var parts = line.substr(line.indexOf(' ') + 1).split(';');
for (var j = 0; j < parts.length; j++) {
kv = parts[j].trim().split('=');
parsed[kv[0].trim()] = kv[1];
}
return parsed;
};
// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
SDPUtils.writeFmtp = function(codec) {
var line = '';
var pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
if (codec.parameters && Object.keys(codec.parameters).length) {
var params = [];
Object.keys(codec.parameters).forEach(function(param) {
params.push(param + '=' + codec.parameters[param]);
});
line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
}
return line;
};
// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
// a=rtcp-fb:98 nack rpsi
SDPUtils.parseRtcpFb = function(line) {
var parts = line.substr(line.indexOf(' ') + 1).split(' ');
return {
type: parts.shift(),
parameter: parts.join(' ')
};
};
// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
SDPUtils.writeRtcpFb = function(codec) {
var lines = '';
var pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
// FIXME: special handling for trr-int?
codec.rtcpFeedback.forEach(function(fb) {
lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +
(fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +
'\r\n';
});
}
return lines;
};
// Parses an RFC 5576 ssrc media attribute. Sample input:
// a=ssrc:3735928559 cname:something
SDPUtils.parseSsrcMedia = function(line) {
var sp = line.indexOf(' ');
var parts = {
ssrc: parseInt(line.substr(7, sp - 7), 10)
};
var colon = line.indexOf(':', sp);
if (colon > -1) {
parts.attribute = line.substr(sp + 1, colon - sp - 1);
parts.value = line.substr(colon + 1);
} else {
parts.attribute = line.substr(sp + 1);
}
return parts;
};
// Extracts DTLS parameters from SDP media section or sessionpart.
// FIXME: for consistency with other functions this should only
// get the fingerprint line as input. See also getIceParameters.
SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
var lines = SDPUtils.splitLines(mediaSection);
// Search in session part, too.
lines = lines.concat(SDPUtils.splitLines(sessionpart));
var fpLine = lines.filter(function(line) {
return line.indexOf('a=fingerprint:') === 0;
})[0].substr(14);
// Note: a=setup line is ignored since we use the 'auto' role.
var dtlsParameters = {
role: 'auto',
fingerprints: [{
algorithm: fpLine.split(' ')[0],
value: fpLine.split(' ')[1]
}]
};
return dtlsParameters;
};
// Serializes DTLS parameters to SDP.
SDPUtils.writeDtlsParameters = function(params, setupType) {
var sdp = 'a=setup:' + setupType + '\r\n';
params.fingerprints.forEach(function(fp) {
sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
});
return sdp;
};
// Parses ICE information from SDP media section or sessionpart.
// FIXME: for consistency with other functions this should only
// get the ice-ufrag and ice-pwd lines as input.
SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
var lines = SDPUtils.splitLines(mediaSection);
// Search in session part, too.
lines = lines.concat(SDPUtils.splitLines(sessionpart));
var iceParameters = {
usernameFragment: lines.filter(function(line) {
return line.indexOf('a=ice-ufrag:') === 0;
})[0].substr(12),
password: lines.filter(function(line) {
return line.indexOf('a=ice-pwd:') === 0;
})[0].substr(10)
};
return iceParameters;
};
// Serializes ICE parameters to SDP.
SDPUtils.writeIceParameters = function(params) {
return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
'a=ice-pwd:' + params.password + '\r\n';
};
// Parses the SDP media section and returns RTCRtpParameters.
SDPUtils.parseRtpParameters = function(mediaSection) {
var description = {
codecs: [],
headerExtensions: [],
fecMechanisms: [],
rtcp: []
};
var lines = SDPUtils.splitLines(mediaSection);
var mline = lines[0].split(' ');
for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
var pt = mline[i];
var rtpmapline = SDPUtils.matchPrefix(
mediaSection, 'a=rtpmap:' + pt + ' ')[0];
if (rtpmapline) {
var codec = SDPUtils.parseRtpMap(rtpmapline);
var fmtps = SDPUtils.matchPrefix(
mediaSection, 'a=fmtp:' + pt + ' ');
// Only the first a=fmtp:<pt> is considered.
codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
codec.rtcpFeedback = SDPUtils.matchPrefix(
mediaSection, 'a=rtcp-fb:' + pt + ' ')
.map(SDPUtils.parseRtcpFb);
description.codecs.push(codec);
// parse FEC mechanisms from rtpmap lines.
switch (codec.name.toUpperCase()) {
case 'RED':
case 'ULPFEC':
description.fecMechanisms.push(codec.name.toUpperCase());
break;
default: // only RED and ULPFEC are recognized as FEC mechanisms.
break;
}
}
}
SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {
description.headerExtensions.push(SDPUtils.parseExtmap(line));
});
// FIXME: parse rtcp.
return description;
};
// Generates parts of the SDP media section describing the capabilities /
// parameters.
SDPUtils.writeRtpDescription = function(kind, caps) {
var sdp = '';
// Build the mline.
sdp += 'm=' + kind + ' ';
sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
sdp += ' UDP/TLS/RTP/SAVPF ';
sdp += caps.codecs.map(function(codec) {
if (codec.preferredPayloadType !== undefined) {
return codec.preferredPayloadType;
}
return codec.payloadType;
}).join(' ') + '\r\n';
sdp += 'c=IN IP4 0.0.0.0\r\n';
sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
// Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
caps.codecs.forEach(function(codec) {
sdp += SDPUtils.writeRtpMap(codec);
sdp += SDPUtils.writeFmtp(codec);
sdp += SDPUtils.writeRtcpFb(codec);
});
sdp += 'a=rtcp-mux\r\n';
caps.headerExtensions.forEach(function(extension) {
sdp += SDPUtils.writeExtmap(extension);
});
// FIXME: write fecMechanisms.
return sdp;
};
// Parses the SDP media section and returns an array of
// RTCRtpEncodingParameters.
SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
var encodingParameters = [];
var description = SDPUtils.parseRtpParameters(mediaSection);
var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
// filter a=ssrc:... cname:, ignore PlanB-msid
var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
.map(function(line) {
return SDPUtils.parseSsrcMedia(line);
})
.filter(function(parts) {
return parts.attribute === 'cname';
});
var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
var secondarySsrc;
var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
.map(function(line) {
var parts = line.split(' ');
parts.shift();
return parts.map(function(part) {
return parseInt(part, 10);
});
});
if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
secondarySsrc = flows[0][1];
}
description.codecs.forEach(function(codec) {
if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
var encParam = {
ssrc: primarySsrc,
codecPayloadType: parseInt(codec.parameters.apt, 10),
rtx: {
payloadType: codec.payloadType,
ssrc: secondarySsrc
}
};
encodingParameters.push(encParam);
if (hasRed) {
encParam = JSON.parse(JSON.stringify(encParam));
encParam.fec = {
ssrc: secondarySsrc,
mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
};
encodingParameters.push(encParam);
}
}
});
if (encodingParameters.length === 0 && primarySsrc) {
encodingParameters.push({
ssrc: primarySsrc
});
}
// we support both b=AS and b=TIAS but interpret AS as TIAS.
var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
if (bandwidth.length) {
if (bandwidth[0].indexOf('b=TIAS:') === 0) {
bandwidth = parseInt(bandwidth[0].substr(7), 10);
} else if (bandwidth[0].indexOf('b=AS:') === 0) {
bandwidth = parseInt(bandwidth[0].substr(5), 10);
}
encodingParameters.forEach(function(params) {
params.maxBitrate = bandwidth;
});
}
return encodingParameters;
};
SDPUtils.writeSessionBoilerplate = function() {
// FIXME: sess-id should be an NTP timestamp.
return 'v=0\r\n' +
'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' +
's=-\r\n' +
't=0 0\r\n';
};
SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
// Map ICE parameters (ufrag, pwd) to SDP.
sdp += SDPUtils.writeIceParameters(
transceiver.iceGatherer.getLocalParameters());
// Map DTLS parameters to SDP.
sdp += SDPUtils.writeDtlsParameters(
transceiver.dtlsTransport.getLocalParameters(),
type === 'offer' ? 'actpass' : 'active');
sdp += 'a=mid:' + transceiver.mid + '\r\n';
if (transceiver.rtpSender && transceiver.rtpReceiver) {
sdp += 'a=sendrecv\r\n';
} else if (transceiver.rtpSender) {
sdp += 'a=sendonly\r\n';
} else if (transceiver.rtpReceiver) {
sdp += 'a=recvonly\r\n';
} else {
sdp += 'a=inactive\r\n';
}
// FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet.
if (transceiver.rtpSender) {
var msid = 'msid:' + stream.id + ' ' +
transceiver.rtpSender.track.id + '\r\n';
sdp += 'a=' + msid;
sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
' ' + msid;
}
// FIXME: this should be written by writeRtpDescription.
sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
' cname:' + SDPUtils.localCName + '\r\n';
return sdp;
};
// Gets the direction from the mediaSection or the sessionpart.
SDPUtils.getDirection = function(mediaSection, sessionpart) {
// Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
var lines = SDPUtils.splitLines(mediaSection);
for (var i = 0; i < lines.length; i++) {
switch (lines[i]) {
case 'a=sendrecv':
case 'a=sendonly':
case 'a=recvonly':
case 'a=inactive':
return lines[i].substr(2);
default:
// FIXME: What should happen here?
}
}
if (sessionpart) {
return SDPUtils.getDirection(sessionpart);
}
return 'sendrecv';
};
// Expose public methods.
module.exports = SDPUtils;
},{}],2:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
// Shimming starts here.
(function() {
// Utils.
var utils = require('./utils');
var logging = utils.log;
var browserDetails = utils.browserDetails;
// Export to the adapter global object visible in the browser.
module.exports.browserDetails = browserDetails;
module.exports.extractVersion = utils.extractVersion;
module.exports.disableLog = utils.disableLog;
// Uncomment the line below if you want logging to occur, including logging
// for the switch statement below. Can also be turned on in the browser via
// adapter.disableLog(false), but then logging from the switch statement below
// will not appear.
// require('./utils').disableLog(false);
// Browser shims.
var chromeShim = require('./chrome/chrome_shim') || null;
var edgeShim = require('./edge/edge_shim') || null;
var firefoxShim = require('./firefox/firefox_shim') || null;
var safariShim = require('./safari/safari_shim') || null;
// Shim browser if found.
switch (browserDetails.browser) {
case 'opera': // fallthrough as it uses chrome shims
case 'chrome':
if (!chromeShim || !chromeShim.shimPeerConnection) {
logging('Chrome shim is not included in this adapter release.');
return;
}
logging('adapter.js shimming chrome.');
// Export to the adapter global object visible in the browser.
module.exports.browserShim = chromeShim;
chromeShim.shimGetUserMedia();
chromeShim.shimMediaStream();
chromeShim.shimSourceObject();
utils.shimCreateObjectURL();
chromeShim.shimPeerConnection();
chromeShim.shimOnTrack();
break;
case 'firefox':
if (!firefoxShim || !firefoxShim.shimPeerConnection) {
logging('Firefox shim is not included in this adapter release.');
return;
}
logging('adapter.js shimming firefox.');
// Export to the adapter global object visible in the browser.
module.exports.browserShim = firefoxShim;
firefoxShim.shimGetUserMedia();
utils.shimCreateObjectURL();
firefoxShim.shimSourceObject();
firefoxShim.shimPeerConnection();
firefoxShim.shimOnTrack();
break;
case 'edge':
if (!edgeShim || !edgeShim.shimPeerConnection) {
logging('MS edge shim is not included in this adapter release.');
return;
}
logging('adapter.js shimming edge.');
// Export to the adapter global object visible in the browser.
module.exports.browserShim = edgeShim;
edgeShim.shimGetUserMedia();
utils.shimCreateObjectURL();
edgeShim.shimPeerConnection();
break;
case 'safari':
if (!safariShim) {
logging('Safari shim is not included in this adapter release.');
return;
}
logging('adapter.js shimming safari.');
// Export to the adapter global object visible in the browser.
module.exports.browserShim = safariShim;
safariShim.shimGetUserMedia();
break;
default:
logging('Unsupported browser!');
}
})();
},{"./chrome/chrome_shim":3,"./edge/edge_shim":5,"./firefox/firefox_shim":7,"./safari/safari_shim":9,"./utils":10}],3:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var logging = require('../utils.js').log;
var browserDetails = require('../utils.js').browserDetails;
var chromeShim = {
shimMediaStream: function() {
window.MediaStream = window.MediaStream || window.webkitMediaStream;
},
shimOnTrack: function() {
if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
window.RTCPeerConnection.prototype)) {
Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
get: function() {
return this._ontrack;
},
set: function(f) {
var self = this;
if (this._ontrack) {
this.removeEventListener('track', this._ontrack);
this.removeEventListener('addstream', this._ontrackpoly);
}
this.addEventListener('track', this._ontrack = f);
this.addEventListener('addstream', this._ontrackpoly = function(e) {
// onaddstream does not fire when a track is added to an existing
// stream. But stream.onaddtrack is implemented so we use that.
e.stream.addEventListener('addtrack', function(te) {
var event = new Event('track');
event.track = te.track;
event.receiver = {track: te.track};
event.streams = [e.stream];
self.dispatchEvent(event);
});
e.stream.getTracks().forEach(function(track) {
var event = new Event('track');
event.track = track;
event.receiver = {track: track};
event.streams = [e.stream];
this.dispatchEvent(event);
}.bind(this));
}.bind(this));
}
});
}
},
shimSourceObject: function() {
if (typeof window === 'object') {
if (window.HTMLMediaElement &&
!('srcObject' in window.HTMLMediaElement.prototype)) {
// Shim the srcObject property, once, when HTMLMediaElement is found.
Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
get: function() {
return this._srcObject;
},
set: function(stream) {
var self = this;
// Use _srcObject as a private property for this shim
this._srcObject = stream;
if (this.src) {
URL.revokeObjectURL(this.src);
}
if (!stream) {
this.src = '';
return;
}
this.src = URL.createObjectURL(stream);
// We need to recreate the blob url when a track is added or
// removed. Doing it manually since we want to avoid a recursion.
stream.addEventListener('addtrack', function() {
if (self.src) {
URL.revokeObjectURL(self.src);
}
self.src = URL.createObjectURL(stream);
});
stream.addEventListener('removetrack', function() {
if (self.src) {
URL.revokeObjectURL(self.src);
}
self.src = URL.createObjectURL(stream);
});
}
});
}
}
},
shimPeerConnection: function() {
// The RTCPeerConnection object.
if (!window.RTCPeerConnection) {
window.RTCPeerConnection = function(pcConfig, pcConstraints) {
// Translate iceTransportPolicy to iceTransports,
// see https://code.google.com/p/webrtc/issues/detail?id=4869
// this was fixed in M56 along with unprefixing RTCPeerConnection.
logging('PeerConnection');
if (pcConfig && pcConfig.iceTransportPolicy) {
pcConfig.iceTransports = pcConfig.iceTransportPolicy;
}
return new webkitRTCPeerConnection(pcConfig, pcConstraints);
};
window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype;
// wrap static methods. Currently just generateCertificate.
if (webkitRTCPeerConnection.generateCertificate) {
Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
get: function() {
return webkitRTCPeerConnection.generateCertificate;
}
});
}
}
var origGetStats = RTCPeerConnection.prototype.getStats;
RTCPeerConnection.prototype.getStats = function(selector,
successCallback, errorCallback) {
var self = this;
var args = arguments;
// If selector is a function then we are in the old style stats so just
// pass back the original getStats format to avoid breaking old users.
if (arguments.length > 0 && typeof selector === 'function') {
return origGetStats.apply(this, arguments);
}
// When spec-style getStats is supported, return those.
if (origGetStats.length === 0) {
return origGetStats.apply(this, arguments);
}
var fixChromeStats_ = function(response) {
var standardReport = {};
var reports = response.result();
reports.forEach(function(report) {
var standardStats = {
id: report.id,
timestamp: report.timestamp,
type: {
localcandidate: 'local-candidate',
remotecandidate: 'remote-candidate'
}[report.type] || report.type
};
report.names().forEach(function(name) {
standardStats[name] = report.stat(name);
});
standardReport[standardStats.id] = standardStats;
});
return standardReport;
};
// shim getStats with maplike support
var makeMapStats = function(stats) {
return new Map(Object.keys(stats).map(function(key) {
return[key, stats[key]];
}));
};
if (arguments.length >= 2) {
var successCallbackWrapper_ = function(response) {
args[1](makeMapStats(fixChromeStats_(response)));
};
return origGetStats.apply(this, [successCallbackWrapper_,
arguments[0]]);
}
// promise-support
return new Promise(function(resolve, reject) {
origGetStats.apply(self, [
function(response) {
resolve(makeMapStats(fixChromeStats_(response)));
}, reject]);
}).then(successCallback, errorCallback);
};
// add promise support -- natively available in Chrome 51
if (browserDetails.version < 51) {
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
.forEach(function(method) {
var nativeMethod = RTCPeerConnection.prototype[method];
RTCPeerConnection.prototype[method] = function() {
var args = arguments;
var self = this;
var promise = new Promise(function(resolve, reject) {
nativeMethod.apply(self, [args[0], resolve, reject]);
});
if (args.length < 2) {
return promise;
}
return promise.then(function() {
args[1].apply(null, []);
},
function(err) {
if (args.length >= 3) {
args[2].apply(null, [err]);
}
});
};
});
}
// promise support for createOffer and createAnswer. Available (without
// bugs) since M52: crbug/619289
if (browserDetails.version < 52) {
['createOffer', 'createAnswer'].forEach(function(method) {
var nativeMethod = RTCPeerConnection.prototype[method];
RTCPeerConnection.prototype[method] = function() {
var self = this;
if (arguments.length < 1 || (arguments.length === 1 &&
typeof arguments[0] === 'object')) {
var opts = arguments.length === 1 ? arguments[0] : undefined;
return new Promise(function(resolve, reject) {
nativeMethod.apply(self, [resolve, reject, opts]);
});
}
return nativeMethod.apply(this, arguments);
};
});
}
// shim implicit creation of RTCSessionDescription/RTCIceCandidate
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
.forEach(function(method) {
var nativeMethod = RTCPeerConnection.prototype[method];
RTCPeerConnection.prototype[method] = function() {
arguments[0] = new ((method === 'addIceCandidate') ?
RTCIceCandidate : RTCSessionDescription)(arguments[0]);
return nativeMethod.apply(this, arguments);
};
});
// support for addIceCandidate(null or undefined)
var nativeAddIceCandidate =
RTCPeerConnection.prototype.addIceCandidate;
RTCPeerConnection.prototype.addIceCandidate = function() {
if (!arguments[0]) {
if (arguments[1]) {
arguments[1].apply(null);
}
return Promise.resolve();
}
return nativeAddIceCandidate.apply(this, arguments);
};
}
};
// Expose public methods.
module.exports = {
shimMediaStream: chromeShim.shimMediaStream,
shimOnTrack: chromeShim.shimOnTrack,
shimSourceObject: chromeShim.shimSourceObject,
shimPeerConnection: chromeShim.shimPeerConnection,
shimGetUserMedia: require('./getusermedia')
};
},{"../utils.js":10,"./getusermedia":4}],4:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var logging = require('../utils.js').log;
// Expose public methods.
module.exports = function() {
var constraintsToChrome_ = function(c) {
if (typeof c !== 'object' || c.mandatory || c.optional) {
return c;
}
var cc = {};
Object.keys(c).forEach(function(key) {
if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
return;
}
var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
if (r.exact !== undefined && typeof r.exact === 'number') {
r.min = r.max = r.exact;
}
var oldname_ = function(prefix, name) {
if (prefix) {
return prefix + name.charAt(0).toUpperCase() + name.slice(1);
}
return (name === 'deviceId') ? 'sourceId' : name;
};
if (r.ideal !== undefined) {
cc.optional = cc.optional || [];
var oc = {};
if (typeof r.ideal === 'number') {
oc[oldname_('min', key)] = r.ideal;
cc.optional.push(oc);
oc = {};
oc[oldname_('max', key)] = r.ideal;
cc.optional.push(oc);
} else {
oc[oldname_('', key)] = r.ideal;
cc.optional.push(oc);
}
}
if (r.exact !== undefined && typeof r.exact !== 'number') {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname_('', key)] = r.exact;
} else {
['min', 'max'].forEach(function(mix) {
if (r[mix] !== undefined) {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname_(mix, key)] = r[mix];
}
});
}
});
if (c.advanced) {
cc.optional = (cc.optional || []).concat(c.advanced);
}
return cc;
};
var shimConstraints_ = function(constraints, func) {
constraints = JSON.parse(JSON.stringify(constraints));
if (constraints && constraints.audio) {
constraints.audio = constraintsToChrome_(constraints.audio);
}
if (constraints && typeof constraints.video === 'object') {
// Shim facingMode for mobile, where it defaults to "user".
var face = constraints.video.facingMode;
face = face && ((typeof face === 'object') ? face : {ideal: face});
if ((face && (face.exact === 'user' || face.exact === 'environment' ||
face.ideal === 'user' || face.ideal === 'environment')) &&
!(navigator.mediaDevices.getSupportedConstraints &&
navigator.mediaDevices.getSupportedConstraints().facingMode)) {
delete constraints.video.facingMode;
if (face.exact === 'environment' || face.ideal === 'environment') {
// Look for "back" in label, or use last cam (typically back cam).
return navigator.mediaDevices.enumerateDevices()
.then(function(devices) {
devices = devices.filter(function(d) {
return d.kind === 'videoinput';
});
var back = devices.find(function(d) {
return d.label.toLowerCase().indexOf('back') !== -1;
}) || (devices.length && devices[devices.length - 1]);
if (back) {
constraints.video.deviceId = face.exact ? {exact: back.deviceId} :
{ideal: back.deviceId};
}
constraints.video = constraintsToChrome_(constraints.video);
logging('chrome: ' + JSON.stringify(constraints));
return func(constraints);
});
}
}
constraints.video = constraintsToChrome_(constraints.video);
}
logging('chrome: ' + JSON.stringify(constraints));
return func(constraints);
};
var shimError_ = function(e) {
return {
name: {
PermissionDeniedError: 'NotAllowedError',
ConstraintNotSatisfiedError: 'OverconstrainedError'
}[e.name] || e.name,
message: e.message,
constraint: e.constraintName,
toString: function() {
return this.name + (this.message && ': ') + this.message;
}
};
};
var getUserMedia_ = function(constraints, onSuccess, onError) {
shimConstraints_(constraints, function(c) {
navigator.webkitGetUserMedia(c, onSuccess, function(e) {
onError(shimError_(e));
});
});
};
navigator.getUserMedia = getUserMedia_;
// Returns the result of getUserMedia as a Promise.
var getUserMediaPromise_ = function(constraints) {
return new Promise(function(resolve, reject) {
navigator.getUserMedia(constraints, resolve, reject);
});
};
if (!navigator.mediaDevices) {
navigator.mediaDevices = {
getUserMedia: getUserMediaPromise_,
enumerateDevices: function() {
return new Promise(function(resolve) {
var kinds = {audio: 'audioinput', video: 'videoinput'};
return MediaStreamTrack.getSources(function(devices) {
resolve(devices.map(function(device) {
return {label: device.label,
kind: kinds[device.kind],
deviceId: device.id,
groupId: ''};
}));
});
});
}
};
}
// A shim for getUserMedia method on the mediaDevices object.
// TODO(KaptenJansson) remove once implemented in Chrome stable.
if (!navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia = function(constraints) {
return getUserMediaPromise_(constraints);
};
} else {
// Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
// function which returns a Promise, it does not accept spec-style
// constraints.
var origGetUserMedia = navigator.mediaDevices.getUserMedia.
bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = function(cs) {
return shimConstraints_(cs, function(c) {
return origGetUserMedia(c).then(function(stream) {
if (c.audio && !stream.getAudioTracks().length ||
c.video && !stream.getVideoTracks().length) {
stream.getTracks().forEach(function(track) {
track.stop();
});
throw new DOMException('', 'NotFoundError');
}
return stream;
}, function(e) {
return Promise.reject(shimError_(e));
});
});
};
}
// Dummy devicechange event methods.
// TODO(KaptenJansson) remove once implemented in Chrome stable.
if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
navigator.mediaDevices.addEventListener = function() {
logging('Dummy mediaDevices.addEventListener called.');
};
}
if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
navigator.mediaDevices.removeEventListener = function() {
logging('Dummy mediaDevices.removeEventListener called.');
};
}
};
},{"../utils.js":10}],5:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var SDPUtils = require('sdp');
var browserDetails = require('../utils').browserDetails;
var edgeShim = {
shimPeerConnection: function() {
if (window.RTCIceGatherer) {
// ORTC defines an RTCIceCandidate object but no constructor.
// Not implemented in Edge.
if (!window.RTCIceCandidate) {
window.RTCIceCandidate = function(args) {
return args;
};
}
// ORTC does not have a session description object but
// other browsers (i.e. Chrome) that will support both PC and ORTC
// in the future might have this defined already.
if (!window.RTCSessionDescription) {
window.RTCSessionDescription = function(args) {
return args;
};
}
// this adds an additional event listener to MediaStrackTrack that signals
// when a tracks enabled property was changed.
var origMSTEnabled = Object.getOwnPropertyDescriptor(
MediaStreamTrack.prototype, 'enabled');
Object.defineProperty(MediaStreamTrack.prototype, 'enabled', {
set: function(value) {
origMSTEnabled.set.call(this, value);
var ev = new Event('enabled');
ev.enabled = value;
this.dispatchEvent(ev);
}
});
}
window.RTCPeerConnection = function(config) {
var self = this;
var _eventTarget = document.createDocumentFragment();
['addEventListener', 'removeEventListener', 'dispatchEvent']
.forEach(function(method) {
self[method] = _eventTarget[method].bind(_eventTarget);
});
this.onicecandidate = null;
this.onaddstream = null;
this.ontrack = null;
this.onremovestream = null;
this.onsignalingstatechange = null;
this.oniceconnectionstatechange = null;
this.onnegotiationneeded = null;
this.ondatachannel = null;
this.localStreams = [];
this.remoteStreams = [];
this.getLocalStreams = function() {
return self.localStreams;
};
this.getRemoteStreams = function() {
return self.remoteStreams;
};
this.localDescription = new RTCSessionDescription({
type: '',
sdp: ''
});
this.remoteDescription = new RTCSessionDescription({
type: '',
sdp: ''
});
this.signalingState = 'stable';
this.iceConnectionState = 'new';
this.iceGatheringState = 'new';
this.iceOptions = {
gatherPolicy: 'all',
iceServers: []
};
if (config && config.iceTransportPolicy) {
switch (config.iceTransportPolicy) {
case 'all':
case 'relay':
this.iceOptions.gatherPolicy = config.iceTransportPolicy;
break;
case 'none':
// FIXME: remove once implementation and spec have added this.
throw new TypeError('iceTransportPolicy "none" not supported');
default:
// don't set iceTransportPolicy.
break;
}
}
this.usingBundle = config && config.bundlePolicy === 'max-bundle';
if (config && config.iceServers) {
// Edge does not like
// 1) stun:
// 2) turn: that does not have all of turn:host:port?transport=udp
// 3) turn: with ipv6 addresses
var iceServers = JSON.parse(JSON.stringify(config.iceServers));
this.iceOptions.iceServers = iceServers.filter(function(server) {
if (server && server.urls) {
var urls = server.urls;
if (typeof urls === 'string') {
urls = [urls];
}
urls = urls.filter(function(url) {
return (url.indexOf('turn:') === 0 &&
url.indexOf('transport=udp') !== -1 &&
url.indexOf('turn:[') === -1) ||
(url.indexOf('stun:') === 0 &&
browserDetails.version >= 14393);
})[0];
return !!urls;
}
return false;
});
}
this._config = config;
// per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
// everything that is needed to describe a SDP m-line.
this.transceivers = [];
// since the iceGatherer is currently created in createOffer but we
// must not emit candidates until after setLocalDescription we buffer
// them in this array.
this._localIceCandidatesBuffer = [];
};
window.RTCPeerConnection.prototype._emitBufferedCandidates = function() {
var self = this;
var sections = SDPUtils.splitSections(self.localDescription.sdp);
// FIXME: need to apply ice candidates in a way which is async but
// in-order
this._localIceCandidatesBuffer.forEach(function(event) {
var end = !event.candidate || Object.keys(event.candidate).length === 0;
if (end) {
for (var j = 1; j < sections.length; j++) {
if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) {
sections[j] += 'a=end-of-candidates\r\n';
}
}
} else {
sections[event.candidate.sdpMLineIndex + 1] +=
'a=' + event.candidate.candidate + '\r\n';
}
self.localDescription.sdp = sections.join('');
self.dispatchEvent(event);
if (self.onicecandidate !== null) {
self.onicecandidate(event);
}
if (!event.candidate && self.iceGatheringState !== 'complete') {
var complete = self.transceivers.every(function(transceiver) {
return transceiver.iceGatherer &&
transceiver.iceGatherer.state === 'completed';
});
if (complete) {
self.iceGatheringState = 'complete';
}
}
});
this._localIceCandidatesBuffer = [];
};
window.RTCPeerConnection.prototype.getConfiguration = function() {
return this._config;
};
window.RTCPeerConnection.prototype.addStream = function(stream) {
// Clone is necessary for local demos mostly, attaching directly
// to two different senders does not work (build 10547).
var clonedStream = stream.clone();
stream.getTracks().forEach(function(track, idx) {
var clonedTrack = clonedStream.getTracks()[idx];
track.addEventListener('enabled', function(event) {
clonedTrack.enabled = event.enabled;
});
});
this.localStreams.push(clonedStream);
this._maybeFireNegotiationNeeded();
};
window.RTCPeerConnection.prototype.removeStream = function(stream) {
var idx = this.localStreams.indexOf(stream);
if (idx > -1) {
this.localStreams.splice(idx, 1);
this._maybeFireNegotiationNeeded();
}
};
window.RTCPeerConnection.prototype.getSenders = function() {
return this.transceivers.filter(function(transceiver) {
return !!transceiver.rtpSender;
})
.map(function(transceiver) {
return transceiver.rtpSender;
});
};
window.RTCPeerConnection.prototype.getReceivers = function() {
return this.transceivers.filter(function(transceiver) {
return !!transceiver.rtpReceiver;
})
.map(function(transceiver) {
return transceiver.rtpReceiver;
});
};
// Determines the intersection of local and remote capabilities.
window.RTCPeerConnection.prototype._getCommonCapabilities =
function(localCapabilities, remoteCapabilities) {
var commonCapabilities = {
codecs: [],
headerExtensions: [],
fecMechanisms: []
};
localCapabilities.codecs.forEach(function(lCodec) {
for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
var rCodec = remoteCapabilities.codecs[i];
if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
lCodec.clockRate === rCodec.clockRate) {
// number of channels is the highest common number of channels
rCodec.numChannels = Math.min(lCodec.numChannels,
rCodec.numChannels);
// push rCodec so we reply with offerer payload type
commonCapabilities.codecs.push(rCodec);
// determine common feedback mechanisms
rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) {
for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
if (lCodec.rtcpFeedback[j].type === fb.type &&
lCodec.rtcpFeedback[j].parameter === fb.parameter) {
return true;
}
}
return false;
});
// FIXME: also need to determine .parameters
// see https://github.com/openpeer/ortc/issues/569
break;
}
}
});
localCapabilities.headerExtensions
.forEach(function(lHeaderExtension) {
for (var i = 0; i < remoteCapabilities.headerExtensions.length;
i++) {
var rHeaderExtension = remoteCapabilities.headerExtensions[i];
if (lHeaderExtension.uri === rHeaderExtension.uri) {
commonCapabilities.headerExtensions.push(rHeaderExtension);
break;
}
}
});
// FIXME: fecMechanisms
return commonCapabilities;
};
// Create ICE gatherer, ICE transport and DTLS transport.
window.RTCPeerConnection.prototype._createIceAndDtlsTransports =
function(mid, sdpMLineIndex) {
var self = this;
var iceGatherer = new RTCIceGatherer(self.iceOptions);
var iceTransport = new RTCIceTransport(iceGatherer);
iceGatherer.onlocalcandidate = function(evt) {
var event = new Event('icecandidate');
event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
var cand = evt.candidate;
var end = !cand || Object.keys(cand).length === 0;
// Edge emits an empty object for RTCIceCandidateComplete‥
if (end) {
// polyfill since RTCIceGatherer.state is not implemented in
// Edge 10547 yet.
if (iceGatherer.state === undefined) {
iceGatherer.state = 'completed';
}
} else {
// RTCIceCandidate doesn't have a component, needs to be added
cand.component = iceTransport.component === 'RTCP' ? 2 : 1;
event.candidate.candidate = SDPUtils.writeCandidate(cand);
}
// update local description.
var sections = SDPUtils.splitSections(self.localDescription.sdp);
if (!end) {
sections[event.candidate.sdpMLineIndex + 1] +=
'a=' + event.candidate.candidate + '\r\n';
} else {
sections[event.candidate.sdpMLineIndex + 1] +=
'a=end-of-candidates\r\n';
}
self.localDescription.sdp = sections.join('');
var transceivers = self._pendingOffer ? self._pendingOffer :
self.transceivers;
var complete = transceivers.every(function(transceiver) {
return transceiver.iceGatherer &&
transceiver.iceGatherer.state === 'completed';
});
// Emit candidate if localDescription is set.
// Also emits null candidate when all gatherers are complete.
switch (self.iceGatheringState) {
case 'new':
if (!end) {
self._localIceCandidatesBuffer.push(event);
}
if (end && complete) {
self._localIceCandidatesBuffer.push(
new Event('icecandidate'));
}
break;
case 'gathering':
self._emitBufferedCandidates();
if (!end) {
self.dispatchEvent(event);
if (self.onicecandidate !== null) {
self.onicecandidate(event);
}
}
if (complete) {
self.dispatchEvent(new Event('icecandidate'));
if (self.onicecandidate !== null) {
self.onicecandidate(new Event('icecandidate'));
}
self.iceGatheringState = 'complete';
}
break;
case 'complete':
// should not happen... currently!
break;
default: // no-op.
break;
}
};
iceTransport.onicestatechange = function() {
self._updateConnectionState();
};
var dtlsTransport = new RTCDtlsTransport(iceTransport);
dtlsTransport.ondtlsstatechange = function() {
self._updateConnectionState();
};
dtlsTransport.onerror = function() {
// onerror does not set state to failed by itself.
dtlsTransport.state = 'failed';
self._updateConnectionState();
};
return {
iceGatherer: iceGatherer,
iceTransport: iceTransport,
dtlsTransport: dtlsTransport
};
};
// Start the RTP Sender and Receiver for a transceiver.
window.RTCPeerConnection.prototype._transceive = function(transceiver,
send, recv) {
var params = this._getCommonCapabilities(transceiver.localCapabilities,
transceiver.remoteCapabilities);
if (send && transceiver.rtpSender) {
params.encodings = transceiver.sendEncodingParameters;
params.rtcp = {
cname: SDPUtils.localCName
};
if (transceiver.recvEncodingParameters.length) {
params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
}
transceiver.rtpSender.send(params);
}
if (recv && transceiver.rtpReceiver) {
// remove RTX field in Edge 14942
if (transceiver.kind === 'video'
&& transceiver.recvEncodingParameters) {
transceiver.recvEncodingParameters.forEach(function(p) {
delete p.rtx;
});
}
params.encodings = transceiver.recvEncodingParameters;
params.rtcp = {
cname: transceiver.cname
};
if (transceiver.sendEncodingParameters.length) {
params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
}
transceiver.rtpReceiver.receive(params);
}
};
window.RTCPeerConnection.prototype.setLocalDescription =
function(description) {
var self = this;
var sections;
var sessionpart;
if (description.type === 'offer') {
// FIXME: What was the purpose of this empty if statement?
// if (!this._pendingOffer) {
// } else {
if (this._pendingOffer) {
// VERY limited support for SDP munging. Limited to:
// * changing the order of codecs
sections = SDPUtils.splitSections(description.sdp);
sessionpart = sections.shift();
sections.forEach(function(mediaSection, sdpMLineIndex) {
var caps = SDPUtils.parseRtpParameters(mediaSection);
self._pendingOffer[sdpMLineIndex].localCapabilities = caps;
});
this.transceivers = this._pendingOffer;
delete this._pendingOffer;
}
} else if (description.type === 'answer') {
sections = SDPUtils.splitSections(self.remoteDescription.sdp);
sessionpart = sections.shift();
var isIceLite = SDPUtils.matchPrefix(sessionpart,
'a=ice-lite').length > 0;
sections.forEach(function(mediaSection, sdpMLineIndex) {
var transceiver = self.transceivers[sdpMLineIndex];
var iceGatherer = transceiver.iceGatherer;
var iceTransport = transceiver.iceTransport;
var dtlsTransport = transceiver.dtlsTransport;
var localCapabilities = transceiver.localCapabilities;
var remoteCapabilities = transceiver.remoteCapabilities;
var rejected = mediaSection.split('\n', 1)[0]
.split(' ', 2)[1] === '0';
if (!rejected && !transceiver.isDatachannel) {
var remoteIceParameters = SDPUtils.getIceParameters(
mediaSection, sessionpart);
var remoteDtlsParameters = SDPUtils.getDtlsParameters(
mediaSection, sessionpart);
if (isIceLite) {
remoteDtlsParameters.role = 'server';
}
if (!self.usingBundle || sdpMLineIndex === 0) {
iceTransport.start(iceGatherer, remoteIceParameters,
isIceLite ? 'controlling' : 'controlled');
dtlsTransport.start(remoteDtlsParameters);
}
// Calculate intersection of capabilities.
var params = self._getCommonCapabilities(localCapabilities,
remoteCapabilities);
// Start the RTCRtpSender. The RTCRtpReceiver for this
// transceiver has already been started in setRemoteDescription.
self._transceive(transceiver,
params.codecs.length > 0,
false);
}
});
}
this.localDescription = {
type: description.type,
sdp: description.sdp
};
switch (description.type) {
case 'offer':
this._updateSignalingState('have-local-offer');
break;
case 'answer':
this._updateSignalingState('stable');
break;
default:
throw new TypeError('unsupported type "' + description.type +
'"');
}
// If a success callback was provided, emit ICE candidates after it
// has been executed. Otherwise, emit callback after the Promise is
// resolved.
var hasCallback = arguments.length > 1 &&
typeof arguments[1] === 'function';
if (hasCallback) {
var cb = arguments[1];
window.setTimeout(function() {
cb();
if (self.iceGatheringState === 'new') {
self.iceGatheringState = 'gathering';
}
self._emitBufferedCandidates();
}, 0);
}
var p = Promise.resolve();
p.then(function() {
if (!hasCallback) {
if (self.iceGatheringState === 'new') {
self.iceGatheringState = 'gathering';
}
// Usually candidates will be emitted earlier.
window.setTimeout(self._emitBufferedCandidates.bind(self), 500);
}
});
return p;
};
window.RTCPeerConnection.prototype.setRemoteDescription =
function(description) {
var self = this;
var stream = new MediaStream();
var receiverList = [];
var sections = SDPUtils.splitSections(description.sdp);
var sessionpart = sections.shift();
var isIceLite = SDPUtils.matchPrefix(sessionpart,
'a=ice-lite').length > 0;
this.usingBundle = SDPUtils.matchPrefix(sessionpart,
'a=group:BUNDLE ').length > 0;
sections.forEach(function(mediaSection, sdpMLineIndex) {
var lines = SDPUtils.splitLines(mediaSection);
var mline = lines[0].substr(2).split(' ');
var kind = mline[0];
var rejected = mline[1] === '0';
var direction = SDPUtils.getDirection(mediaSection, sessionpart);
var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:');
if (mid.length) {
mid = mid[0].substr(6);
} else {
mid = SDPUtils.generateIdentifier();
}
// Reject datachannels which are not implemented yet.
if (kind === 'application' && mline[2] === 'DTLS/SCTP') {
self.transceivers[sdpMLineIndex] = {
mid: mid,
isDatachannel: true
};
return;
}
var transceiver;
var iceGatherer;
var iceTransport;
var dtlsTransport;
var rtpSender;
var rtpReceiver;
var sendEncodingParameters;
var recvEncodingParameters;
var localCapabilities;
var track;
// FIXME: ensure the mediaSection has rtcp-mux set.
var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
var remoteIceParameters;
var remoteDtlsParameters;
if (!rejected) {
remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
sessionpart);
remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
sessionpart);
remoteDtlsParameters.role = 'client';
}
recvEncodingParameters =
SDPUtils.parseRtpEncodingParameters(mediaSection);
var cname;
// Gets the first SSRC. Note that with RTX there might be multiple
// SSRCs.
var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
.map(function(line) {
return SDPUtils.parseSsrcMedia(line);
})
.filter(function(obj) {
return obj.attribute === 'cname';
})[0];
if (remoteSsrc) {
cname = remoteSsrc.value;
}
var isComplete = SDPUtils.matchPrefix(mediaSection,
'a=end-of-candidates', sessionpart).length > 0;
var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
.map(function(cand) {
return SDPUtils.parseCandidate(cand);
})
.filter(function(cand) {
return cand.component === '1';
});
if (description.type === 'offer' && !rejected) {
var transports = self.usingBundle && sdpMLineIndex > 0 ? {
iceGatherer: self.transceivers[0].iceGatherer,
iceTransport: self.transceivers[0].iceTransport,
dtlsTransport: self.transceivers[0].dtlsTransport
} : self._createIceAndDtlsTransports(mid, sdpMLineIndex);
if (isComplete && (!self.usingBundle || sdpMLineIndex === 0)) {
transports.iceTransport.setRemoteCandidates(cands);
}
localCapabilities = RTCRtpReceiver.getCapabilities(kind);
// filter RTX until additional stuff needed for RTX is implemented
// in adapter.js
localCapabilities.codecs = localCapabilities.codecs.filter(
function(codec) {
return codec.name !== 'rtx';
});
sendEncodingParameters = [{
ssrc: (2 * sdpMLineIndex + 2) * 1001
}];
rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
track = rtpReceiver.track;
receiverList.push([track, rtpReceiver]);
// FIXME: not correct when there are multiple streams but that is
// not currently supported in this shim.
stream.addTrack(track);
// FIXME: look at direction.
if (self.localStreams.length > 0 &&
self.localStreams[0].getTracks().length >= sdpMLineIndex) {
var localTrack;
if (kind === 'audio') {
localTrack = self.localStreams[0].getAudioTracks()[0];
} else if (kind === 'video') {
localTrack = self.localStreams[0].getVideoTracks()[0];
}
if (localTrack) {
rtpSender = new RTCRtpSender(localTrack,
transports.dtlsTransport);
}
}
self.transceivers[sdpMLineIndex] = {
iceGatherer: transports.iceGatherer,
iceTransport: transports.iceTransport,
dtlsTransport: transports.dtlsTransport,
localCapabilities: localCapabilities,
remoteCapabilities: remoteCapabilities,
rtpSender: rtpSender,
rtpReceiver: rtpReceiver,
kind: kind,
mid: mid,
cname: cname,
sendEncodingParameters: sendEncodingParameters,
recvEncodingParameters: recvEncodingParameters
};
// Start the RTCRtpReceiver now. The RTPSender is started in
// setLocalDescription.
self._transceive(self.transceivers[sdpMLineIndex],
false,
direction === 'sendrecv' || direction === 'sendonly');
} else if (description.type === 'answer' && !rejected) {
transceiver = self.transceivers[sdpMLineIndex];
iceGatherer = transceiver.iceGatherer;
iceTransport = transceiver.iceTransport;
dtlsTransport = transceiver.dtlsTransport;
rtpSender = transceiver.rtpSender;
rtpReceiver = transceiver.rtpReceiver;
sendEncodingParameters = transceiver.sendEncodingParameters;
localCapabilities = transceiver.localCapabilities;
self.transceivers[sdpMLineIndex].recvEncodingParameters =
recvEncodingParameters;
self.transceivers[sdpMLineIndex].remoteCapabilities =
remoteCapabilities;
self.transceivers[sdpMLineIndex].cname = cname;
if ((isIceLite || isComplete) && cands.length) {
iceTransport.setRemoteCandidates(cands);
}
if (!self.usingBundle || sdpMLineIndex === 0) {
iceTransport.start(iceGatherer, remoteIceParameters,
'controlling');
dtlsTransport.start(remoteDtlsParameters);
}
self._transceive(transceiver,
direction === 'sendrecv' || direction === 'recvonly',
direction === 'sendrecv' || direction === 'sendonly');
if (rtpReceiver &&
(direction === 'sendrecv' || direction === 'sendonly')) {
track = rtpReceiver.track;
receiverList.push([track, rtpReceiver]);
stream.addTrack(track);
} else {
// FIXME: actually the receiver should be created later.
delete transceiver.rtpReceiver;
}
}
});
this.remoteDescription = {
type: description.type,
sdp: description.sdp
};
switch (description.type) {
case 'offer':
this._updateSignalingState('have-remote-offer');
break;
case 'answer':
this._updateSignalingState('stable');
break;
default:
throw new TypeError('unsupported type "' + description.type +
'"');
}
if (stream.getTracks().length) {
self.remoteStreams.push(stream);
window.setTimeout(function() {
var event = new Event('addstream');
event.stream = stream;
self.dispatchEvent(event);
if (self.onaddstream !== null) {
window.setTimeout(function() {
self.onaddstream(event);
}, 0);
}
receiverList.forEach(function(item) {
var track = item[0];
var receiver = item[1];
var trackEvent = new Event('track');
trackEvent.track = track;
trackEvent.receiver = receiver;
trackEvent.streams = [stream];
self.dispatchEvent(trackEvent);
if (self.ontrack !== null) {
window.setTimeout(function() {
self.ontrack(trackEvent);
}, 0);
}
});
}, 0);
}
if (arguments.length > 1 && typeof arguments[1] === 'function') {
window.setTimeout(arguments[1], 0);
}
return Promise.resolve();
};
window.RTCPeerConnection.prototype.close = function() {
this.transceivers.forEach(function(transceiver) {
/* not yet
if (transceiver.iceGatherer) {
transceiver.iceGatherer.close();
}
*/
if (transceiver.iceTransport) {
transceiver.iceTransport.stop();
}
if (transceiver.dtlsTransport) {
transceiver.dtlsTransport.stop();
}
if (transceiver.rtpSender) {
transceiver.rtpSender.stop();
}
if (transceiver.rtpReceiver) {
transceiver.rtpReceiver.stop();
}
});
// FIXME: clean up tracks, local streams, remote streams, etc
this._updateSignalingState('closed');
};
// Update the signaling state.
window.RTCPeerConnection.prototype._updateSignalingState =
function(newState) {
this.signalingState = newState;
var event = new Event('signalingstatechange');
this.dispatchEvent(event);
if (this.onsignalingstatechange !== null) {
this.onsignalingstatechange(event);
}
};
// Determine whether to fire the negotiationneeded event.
window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded =
function() {
// Fire away (for now).
var event = new Event('negotiationneeded');
this.dispatchEvent(event);
if (this.onnegotiationneeded !== null) {
this.onnegotiationneeded(event);
}
};
// Update the connection state.
window.RTCPeerConnection.prototype._updateConnectionState = function() {
var self = this;
var newState;
var states = {
'new': 0,
closed: 0,
connecting: 0,
checking: 0,
connected: 0,
completed: 0,
failed: 0
};
this.transceivers.forEach(function(transceiver) {
states[transceiver.iceTransport.state]++;
states[transceiver.dtlsTransport.state]++;
});
// ICETransport.completed and connected are the same for this purpose.
states.connected += states.completed;
newState = 'new';
if (states.failed > 0) {
newState = 'failed';
} else if (states.connecting > 0 || states.checking > 0) {
newState = 'connecting';
} else if (states.disconnected > 0) {
newState = 'disconnected';
} else if (states.new > 0) {
newState = 'new';
} else if (states.connected > 0 || states.completed > 0) {
newState = 'connected';
}
if (newState !== self.iceConnectionState) {
self.iceConnectionState = newState;
var event = new Event('iceconnectionstatechange');
this.dispatchEvent(event);
if (this.oniceconnectionstatechange !== null) {
this.oniceconnectionstatechange(event);
}
}
};
window.RTCPeerConnection.prototype.createOffer = function() {
var self = this;
if (this._pendingOffer) {
throw new Error('createOffer called while there is a pending offer.');
}
var offerOptions;
if (arguments.length === 1 && typeof arguments[0] !== 'function') {
offerOptions = arguments[0];
} else if (arguments.length === 3) {
offerOptions = arguments[2];
}
var tracks = [];
var numAudioTracks = 0;
var numVideoTracks = 0;
// Default to sendrecv.
if (this.localStreams.length) {
numAudioTracks = this.localStreams[0].getAudioTracks().length;
numVideoTracks = this.localStreams[0].getVideoTracks().length;
}
// Determine number of audio and video tracks we need to send/recv.
if (offerOptions) {
// Reject Chrome legacy constraints.
if (offerOptions.mandatory || offerOptions.optional) {
throw new TypeError(
'Legacy mandatory/optional constraints not supported.');
}
if (offerOptions.offerToReceiveAudio !== undefined) {
numAudioTracks = offerOptions.offerToReceiveAudio;
}
if (offerOptions.offerToReceiveVideo !== undefined) {
numVideoTracks = offerOptions.offerToReceiveVideo;
}
}
if (this.localStreams.length) {
// Push local streams.
this.localStreams[0].getTracks().forEach(function(track) {
tracks.push({
kind: track.kind,
track: track,
wantReceive: track.kind === 'audio' ?
numAudioTracks > 0 : numVideoTracks > 0
});
if (track.kind === 'audio') {
numAudioTracks--;
} else if (track.kind === 'video') {
numVideoTracks--;
}
});
}
// Create M-lines for recvonly streams.
while (numAudioTracks > 0 || numVideoTracks > 0) {
if (numAudioTracks > 0) {
tracks.push({
kind: 'audio',
wantReceive: true
});
numAudioTracks--;
}
if (numVideoTracks > 0) {
tracks.push({
kind: 'video',
wantReceive: true
});
numVideoTracks--;
}
}
var sdp = SDPUtils.writeSessionBoilerplate();
var transceivers = [];
tracks.forEach(function(mline, sdpMLineIndex) {
// For each track, create an ice gatherer, ice transport,
// dtls transport, potentially rtpsender and rtpreceiver.
var track = mline.track;
var kind = mline.kind;
var mid = SDPUtils.generateIdentifier();
var transports = self.usingBundle && sdpMLineIndex > 0 ? {
iceGatherer: transceivers[0].iceGatherer,
iceTransport: transceivers[0].iceTransport,
dtlsTransport: transceivers[0].dtlsTransport
} : self._createIceAndDtlsTransports(mid, sdpMLineIndex);
var localCapabilities = RTCRtpSender.getCapabilities(kind);
// filter RTX until additional stuff needed for RTX is implemented
// in adapter.js
localCapabilities.codecs = localCapabilities.codecs.filter(
function(codec) {
return codec.name !== 'rtx';
});
localCapabilities.codecs.forEach(function(codec) {
// work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
// by adding level-asymmetry-allowed=1
if (codec.name === 'H264' &&
codec.parameters['level-asymmetry-allowed'] === undefined) {
codec.parameters['level-asymmetry-allowed'] = '1';
}
});
var rtpSender;
var rtpReceiver;
// generate an ssrc now, to be used later in rtpSender.send
var sendEncodingParameters = [{
ssrc: (2 * sdpMLineIndex + 1) * 1001
}];
if (track) {
rtpSender = new RTCRtpSender(track, transports.dtlsTransport);
}
if (mline.wantReceive) {
rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
}
transceivers[sdpMLineIndex] = {
iceGatherer: transports.iceGatherer,
iceTransport: transports.iceTransport,
dtlsTransport: transports.dtlsTransport,
localCapabilities: localCapabilities,
remoteCapabilities: null,
rtpSender: rtpSender,
rtpReceiver: rtpReceiver,
kind: kind,
mid: mid,
sendEncodingParameters: sendEncodingParameters,
recvEncodingParameters: null
};
});
if (this.usingBundle) {
sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) {
return t.mid;
}).join(' ') + '\r\n';
}
tracks.forEach(function(mline, sdpMLineIndex) {
var transceiver = transceivers[sdpMLineIndex];
sdp += SDPUtils.writeMediaSection(transceiver,
transceiver.localCapabilities, 'offer', self.localStreams[0]);
});
this._pendingOffer = transceivers;
var desc = new RTCSessionDescription({
type: 'offer',
sdp: sdp
});
if (arguments.length && typeof arguments[0] === 'function') {
window.setTimeout(arguments[0], 0, desc);
}
return Promise.resolve(desc);
};
window.RTCPeerConnection.prototype.createAnswer = function() {
var self = this;
var sdp = SDPUtils.writeSessionBoilerplate();
if (this.usingBundle) {
sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
return t.mid;
}).join(' ') + '\r\n';
}
this.transceivers.forEach(function(transceiver) {
if (transceiver.isDatachannel) {
sdp += 'm=application 0 DTLS/SCTP 5000\r\n' +
'c=IN IP4 0.0.0.0\r\n' +
'a=mid:' + transceiver.mid + '\r\n';
return;
}
// Calculate intersection of capabilities.
var commonCapabilities = self._getCommonCapabilities(
transceiver.localCapabilities,
transceiver.remoteCapabilities);
sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities,
'answer', self.localStreams[0]);
});
var desc = new RTCSessionDescription({
type: 'answer',
sdp: sdp
});
if (arguments.length && typeof arguments[0] === 'function') {
window.setTimeout(arguments[0], 0, desc);
}
return Promise.resolve(desc);
};
window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
if (!candidate) {
for (var j = 0; j < this.transceivers.length; j++) {
this.transceivers[j].iceTransport.addRemoteCandidate({});
if (this.usingBundle) {
return;
}
}
} else {
var mLineIndex = candidate.sdpMLineIndex;
if (candidate.sdpMid) {
for (var i = 0; i < this.transceivers.length; i++) {
if (this.transceivers[i].mid === candidate.sdpMid) {
mLineIndex = i;
break;
}
}
}
var transceiver = this.transceivers[mLineIndex];
if (transceiver) {
var cand = Object.keys(candidate.candidate).length > 0 ?
SDPUtils.parseCandidate(candidate.candidate) : {};
// Ignore Chrome's invalid candidates since Edge does not like them.
if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {
return;
}
// Ignore RTCP candidates, we assume RTCP-MUX.
if (cand.component !== '1') {
return;
}
transceiver.iceTransport.addRemoteCandidate(cand);
// update the remoteDescription.
var sections = SDPUtils.splitSections(this.remoteDescription.sdp);
sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim()
: 'a=end-of-candidates') + '\r\n';
this.remoteDescription.sdp = sections.join('');
}
}
if (arguments.length > 1 && typeof arguments[1] === 'function') {
window.setTimeout(arguments[1], 0);
}
return Promise.resolve();
};
window.RTCPeerConnection.prototype.getStats = function() {
var promises = [];
this.transceivers.forEach(function(transceiver) {
['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
'dtlsTransport'].forEach(function(method) {
if (transceiver[method]) {
promises.push(transceiver[method].getStats());
}
});
});
var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
arguments[1];
var fixStatsType = function(stat) {
stat.type = {
inboundrtp: 'inbound-rtp',
outboundrtp: 'outbound-rtp',
candidatepair: 'candidate-pair',
localcandidate: 'local-candidate',
remotecandidate: 'remote-candidate'
}[stat.type] || stat.type;
return stat;
};
return new Promise(function(resolve) {
// shim getStats with maplike support
var results = new Map();
Promise.all(promises).then(function(res) {
res.forEach(function(result) {
Object.keys(result).forEach(function(id) {
result[id].type = fixStatsType(result[id]);
results.set(id, result[id]);
});
});
if (cb) {
window.setTimeout(cb, 0, results);
}
resolve(results);
});
});
};
}
};
// Expose public methods.
module.exports = {
shimPeerConnection: edgeShim.shimPeerConnection,
shimGetUserMedia: require('./getusermedia')
};
},{"../utils":10,"./getusermedia":6,"sdp":1}],6:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
// Expose public methods.
module.exports = function() {
var shimError_ = function(e) {
return {
name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name,
message: e.message,
constraint: e.constraint,
toString: function() {
return this.name;
}
};
};
// getUserMedia error shim.
var origGetUserMedia = navigator.mediaDevices.getUserMedia.
bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = function(c) {
return origGetUserMedia(c).catch(function(e) {
return Promise.reject(shimError_(e));
});
};
};
},{}],7:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var browserDetails = require('../utils').browserDetails;
var firefoxShim = {
shimOnTrack: function() {
if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
window.RTCPeerConnection.prototype)) {
Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
get: function() {
return this._ontrack;
},
set: function(f) {
if (this._ontrack) {
this.removeEventListener('track', this._ontrack);
this.removeEventListener('addstream', this._ontrackpoly);
}
this.addEventListener('track', this._ontrack = f);
this.addEventListener('addstream', this._ontrackpoly = function(e) {
e.stream.getTracks().forEach(function(track) {
var event = new Event('track');
event.track = track;
event.receiver = {track: track};
event.streams = [e.stream];
this.dispatchEvent(event);
}.bind(this));
}.bind(this));
}
});
}
},
shimSourceObject: function() {
// Firefox has supported mozSrcObject since FF22, unprefixed in 42.
if (typeof window === 'object') {
if (window.HTMLMediaElement &&
!('srcObject' in window.HTMLMediaElement.prototype)) {
// Shim the srcObject property, once, when HTMLMediaElement is found.
Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
get: function() {
return this.mozSrcObject;
},
set: function(stream) {
this.mozSrcObject = stream;
}
});
}
}
},
shimPeerConnection: function() {
if (typeof window !== 'object' || !(window.RTCPeerConnection ||
window.mozRTCPeerConnection)) {
return; // probably media.peerconnection.enabled=false in about:config
}
// The RTCPeerConnection object.
if (!window.RTCPeerConnection) {
window.RTCPeerConnection = function(pcConfig, pcConstraints) {
if (browserDetails.version < 38) {
// .urls is not supported in FF < 38.
// create RTCIceServers with a single url.
if (pcConfig && pcConfig.iceServers) {
var newIceServers = [];
for (var i = 0; i < pcConfig.iceServers.length; i++) {
var server = pcConfig.iceServers[i];
if (server.hasOwnProperty('urls')) {
for (var j = 0; j < server.urls.length; j++) {
var newServer = {
url: server.urls[j]
};
if (server.urls[j].indexOf('turn') === 0) {
newServer.username = server.username;
newServer.credential = server.credential;
}
newIceServers.push(newServer);
}
} else {
newIceServers.push(pcConfig.iceServers[i]);
}
}
pcConfig.iceServers = newIceServers;
}
}
return new mozRTCPeerConnection(pcConfig, pcConstraints);
};
window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype;
// wrap static methods. Currently just generateCertificate.
if (mozRTCPeerConnection.generateCertificate) {
Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
get: function() {
return mozRTCPeerConnection.generateCertificate;
}
});
}
window.RTCSessionDescription = mozRTCSessionDescription;
window.RTCIceCandidate = mozRTCIceCandidate;
}
// shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
.forEach(function(method) {
var nativeMethod = RTCPeerConnection.prototype[method];
RTCPeerConnection.prototype[method] = function() {
arguments[0] = new ((method === 'addIceCandidate') ?
RTCIceCandidate : RTCSessionDescription)(arguments[0]);
return nativeMethod.apply(this, arguments);
};
});
// support for addIceCandidate(null or undefined)
var nativeAddIceCandidate =
RTCPeerConnection.prototype.addIceCandidate;
RTCPeerConnection.prototype.addIceCandidate = function() {
if (!arguments[0]) {
if (arguments[1]) {
arguments[1].apply(null);
}
return Promise.resolve();
}
return nativeAddIceCandidate.apply(this, arguments);
};
// shim getStats with maplike support
var makeMapStats = function(stats) {
var map = new Map();
Object.keys(stats).forEach(function(key) {
map.set(key, stats[key]);
map[key] = stats[key];
});
return map;
};
var modernStatsTypes = {
inboundrtp: 'inbound-rtp',
outboundrtp: 'outbound-rtp',
candidatepair: 'candidate-pair',
localcandidate: 'local-candidate',
remotecandidate: 'remote-candidate'
};
var nativeGetStats = RTCPeerConnection.prototype.getStats;
RTCPeerConnection.prototype.getStats = function(selector, onSucc, onErr) {
return nativeGetStats.apply(this, [selector || null])
.then(function(stats) {
if (browserDetails.version < 48) {
stats = makeMapStats(stats);
}
if (browserDetails.version < 53 && !onSucc) {
// Shim only promise getStats with spec-hyphens in type names
// Leave callback version alone; misc old uses of forEach before Map
stats.forEach(function(stat) {
stat.type = modernStatsTypes[stat.type] || stat.type;
});
}
return stats;
})
.then(onSucc, onErr);
};
}
};
// Expose public methods.
module.exports = {
shimOnTrack: firefoxShim.shimOnTrack,
shimSourceObject: firefoxShim.shimSourceObject,
shimPeerConnection: firefoxShim.shimPeerConnection,
shimGetUserMedia: require('./getusermedia')
};
},{"../utils":10,"./getusermedia":8}],8:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var logging = require('../utils').log;
var browserDetails = require('../utils').browserDetails;
// Expose public methods.
module.exports = function() {
var shimError_ = function(e) {
return {
name: {
SecurityError: 'NotAllowedError',
PermissionDeniedError: 'NotAllowedError'
}[e.name] || e.name,
message: {
'The operation is insecure.': 'The request is not allowed by the ' +
'user agent or the platform in the current context.'
}[e.message] || e.message,
constraint: e.constraint,
toString: function() {
return this.name + (this.message && ': ') + this.message;
}
};
};
// getUserMedia constraints shim.
var getUserMedia_ = function(constraints, onSuccess, onError) {
var constraintsToFF37_ = function(c) {
if (typeof c !== 'object' || c.require) {
return c;
}
var require = [];
Object.keys(c).forEach(function(key) {
if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
return;
}
var r = c[key] = (typeof c[key] === 'object') ?
c[key] : {ideal: c[key]};
if (r.min !== undefined ||
r.max !== undefined || r.exact !== undefined) {
require.push(key);
}
if (r.exact !== undefined) {
if (typeof r.exact === 'number') {
r. min = r.max = r.exact;
} else {
c[key] = r.exact;
}
delete r.exact;
}
if (r.ideal !== undefined) {
c.advanced = c.advanced || [];
var oc = {};
if (typeof r.ideal === 'number') {
oc[key] = {min: r.ideal, max: r.ideal};
} else {
oc[key] = r.ideal;
}
c.advanced.push(oc);
delete r.ideal;
if (!Object.keys(r).length) {
delete c[key];
}
}
});
if (require.length) {
c.require = require;
}
return c;
};
constraints = JSON.parse(JSON.stringify(constraints));
if (browserDetails.version < 38) {
logging('spec: ' + JSON.stringify(constraints));
if (constraints.audio) {
constraints.audio = constraintsToFF37_(constraints.audio);
}
if (constraints.video) {
constraints.video = constraintsToFF37_(constraints.video);
}
logging('ff37: ' + JSON.stringify(constraints));
}
return navigator.mozGetUserMedia(constraints, onSuccess, function(e) {
onError(shimError_(e));
});
};
// Returns the result of getUserMedia as a Promise.
var getUserMediaPromise_ = function(constraints) {
return new Promise(function(resolve, reject) {
getUserMedia_(constraints, resolve, reject);
});
};
// Shim for mediaDevices on older versions.
if (!navigator.mediaDevices) {
navigator.mediaDevices = {getUserMedia: getUserMediaPromise_,
addEventListener: function() { },
removeEventListener: function() { }
};
}
navigator.mediaDevices.enumerateDevices =
navigator.mediaDevices.enumerateDevices || function() {
return new Promise(function(resolve) {
var infos = [
{kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
{kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
];
resolve(infos);
});
};
if (browserDetails.version < 41) {
// Work around http://bugzil.la/1169665
var orgEnumerateDevices =
navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
navigator.mediaDevices.enumerateDevices = function() {
return orgEnumerateDevices().then(undefined, function(e) {
if (e.name === 'NotFoundError') {
return [];
}
throw e;
});
};
}
if (browserDetails.version < 49) {
var origGetUserMedia = navigator.mediaDevices.getUserMedia.
bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = function(c) {
return origGetUserMedia(c).then(function(stream) {
// Work around https://bugzil.la/802326
if (c.audio && !stream.getAudioTracks().length ||
c.video && !stream.getVideoTracks().length) {
stream.getTracks().forEach(function(track) {
track.stop();
});
throw new DOMException('The object can not be found here.',
'NotFoundError');
}
return stream;
}, function(e) {
return Promise.reject(shimError_(e));
});
};
}
navigator.getUserMedia = function(constraints, onSuccess, onError) {
if (browserDetails.version < 44) {
return getUserMedia_(constraints, onSuccess, onError);
}
// Replace Firefox 44+'s deprecation warning with unprefixed version.
console.warn('navigator.getUserMedia has been replaced by ' +
'navigator.mediaDevices.getUserMedia');
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
};
};
},{"../utils":10}],9:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
var safariShim = {
// TODO: DrAlex, should be here, double check against LayoutTests
// shimOnTrack: function() { },
// TODO: once the back-end for the mac port is done, add.
// TODO: check for webkitGTK+
// shimPeerConnection: function() { },
shimGetUserMedia: function() {
navigator.getUserMedia = navigator.webkitGetUserMedia;
}
};
// Expose public methods.
module.exports = {
shimGetUserMedia: safariShim.shimGetUserMedia
// TODO
// shimOnTrack: safariShim.shimOnTrack,
// shimPeerConnection: safariShim.shimPeerConnection
};
},{}],10:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var logDisabled_ = true;
// Utility methods.
var utils = {
disableLog: function(bool) {
if (typeof bool !== 'boolean') {
return new Error('Argument type: ' + typeof bool +
'. Please use a boolean.');
}
logDisabled_ = bool;
return (bool) ? 'adapter.js logging disabled' :
'adapter.js logging enabled';
},
log: function() {
if (typeof window === 'object') {
if (logDisabled_) {
return;
}
if (typeof console !== 'undefined' && typeof console.log === 'function') {
console.log.apply(console, arguments);
}
}
},
/**
* Extract browser version out of the provided user agent string.
*
* @param {!string} uastring userAgent string.
* @param {!string} expr Regular expression used as match criteria.
* @param {!number} pos position in the version string to be returned.
* @return {!number} browser version.
*/
extractVersion: function(uastring, expr, pos) {
var match = uastring.match(expr);
return match && match.length >= pos && parseInt(match[pos], 10);
},
/**
* Browser detector.
*
* @return {object} result containing browser and version
* properties.
*/
detectBrowser: function() {
// Returned result object.
var result = {};
result.browser = null;
result.version = null;
// Fail early if it's not a browser
if (typeof window === 'undefined' || !window.navigator) {
result.browser = 'Not a browser.';
return result;
}
// Firefox.
if (navigator.mozGetUserMedia) {
result.browser = 'firefox';
result.version = this.extractVersion(navigator.userAgent,
/Firefox\/([0-9]+)\./, 1);
// all webkit-based browsers
} else if (navigator.webkitGetUserMedia) {
// Chrome, Chromium, Webview, Opera, all use the chrome shim for now
if (window.webkitRTCPeerConnection) {
result.browser = 'chrome';
result.version = this.extractVersion(navigator.userAgent,
/Chrom(e|ium)\/([0-9]+)\./, 2);
// Safari or unknown webkit-based
// for the time being Safari has support for MediaStreams but not webRTC
} else {
// Safari UA substrings of interest for reference:
// - webkit version: AppleWebKit/602.1.25 (also used in Op,Cr)
// - safari UI version: Version/9.0.3 (unique to Safari)
// - safari UI webkit version: Safari/601.4.4 (also used in Op,Cr)
//
// if the webkit version and safari UI webkit versions are equals,
// ... this is a stable version.
//
// only the internal webkit version is important today to know if
// media streams are supported
//
if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) {
result.browser = 'safari';
result.version = this.extractVersion(navigator.userAgent,
/AppleWebKit\/([0-9]+)\./, 1);
// unknown webkit-based browser
} else {
result.browser = 'Unsupported webkit-based browser ' +
'with GUM support but no WebRTC support.';
return result;
}
}
// Edge.
} else if (navigator.mediaDevices &&
navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) {
result.browser = 'edge';
result.version = this.extractVersion(navigator.userAgent,
/Edge\/(\d+).(\d+)$/, 2);
// Default fallthrough: not supported.
} else {
result.browser = 'Not a supported browser.';
return result;
}
return result;
},
shimCreateObjectURL: function() {
var nativeCreateObjectURL = URL.createObjectURL.bind(URL);
var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL);
var streams = new Map(), newId = 0;
URL.createObjectURL = function(stream) {
if ('getTracks' in stream) {
var url = 'polyblob:' + (++newId);
streams.set(url, stream);
console.log('URL.createObjectURL(stream) is deprecated! ' +
'Use elem.srcObject = stream instead!');
return url;
}
return nativeCreateObjectURL(stream);
};
URL.revokeObjectURL = function(url) {
nativeRevokeObjectURL(url);
streams.delete(url);
};
var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,
'src');
Object.defineProperty(window.HTMLMediaElement.prototype, 'src', {
get: function() {
return dsc.get.apply(this);
},
set: function(url) {
this.srcObject = streams.get(url) || null;
return dsc.set.apply(this, [url]);
}
});
}
};
// Export.
module.exports = {
log: utils.log,
disableLog: utils.disableLog,
browserDetails: utils.detectBrowser(),
extractVersion: utils.extractVersion,
shimCreateObjectURL: utils.shimCreateObjectURL
};
},{}]},{},[2])(2)
});
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>adapter.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>adapter_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>WebRTC Adapter JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/**
* FastPriorityQueue.js : a fast heap-based priority queue in JavaScript.
* (c) the authors
* Licensed under the Apache License, Version 2.0.
*
* Speed-optimized heap-based priority queue for modern browsers and JavaScript engines.
*
* Usage :
Installation (in shell, if you use node):
$ npm install fastpriorityqueue
Running test program (in JavaScript):
// var FastPriorityQueue = require("fastpriorityqueue");// in node
var x = new FastPriorityQueue();
x.add(1);
x.add(0);
x.add(5);
x.add(4);
x.add(3);
x.peek(); // should return 0, leaves x unchanged
x.size; // should return 5, leaves x unchanged
while(!x.isEmpty()) {
console.log(x.poll());
} // will print 0 1 3 4 5
x.trim(); // (optional) optimizes memory usage
*/
"use strict";
var defaultcomparator = function (a, b) {
return a < b;
};
// the provided comparator function should take a, b and return *true* when a < b
function FastPriorityQueue(comparator) {
this.array = [];
this.size = 0;
this.compare = comparator || defaultcomparator;
}
// Add an element the the queue
// runs in O(log n) time
FastPriorityQueue.prototype.add = function (myval) {
var i = this.size;
this.array[this.size] = myval;
this.size += 1;
var p;
var ap;
while (i > 0) {
p = (i - 1) >> 1;
ap = this.array[p];
if (!this.compare(myval, ap)) {
break;
}
this.array[i] = ap;
i = p;
}
this.array[i] = myval;
};
// replace the content of the heap by provided array and "heapifies it"
FastPriorityQueue.prototype.heapify = function (arr) {
this.array = arr;
this.size = arr.length;
var i;
for (i = (this.size >> 1); i >= 0; i--) {
this._percolateDown(i);
}
};
// for internal use
FastPriorityQueue.prototype._percolateUp = function (i) {
var myval = this.array[i];
var p;
var ap;
while (i > 0) {
p = (i - 1) >> 1;
ap = this.array[p];
if (!this.compare(myval, ap)) {
break;
}
this.array[i] = ap;
i = p;
}
this.array[i] = myval;
};
// for internal use
FastPriorityQueue.prototype._percolateDown = function (i) {
var size = this.size;
var hsize = this.size >>> 1;
var ai = this.array[i];
var l;
var r;
var bestc;
while (i < hsize) {
l = (i << 1) + 1;
r = l + 1;
bestc = this.array[l];
if (r < size) {
if (this.compare(this.array[r], bestc)) {
l = r;
bestc = this.array[r];
}
}
if (!this.compare(bestc, ai)) {
break;
}
this.array[i] = bestc;
i = l;
}
this.array[i] = ai;
};
// Look at the top of the queue (a smallest element)
// executes in constant time
//
// This function assumes that the priority queue is
// not empty and the caller is resposible for the check.
// You can use an expression such as
// "isEmpty() ? undefined : peek()"
// if you expect to be calling peek on an empty priority queue.
//
FastPriorityQueue.prototype.peek = function () {
return this.array[0];
};
// remove the element on top of the heap (a smallest element)
// runs in logarithmic time
//
//
// This function assumes that the priority queue is
// not empty, and the caller is responsible for the check.
// You can use an expression such as
// "isEmpty() ? undefined : poll()"
// if you expect to be calling poll on an empty priority queue.
//
// For long-running and large priority queues, or priority queues
// storing large objects, you may want to call the trim function
// at strategic times to recover allocated memory.
FastPriorityQueue.prototype.poll = function () {
var ans = this.array[0];
if (this.size > 1) {
this.array[0] = this.array[--this.size];
this._percolateDown(0 | 0);
} else {
this.size -= 1;
}
return ans;
};
// recover unused memory (for long-running priority queues)
FastPriorityQueue.prototype.trim = function () {
this.array = this.array.slice(0, this.size);
};
// Check whether the heap is empty
FastPriorityQueue.prototype.isEmpty = function () {
return this.size === 0;
};
// just for illustration purposes
var main = function () {
// main code
var x = new FastPriorityQueue(function (a, b) {
return a < b;
});
x.add(1);
x.add(0);
x.add(5);
x.add(4);
x.add(3);
while (!x.isEmpty()) {
console.log(x.poll());
}
};
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>fast_priority_queue.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>fast_priority_queue_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Fast Priority Queue JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OfficeJS Chat Panel</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="gadget_erp5_chat_panel.js"></script>
</head>
<body>
<header class="panel-header">
<button>
<span class="fa fa-times" aria-hidden="true"></span>
</button>
<a href="#">
<img alt="ERP5 Logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJcAAAA/CAMAAADaDqrIAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowMEM5NUE4MzQ5NjQxMUUzOUZEQUU2NUY1RTI1RjdCQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowMEM5NUE4NDQ5NjQxMUUzOUZEQUU2NUY1RTI1RjdCQiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjAwQzk1QTgxNDk2NDExRTM5RkRBRTY1RjVFMjVGN0JCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjAwQzk1QTgyNDk2NDExRTM5RkRBRTY1RjVFMjVGN0JCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+J9MJsAAAAwBQTFRF///////M//+Z//9m//8z//8A/8z//8zM/8yZ/8xm/8wz/8wA/5n//5nM/5mZ/5lm/5kz/5kA/2b//2bM/2aZ/2Zm/2Yz/2YA/zP//zPM/zOZ/zNm/zMz/zMA/wD//wDM/wCZ/wBm/wAz/wAAzP//zP/MzP+ZzP9mzP8zzP8AzMz/zMzMzMyZzMxmzMwzzMwAzJn/zJnMzJmZzJlmzJkzzJkAzGb/zGbMzGaZzGZmzGYzzGYAzDP/zDPMzDOZzDNmzDMzzDMAzAD/zADMzACZzABmzAAzzAAAmf//mf/Mmf+Zmf9mmf8zmf8Amcz/mczMmcyZmcxmmcwzmcwAmZn/mZnMmZmZmZlmmZkzmZkAmWb/mWbMmWaZmWZmmWYzmWYAmTP/mTPMmTOZmTNmmTMzmTMAmQD/mQDMmQCZmQBmmQAzmQAAZv//Zv/MZv+ZZv9mZv8zZv8AZsz/ZszMZsyZZsxmZswzZswAZpn/ZpnMZpmZZplmZpkzZpkAZmb/ZmbMZmaZZmZmZmYzZmYAZjP/ZjPMZjOZZjNmZjMzZjMAZgD/ZgDMZgCZZgBmZgAzZgAAM///M//MM/+ZM/9mM/8zM/8AM8z/M8zMM8yZM8xmM8wzM8wAM5n/M5nMM5mZM5lmM5kzM5kAM2b/M2bMM2aZM2ZmM2YzM2YAMzP/MzPMMzOZMzNmMzMzMzMAMwD/MwDMMwCZMwBmMwAzMwAAAP//AP/MAP+ZAP9mAP8zAP8AAMz/AMzMAMyZAMxmAMwzAMwAAJn/AJnMAJmZAJlmAJkzAJkAAGb/AGbMAGaZAGZmAGYzAGYAADP/ADPMADOZADNmADMzADMAAAD/AADMAACZAABmAAAzAAAAHHa7K3/AOojESZHJWZvNaKTSd63Whrbblb/fpMjks9Howtrt4e320uTx8Pb6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdfKHSQAAAOh0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ALItoLoAAAJkSURBVHja7NlZsqsgEABQ979C5kGGddxo4os0Q8BAynoFn0nUU23TNGRB9xzLdE3XdE3XdP2fLnIzF2VSrsYvC72HizAh9eZ5DVuOl15S4/jWwC+kDC8HPzCSJVHcRY8QXV2PwQuujUYTsYrvYlBv1yKLrsXHsPghnvR3Lazoip/JKuwpF8sm6/bY01Ow9CBlTXi53PNoRcUktEZT1NV1PIPnXAjtMJIMlzerFIzU1dVWF7aPj3Tetae4gjdxktG2et/qQjJ4kZFrv6ED4bK4eR1qduHzvEi4+PbGwD0EGu9CQQLFrv1F4jBc6BcuW3YhkPiPX1hOfxQvnHfh0HVMRqc4HuqiQf5k3mO6dq1s8Hx0H/Le5kq9YaNcxAcrZLpOvAv+ClcgNcZFtnrucd5Fwx4mXj0drnbBYXMuJmFDAV3Uhm4qlbHh3ddOrmholHNxFTVC+xTl2mf6pI4uVe4LwSp0DPGOmh/hAnM94bKZjkHlWuiLeV+cTab4luMKk82wy/Px2fLJskvKUk03hRf9RZ0wiYiZ4uVwVwTb9E71y0XboTYXGuTaa33Y47W5zBjXM3P11y7TfR2ycFdxyaW7uwSc5m0un9ix91m3TWJfW+1ijfW+wcVAfjS5dOP62LyvZZdcLHnA1MkFAtbgwq+Vm47po8OA1buwyy/a1ec5urzveAes2sV8kfW963mFaHNhlT0b6+Yi5+StchH1CtaK0ThXELDPLiyOPZHldzm/J1z9O/B14g7/K+DtkP7U8ivSeD4xaPCTSQvy+YIfubZWzRgtRe1iMP+3mq7pmq7p6jv+BBgAPrgi/TzwWzkAAAAASUVORK5CYII=" />
</a>
</header>
<article class="panel-listview">
<ul>
<li>
<a class="panel-element" href="#!display?n.page=contact_list">
<span class="fa fa-home" aria-hidden="true"></span>
Contact List
</a>
</li>
<li>
<a class="panel-element" href="#!display?n.page=chat_box">
<span class="fa fa-commenting-o" aria-hidden="true"></span>
Chat Box
</a>
</li>
<li>
<a class="panel-element" href="#!display?n.page=jio_configurator">
<span class="fa fa-exchange" aria-hidden="true"></span>
Storage Configuration
</a>
</li>
<li>
<a class="panel-element" href="#!display?n.page=sync">
<span class="fa fa-cogs" aria-hidden="true"></span>
Synchronization
</a>
</li>
</ul>
</article>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
<string>contributor/person_module/1</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_chat_panel.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_chat_panel_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Chat Panel</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/* global window, RSVP, rJS */
/* jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
(function (window, RSVP, rJS) {
'use strict';
rJS(window)
.setState({
// Boolean to indicate whether the panel is shown on the screen
visible: false,
})
.declareMethod('render', function () {})
// Toggle the panel between shown and hidden
.declareMethod('toggle', function () {
const gadget = this;
return gadget.changeState({visible: !gadget.state.visible});
})
// Collapse the panel into being hidden
.declareMethod('close', function () {
const gadget = this;
return gadget.changeState({visible: false});
})
// Handle panel visibility changes
.onStateChange(function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
if (gadget.state.visible &&
!gadget.element.classList.contains('visible')) {
gadget.element.classList.toggle('visible');
} else if (!gadget.state.visible &&
gadget.element.classList.contains('visible')) {
gadget.element.classList.remove('visible');
}
return;
});
})
/* Collapse the panel whenever any button is clicked,
* since there should be only one button, close;
* set all parameters to false to click other links
*/
.onEvent('click', function (event) {
const gadget = this;
if (event.target.tagName === 'BUTTON') {
return gadget.close();
} else {
return;
}
}, false, false);
}(window, RSVP, rJS));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
<string>contributor/person_module/1</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_chat_panel.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_chat_panel_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Chat Panel JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>>WebRTC Gadget</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="adapter.js"></script>
<script src="gadget_global.js"></script>
<script src="gadget_erp5_chat_webrtc.js"></script>
</head>
<body>
<p class="error"></p>
<p class="status"></p>
<form class="contact-form">
<label>E-mail Address:</label>
<input type="text" name="email" />
<label>Dropbox folder:</label>
<input type="text" name="dropbox_folder" placeholder="/Apps/OfficeJS Chat/" />
<label>ERP5 URL:</label>
<input type="text" name="erp5_url" placeholder="https://softinst75770.host.vifib.net/erp5/web_site_module/" />
<br />
<input type="submit" value="Update information!" />
</form>
<form class="download-form">
<input type="submit" value="Download information from remote storage!" />
</form>
<form class="auth-form">
<label>
<input type="radio" name="auth" value="erp5" required="required" />
ERP5
</label>
<label>
<input type="radio" name="auth" value="dropbox" required="required" />
Dropbox
</label>
<input type="submit" value="Authenticate!" />
</form>
<form class="host-offer-form">
<label>Paste your guest's offer in this box:</label>
<textarea rows="5" cols="80" name="send"></textarea>
<input type="submit" value="Paste it!" />
</form>
<form class="host-answer-form">
<p>This is your answer. Send it to your guest!</p>
<textarea rows="5" cols="80" name="receive" readonly></textarea>
</form>
<form class="guest-offer-form">
<p>This is your new offer. Send it to your host!</p>
<textarea rows="5" cols="80" name="receive" readonly></textarea>
<input type="submit" value="I sent it to my host." />
</form>
<form class="guest-answer-form">
<label>Now, paste your host's answer in this box:</label>
<textarea rows="5" cols="80" name="send"></textarea>
<input type="submit" value="Paste it!" />
</form>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
<string>contributor/person_module/1</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_chat_webrtc.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_chat_webrtc_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Chat WebRTC</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/* global window, document, RSVP, rJS */
/* jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
(function (window, document, RSVP, rJS) {
"use strict";
function logError(error) {
console.log(error);
}
function logQueue(action) {
return new RSVP.Queue()
.push(function () {
return action;
})
.push(null, logError);
}
function styleElementByQuery(gadget, query, style) {
gadget.element
.querySelector(query).style.display = style;
}
function resetInputValue(element) {
const value = element.value;
element.value = "";
return value;
}
function getQueryValue(query_list, query_string) {
for (let i = 0, i_len = query_list.length; i < i_len; i++) {
const query = query_list[i];
if (query_string.indexOf(query + "=") !== -1) {
const start = query_string.indexOf(query + "=") + query.length + 1;
let end = query_string.indexOf("&", start);
if (end === -1) {
end = query_string.length;
}
return query_string.slice(start, end);
}
}
return null;
}
function setQueryValue(query_list, query_string, element) {
const value = getQueryValue(query_list, query_string);
if (value) {
element.value = value;
}
}
function pollUntilNotNull(gadget, delay_ms, timeout_ms,
nullableFunction, callbackFunction) {
if (callbackFunction === undefined) {
callbackFunction = function () {};
}
return new RSVP.Queue()
.push(function () {
return nullableFunction();
})
.push(function (result) {
if (result !== null) {
return callbackFunction(result);
} else {
return RSVP.any([
RSVP.timeout(timeout_ms),
promiseDoWhile(function () {
return new RSVP.Queue()
.push(function () {
return RSVP.delay(delay_ms);
})
.push(function () {
return nullableFunction();
})
.push(function (result) {
if (result === null) {
return null;
} else {
return callbackFunction(result);
}
})
.push(function (nullable) {
return nullable === null;
})
.push(null, logError);
})
]);
}
});
}
function webrtcError(gadget, error_string) {
return new RSVP.Queue()
.push(function () {
gadget.element.querySelector(".error").textContent = error_string;
})
.push(function () {
return RSVP.delay(10000);
})
.push(function () {
return error_string;
});
}
// jIO utility functions
function createDropboxJio(gadget, param_dict) {
let dropbox_gadget;
return new RSVP.Queue()
.push(function () {
return gadget.declareGadget(
"gadget_jio.html", {scope: "dropbox_gadget"});
})
.push(function (jio_gadget) {
dropbox_gadget = jio_gadget;
const start = document.URL.indexOf("access_token=") + 13;
const end = document.URL.indexOf("&", start);
const token = document.URL.slice(start, end);
return dropbox_gadget.createJio({
type: "dropbox",
access_token: token,
root: "dropbox",
});
})
.push(function () {
return dropbox_gadget.put(param_dict.dropbox_folder, {});
})
.push(null, logError);
}
function resetDropboxContent(dropbox_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
return dropbox_gadget.allAttachments(param_dict.dropbox_folder);
})
.push(function (negotiation_list) {
const promise_list = [];
for (let file_name in negotiation_list) {
if (negotiation_list.hasOwnProperty(file_name)
&& (file_name.indexOf("offer_" + param_dict.room + "_") === 0
|| file_name.indexOf("answer_" + param_dict.room + "_") === 0)) {
promise_list.push(dropbox_gadget
.removeAttachment(param_dict.dropbox_folder, file_name));
}
}
return RSVP.all(promise_list);
})
.push(null, logError);
}
function getDropboxOffer(dropbox_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
return dropbox_gadget.allAttachments(param_dict.dropbox_folder);
})
.push(function (negotiation_list) {
for (let file_name in negotiation_list) {
if (negotiation_list.hasOwnProperty(file_name)
&& file_name.indexOf("offer_" + param_dict.room + "_") === 0) {
return file_name.slice(6);
}
}
return null;
})
.push(null, logError);
}
function getDropboxContent(dropbox_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
return dropbox_gadget.getAttachment(
param_dict.dropbox_folder, param_dict.file_name);
})
.push(function (attachment) {
return promiseReadAsText(attachment);
}, function () {
return null;
})
.push(null, logError);
}
function putDropboxContent(dropbox_gadget, param_dict) {
return logQueue(dropbox_gadget.putAttachment(
param_dict.dropbox_folder, param_dict.file_name,
new Blob([param_dict.content], {type: "text"})));
}
function removeDropboxContent(dropbox_gadget, param_dict) {
return logQueue(dropbox_gadget.removeAttachment(
param_dict.dropbox_folder, param_dict.file_name));
}
function authenticateDropbox(gadget) {
const dropbox_param_dict = {
gadget: gadget,
gadget_name: "dropbox_gadget",
room: gadget.state.login_dict.room,
name: gadget.state.login_dict.name,
file_name: null,
content: null,
dropbox_folder: gadget.state.dropbox_folder,
function_dict: {
resetContent: resetDropboxContent,
getOffer: getDropboxOffer,
getContent: getDropboxContent,
putContent: putDropboxContent,
removeContent: removeDropboxContent,
},
};
return new RSVP.Queue()
.push(function () {
if (document.URL.indexOf("access_token=") === -1) {
throw webrtcError(gadget, "Please log in to Dropbox!");
} else {
return createDropboxJio(gadget, dropbox_param_dict);
}
})
.push(function () {
return gadget.getDeclaredGadget("dropbox_gadget");
})
.push(function (dropbox_gadget) {
return resetDropboxContent(dropbox_gadget, dropbox_param_dict);
})
.push(function () {
if (gadget.state.login_dict.role === "host") {
return authenticateHost(dropbox_param_dict);
} else if (gadget.state.login_dict.role === "guest") {
return authenticateGuest(dropbox_param_dict);
}
})
.push(null, logError);
}
function createErp5Jio(gadget, param_dict) {
let erp5_gadget;
return new RSVP.Queue()
.push(function () {
return gadget.declareGadget(
"gadget_jio.html", {scope: "erp5_gadget"});
})
.push(function (jio_gadget) {
erp5_gadget = jio_gadget;
return erp5_gadget.createJio({
type: "erp5",
url: (new URI("hateoas")).absoluteTo(param_dict.erp5_url).toString(),
default_view_reference: "view",
});
})
.push(function () {
// Must use double quotation marks in query string!
return erp5_gadget.allDocs({
limit: [0, 1000000],
query: 'portal_type: "Webrtc Room"',
sort_on: [["last_modified", "descending"]],
select_list: ["title"],
});
})
.push(function (room_list) {
if (room_list.data.rows.length) {
for (let i = 0, i_len = room_list.data.rows.length; i < i_len; i++) {
if (room_list.data.rows[i].value.title === param_dict.room) {
return room_list.data.rows[i].id;
}
}
}
return erp5_gadget.post({
portal_type: "Webrtc Room",
parent_relative_url: "webrtc_rooms_module",
title: param_dict.room,
negotiation_list: JSON.stringify({}),
});
})
.push(null, logError);
}
function resetErp5Content(erp5_gadget, param_dict) {
return logQueue(erp5_gadget.put(param_dict.id, {
portal_type: "Webrtc Room",
parent_relative_url: "webrtc_rooms_module",
title: param_dict.room,
negotiation_list: JSON.stringify({}),
}));
}
function getErp5Offer(erp5_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
return erp5_gadget.get(param_dict.id);
})
.push(function (webrtc_room) {
const negotiation_list = JSON.parse(webrtc_room.negotiation_list);
for (let file_name in negotiation_list) {
if (negotiation_list.hasOwnProperty(file_name)
&& file_name.indexOf("offer_" + param_dict.room + "_") === 0) {
return file_name.slice(6);
}
}
return null;
})
.push(null, logError);
}
function getErp5Content(erp5_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
return erp5_gadget.get(param_dict.id);
})
.push(function (webrtc_room) {
const negotiation_list = JSON.parse(webrtc_room.negotiation_list);
if (negotiation_list[param_dict.file_name]) {
return negotiation_list[param_dict.file_name];
} else {
return null;
}
})
.push(null, logError);
}
function putErp5Content(erp5_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
return erp5_gadget.get(param_dict.id);
})
.push(function (webrtc_room) {
const negotiation_list = JSON.parse(webrtc_room.negotiation_list);
negotiation_list[param_dict.file_name] = param_dict.content;
return logQueue(erp5_gadget.put(param_dict.id, {
portal_type: "Webrtc Room",
parent_relative_url: "webrtc_rooms_module",
title: param_dict.room,
negotiation_list: JSON.stringify(negotiation_list),
}));
})
.push(null, logError);
}
function removeErp5Content(erp5_gadget, param_dict) {
return new RSVP.Queue()
.push(function () {
return erp5_gadget.get(param_dict.id);
})
.push(function (webrtc_room) {
const negotiation_list = JSON.parse(webrtc_room.negotiation_list);
delete negotiation_list[param_dict.file_name];
return logQueue(erp5_gadget.put(param_dict.id, {
portal_type: "Webrtc Room",
parent_relative_url: "webrtc_rooms_module",
title: param_dict.room,
negotiation_list: JSON.stringify(negotiation_list),
}));
})
.push(null, logError);
}
function authenticateErp5(gadget) {
const erp5_param_dict = {
gadget: gadget,
gadget_name: "erp5_gadget",
room: gadget.state.login_dict.room,
name: gadget.state.login_dict.name,
file_name: null,
content: null,
id: null,
erp5_url: gadget.state.erp5_url,
function_dict: {
resetContent: resetErp5Content,
getOffer: getErp5Offer,
getContent: getErp5Content,
putContent: putErp5Content,
removeContent: removeErp5Content,
},
};
return new RSVP.Queue()
.push(function () {
return createErp5Jio(gadget, erp5_param_dict);
})
.push(function (id) {
erp5_param_dict.id = id;
return gadget.getDeclaredGadget("erp5_gadget");
})
.push(function (erp5_gadget) {
return resetErp5Content(erp5_gadget, erp5_param_dict);
})
.push(function () {
if (gadget.state.login_dict.role === "host") {
return authenticateHost(erp5_param_dict);
} else if (gadget.state.login_dict.role === "guest") {
return authenticateGuest(erp5_param_dict);
}
})
.push(null, logError);
}
function authenticateHost(param_dict) {
const gadget = param_dict.gadget;
let file_name;
let jio_gadget;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget(param_dict.gadget_name);
})
.push(function (storage_gadget) {
jio_gadget = storage_gadget;
return pollUntilNotNull(gadget, 2000, 86400000, function () {
return param_dict.function_dict.getOffer(jio_gadget, param_dict);
}, function (offer_name) {
file_name = offer_name;
});
})
.push(function () {
return pollUntilNotNull(gadget, 500, 30000, function () {
if (gadget.state.offer_ready === true) {
gadget.state.offer_ready = false;
return true;
} else {
return null;
}
});
}, function () {
throw param_dict.function_dict.resetContent(jio_gadget, param_dict);
})
.push(function () {
param_dict.file_name = "offer_" + file_name;
return pollUntilNotNull(gadget, 500, 10000, function () {
return param_dict.function_dict.getContent(jio_gadget, param_dict);
}, function (guest_offer) {
return sendOffer(gadget, guest_offer);
});
}, function (error) {
if (error.toString().indexOf("Timed out after") === 0) {
throw createInitialOffer(gadget);
} else {
throw error;
}
})
.push(function () {
return pollUntilNotNull(gadget, 500, 10000, function () {
return gadget.state.candidate;
}, function (host_answer) {
gadget.state.candidate = null;
param_dict.file_name = "answer_" + file_name;
param_dict.content = host_answer;
return param_dict.function_dict.putContent(jio_gadget, param_dict);
});
})
.push(null, logError)
.push(function () {
if (file_name) {
param_dict.file_name = "offer_" + file_name;
return param_dict.function_dict.removeContent(jio_gadget, param_dict);
} else {
return;
}
})
.push(null, logError)
.push(function () {
return authenticateHost(param_dict);
})
}
function authenticateGuest(param_dict) {
const gadget = param_dict.gadget;
let file_name;
let jio_gadget;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget(param_dict.gadget_name);
})
.push(function (storage_gadget) {
jio_gadget = storage_gadget;
return pollUntilNotNull(gadget, 500, 30000, function () {
return gadget.state.candidate;
}, function (guest_offer) {
file_name = param_dict.room + "_" + param_dict.name + ".txt";
param_dict.file_name = "offer_" + file_name;
param_dict.content = guest_offer;
return param_dict.function_dict.putContent(jio_gadget, param_dict);
});
})
.push(function () {
return pollUntilNotNull(gadget, 500, 30000, function () {
param_dict.file_name = "answer_" + file_name;
return param_dict.function_dict.getContent(jio_gadget, param_dict);
}, function (host_answer) {
return sendAnswer(gadget, host_answer);
});
})
.push(function () {
// XXX pause to avoid data races, use locks later
return RSVP.delay(1000);
}, logError)
.push(function () {
if (file_name) {
param_dict.file_name = "answer_" + file_name;
return param_dict.function_dict.removeContent(jio_gadget, param_dict);
} else {
return;
}
})
.push(null, logError);
}
// WebRTC signalling functions
function createInitialOffer(gadget) {
const peer_list = gadget.state.peer_connection_list;
return new RSVP.Queue()
.push(function () {
return new RTCPeerConnection(
gadget.state.ice_config,
gadget.state.ice_constraints);
})
.push(function (peer_connection) {
if (gadget.state.login_dict.role === "host") {
gadget.element.querySelector(".host-offer-form textarea").value = "";
styleElementByQuery(gadget, ".host-answer-form", "none");
styleElementByQuery(gadget, ".host-offer-form", "block");
peer_list.push(peer_connection);
return setupPeerConnection(
gadget, peer_connection, ".host-answer-form textarea");
} else if (gadget.state.login_dict.role === "guest") {
peer_list[0] = peer_connection;
return setupPeerConnection(
gadget, peer_connection, ".guest-offer-form textarea");
}
})
.push(function () {
if (gadget.state.login_dict.role === "host") {
peer_list[peer_list.length - 1].ondatachannel =
function (event) {
setupDataChannel(gadget, event.channel);
gadget.state.data_channel_list.push(event.channel);
};
return;
} else if (gadget.state.login_dict.role === "guest") {
const peer_connection = peer_list[0];
return new RSVP.Queue()
.push(function () {
return peer_connection.createDataChannel(
gadget.state.login_dict.room + "_"
+ gadget.state.login_dict.name + "_"
+ gadget.state.data_channel_list.length,
gadget.state.dc_constraints);
})
.push(function (channel) {
gadget.state.data_channel_list.push(channel);
return setupDataChannel(gadget, channel);
})
.push(function () {
return peer_connection.createOffer(
function (offer_description) {
peer_connection.setLocalDescription(offer_description);
}, logError, gadget.state.sdp_constraints);
})
.push(null, logError);
}
})
.push(function () {
gadget.state.offer_ready = true;
return;
})
.push(null, logError);
}
function setupPeerConnection(gadget, peer_connection, form_selector) {
peer_connection.onicecandidate = function (event) {
const candidate = JSON.stringify(peer_connection.localDescription);
gadget.state.candidate = candidate;
gadget.element.querySelector(form_selector).value = candidate;
};
if (gadget.state.login_dict.role === "host") {
peer_connection.oniceconnectionstatechange = function () {
return new RSVP.Queue()
.push(function () {
if (peer_connection.iceConnectionState === "connected") {
gadget.state.guest_amount += 1;
} else if (peer_connection.iceConnectionState === "disconnected") {
gadget.state.guest_amount -= 1;
}
return;
})
};
} else if (gadget.state.login_dict.role === "guest") {
peer_connection.oniceconnectionstatechange = function () {
gadget.element.querySelector(".status").textContent =
"WebRTC connection status: "
+ peer_connection.iceConnectionState + ".";
if (peer_connection.iceConnectionState === "failed") {
throw webrtcError(gadget, "WebRTC connection failed!");
} else if (peer_connection.iceConnectionState === "failed") {
throw webrtcError(gadget, "WebRTC connection disconnected!");
}
};
}
}
function setupDataChannel(gadget, data_channel) {
if (!gadget.state.connected) {
data_channel.onopen = function () {
return new RSVP.Queue()
.push(function () {
gadget.state.connected = true;
if (gadget.state.login_dict.role === "host") {
return createInitialOffer(gadget);
} else if (
gadget.state.login_dict.role === "guest") {
styleElementByQuery(gadget, ".guest-offer-form", "none");
styleElementByQuery(gadget, ".auth-form", "none");
return;
}
})
.push(gadget.state.dataChannelOnopen)
.push(null, logError);
};
} else if (gadget.state.login_dict.role === "host") {
data_channel.onopen = function () {
return new RSVP.Queue()
.push(function () {
return createInitialOffer(gadget);
})
.push(gadget.state.dataChannelOnopen)
.push(null, logError);
};
}
data_channel.onmessage =
gadget.state.dataChannelOnmessage;
}
function sendOffer(gadget, offer) {
const peer_list = gadget.state.peer_connection_list;
const peer_connection = peer_list[peer_list.length - 1];
return new RSVP.Queue()
.push(function () {
return new RTCSessionDescription(JSON.parse(offer));
})
.push(function (offer_description) {
return peer_connection.setRemoteDescription(offer_description);
})
.push(function () {
return peer_connection.createAnswer(
function (answer_description) {
peer_connection.setLocalDescription(answer_description);
}, logError, gadget.state.sdp_constraints);
})
.push(null, logError);
}
function sendAnswer(gadget, answer) {
return new RSVP.Queue()
.push(function () {
return new RTCSessionDescription(JSON.parse(answer));
})
.push(function (answer_description) {
return gadget.state.peer_connection_list[0]
.setRemoteDescription(answer_description);
})
.push(null, logError);
}
// Initialization, rendering, public methods, and event listeners
rJS(window)
.ready(function (gadget) {
gadget.state = {
element: null,
login_dict: {
room: null,
name: null,
role: null,
},
dropbox_folder: null,
erp5_url: null,
dataChannelOnopen: null,
dataChannelOnmessage: null,
candidate: null,
connected: false,
offer_ready: false,
data_channel_list: [],
peer_connection_list: [],
guest_amount: 0,
archive_amount: 0,
ice_config: {
// iceServers: [{url: "stun:stun.1.google.com:19302"}],
// Must use own STUN server in production, but neither
// TURN nor STUN needed for local networks and IPv6
iceServers: [],
},
ice_constraints: {
mandatory: {googIPv6: true},
optional: [{DtlsSrtpKeyAgreement: true}],
},
dc_constraints: {
reliable: true,
},
sdp_constraints: {
optional: [],
mandatory: {
OfferToReceiveAudio: false,
OfferToReceiveVideo: false,
},
},
};
return new RSVP.Queue()
.push(function () {
return gadget.getElement();
})
.push(function (element) {
gadget.element = element;
return;
})
.push(null, logError);
})
// The following function is acquired from erp5_page_chat_box.js
.declareAcquiredMethod("getContactByEmail", "getContactByEmail")
// The following functions are all acquired from erp5_launcher_nojqm.js
.declareAcquiredMethod("jio_put", "jio_put")
.declareAcquiredMethod("jio_get", "jio_get")
.declareAcquiredMethod("jio_repair", "jio_repair")
.declareMethod("render", function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
gadget.element.setAttribute("id",
"webrtc-gadget-" + gadget.state.login_dict.room);
styleElementByQuery(gadget, ".host-answer-form", "none");
styleElementByQuery(gadget, ".guest-answer-form", "none");
if (gadget.state.login_dict.role === "host") {
styleElementByQuery(gadget, ".guest-offer-form", "none");
} else if (gadget.state.login_dict.role === "guest") {
styleElementByQuery(gadget, ".host-offer-form", "none");
}
return createInitialOffer(gadget);
})
.push(null, logError);
})
.declareMethod("sendMessage", function (message, source) {
const gadget = this;
const message_string = JSON.stringify(message);
const channel_list = gadget.state.data_channel_list;
return new RSVP.Queue()
.push(function () {
if (gadget.state.login_dict.role === "host") {
if (message.type === "bundle") {
for (let i = 0, i_len = channel_list.length; i < i_len; i++) {
const channel = channel_list[i];
if (channel.readyState === "open" && channel === source) {
return channel.send(message_string);
}
}
} else {
var promise_list = [];
for (let i = 0, i_len = channel_list.length; i < i_len; i++) {
const channel = channel_list[i];
if (channel.readyState === "open" && channel !== source) {
promise_list.push(channel.send(message_string));
}
}
}
return RSVP.all(promise_list);
} else if (gadget.state.login_dict.role === "guest") {
if (channel_list[0].readyState !== "open") {
throw "The data channel is not open!";
} else {
return channel_list[0].send(message_string);
}
}
})
.push(null, logError);
})
.declareMethod("authenticate", function (auth) {
const gadget = this;
const fields = gadget.element.querySelector(".contact-form").elements;
return new RSVP.Queue()
.push(function () {
gadget.state.dropbox_folder =
fields.dropbox_folder.value;
gadget.state.erp5_url = fields.erp5_url.value;
switch (auth) {
case "dropbox":
return authenticateDropbox(gadget);
case "erp5":
return authenticateErp5(gadget);
default:
return;
}
});
})
.declareMethod("uploadContact", function (event) {
const gadget = this;
const fields = event.target.elements;
return new RSVP.Queue()
.push(function () {
return gadget.getContactByEmail(fields.email.value);
})
.push(function (id) {
const param_dict = {
dropbox_folder: fields.dropbox_folder.value,
erp5_url: fields.erp5_url.value,
auth: gadget.element.querySelector(".auth-form").elements.auth.value
}
let jio_configuration = "";
for (let key in param_dict) {
if (param_dict.hasOwnProperty(key) && param_dict[key]) {
jio_configuration += "&" + key + "=" + param_dict[key];
}
}
return gadget.jio_put(id, {
portal_type: "Person",
parent_relative_url: "person_module",
default_email_coordinate_text: fields.email.value,
jio_configuration: jio_configuration,
});
})
.push(function () {
return gadget.jio_repair();
})
.push(null, logError);
})
.declareMethod("downloadContact", function () {
const gadget = this;
const fields = gadget.element.querySelector(".contact-form").elements;
return new RSVP.Queue()
.push(function () {
return gadget.getContactByEmail(fields.email.value);
})
.push(function (id) {
return gadget.jio_get(id);
})
.push(function (contact) {
if (!contact) {
throw webrtcError(gadget, "Email not found!");
} else {
const param_dict = {};
const query_list = [["email", "e"],
["dropbox_folder", "d"], ["erp5_url", "e"]];
for (let i = 0, i_len = query_list.length; i < i_len; i++) {
const query = query_list[i];
setQueryValue(query, contact.jio_configuration, fields[query[0]]);
}
setQueryValue(["auth", "a"], contact.jio_configuration,
gadget.element.querySelector(".auth-form").auth);
return;
}
})
.push(null, logError);
})
.onEvent("submit", function (event) {
const gadget = this;
switch (event.target.className) {
case "auth-form":
return gadget.authenticate(event.target.elements.auth.value);
case "contact-form":
return gadget.uploadContact(event);
case "download-form":
return gadget.downloadContact();
case "host-offer-form":
styleElementByQuery(gadget, ".host-offer-form", "none");
styleElementByQuery(gadget, ".host-answer-form", "block");
return sendOffer(gadget, resetInputValue(event.target.elements.send));
case "guest-offer-form":
styleElementByQuery(gadget, ".guest-offer-form", "none");
styleElementByQuery(gadget, ".guest-answer-form", "block");
break;
case "guest-answer-form":
styleElementByQuery(gadget, ".guest-answer-form", "none");
return sendAnswer(
gadget, resetInputValue(event.target.elements.send));
}
});
}(window, document, RSVP, rJS));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
<string>contributor/person_module/1</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_chat_webrtc.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_chat_webrtc_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Chat WebRTC JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/**********************************************
* Colors
**********************************************/
/**********************************************
* Fonts (font-family)
**********************************************/
/**********************************************
* Shared
**********************************************/
/**********************************************
* http://meyerweb.com/eric/tools/css/reset/
* v2.0 | 20110126
* License: none (public domain)
**********************************************/
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/**********************************************
* Default
**********************************************/
@font-face {
font-family: 'FontAwesome';
src: url('font-awesome/font-awesome-webfont.eot?v=4.6.3');
src: url('font-awesome/font-awesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'), url('font-awesome/font-awesome-webfont.woff2?v=4.6.3') format('woff2'), url('font-awesome/font-awesome-webfont.woff?v=4.6.3') format('woff'), url('font-awesome/font-awesome-webfont.ttf?v=4.6.3') format('truetype'), url('font-awesome/font-awesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');
font-weight: normal;
font-style: normal;
}
html {
height: 100%;
width: 100%;
display: block;
background-color: #FFFFFF;
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
height: 100%;
width: 100%;
display: block;
color: #1F1F1F;
word-wrap: break-word;
}
body,
button,
input,
textarea,
select {
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
font-size: 12pt;
padding: 0;
margin: 0;
font-weight: 400;
line-height: 1.5;
}
/**********************************************
* Inline elements
**********************************************/
strong {
font-weight: bold;
}
i,
cite,
em,
var,
address,
dfn {
font-style: italic;
}
strong,
b {
font-weight: bold;
}
u,
ins {
text-decoration: underline;
}
s,
strike,
del {
text-decoration: line-through;
}
sup {
vertical-align: super;
font-size: smaller;
}
sub {
vertical-align: sub;
font-size: smaller;
}
small {
font-size: smaller;
}
tt,
code,
kbd,
samp {
font-family: "Courier New", Courier, monospace;
}
q {
display: inline;
quotes: initial;
}
q:before {
content: open-quote;
}
q:after {
content: close-quote;
}
mark {
color: #22CC22;
}
/**********************************************
* Link
**********************************************/
a {
color: #2FA2E4;
text-decoration: none;
}
a[href=""] {
color: #1F1F1F;
}
a:hover {
text-decoration: underline;
}
a:focus {
outline-offset: -2px;
outline: 2px solid #3388cc;
}
@media only screen and (min-width: 90em) {
a[accesskey]:after {
content: " [" attr(accesskey) "] ";
}
}
/**********************************************
* Preformatted
**********************************************/
pre,
xmp,
plaintext,
listing {
display: block;
white-space: pre-wrap;
}
/**********************************************
* hr
**********************************************/
hr {
display: block;
border-style: inset;
border-width: 1px;
border-color: #FF6600;
}
/**********************************************
* Text fields
**********************************************/
label {
color: #777777;
}
input:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]):not([type=color]),
textarea,
select {
width: 100%;
padding: 3pt;
background-color: #FFFFFF;
color: #1F1F1F;
border: 1px solid rgba(0, 0, 0, 0.3);
border-radius: 0.325em;
transition: border 0.2s ease-out, box-shadow 0.2s ease-out;
}
input:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]):not([type=color]):active,
textarea:active,
select:active,
input:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]):not([type=color]):focus,
textarea:focus,
select:focus {
outline: none;
}
input:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]):not([type=color]):focus,
textarea:focus,
select:focus {
border: 1px solid #3388cc;
box-shadow: 0 0 12pt #3388cc;
}
input:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]):not([type=color]):invalid,
textarea:invalid,
select:invalid {
border: 1px solid #FF6600;
}
input:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]):not([type=color]):invalid:focus,
textarea:invalid:focus,
select:invalid:focus {
box-shadow: 0 0 12pt #FF6600;
}
select {
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: none;
background-color: #FFFFFF;
padding-right: 24pt;
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='50px' height='50px'><polyline fill-opacity='0.5' points='46.139,15.518 25.166,36.49 4.193,15.519'/></svg>") right no-repeat;
background-position: right 6pt top 50%;
background-size: 12pt 12pt;
}
select:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 #000;
}
textarea {
word-wrap: break-word;
white-space: normal;
vertical-align: top;
transition: height 0.2s ease-out;
height: 8em;
}
::-webkit-input-placeholder {
color: #575757;
}
:-moz-placeholder {
/* Firefox 18- */
color: #575757;
}
::-moz-placeholder {
/* Firefox 19+ */
color: #575757;
}
:-ms-input-placeholder {
color: #575757;
}
input[type=submit],
button {
margin: 0;
padding: 0;
border: none;
background: transparent;
color: #1F1F1F;
/*
// XXX TODO
// transition: background-color 0.2s ease-out;
&:hover, &:focus {
outline: none;
}
&:active {
// box-shadow: inset 0 0 0 5px darken(@colorblocklinkbackground, 10%);
background-color: darken(@colorblocklinkbackground, 20%);
}
*/
cursor: pointer;
}
input[type=submit]::-moz-focus-inner,
button::-moz-focus-inner {
border: 0px;
padding: 0px;
}
input[type=submit]:hover,
button:hover,
input[type=submit]:focus,
button:focus {
outline: none;
}
/**********************************************
* Media
**********************************************/
img,
iframe,
video,
svg {
max-width: 100%;
}
svg polyline {
stroke: #1F1F1F;
fill: none;
}
svg text {
stroke: #1F1F1F;
fill: #1F1F1F;
}
iframe {
width: 100%;
height: 80vh;
background-color: #FFFFFF;
}
img {
image-orientation: from-image;
}
/**********************************************
* Gadget: panel
**********************************************/
div[data-gadget-scope='panel'] {
background-color: #444444;
color: #FFFFFF;
width: 180pt;
min-height: 100%;
max-height: 100%;
overflow-y: auto;
position: fixed;
top: 0;
left: 0;
display: block;
z-index: 2000;
}
@media only screen and (max-width: 45em), only screen and (min-width: 45em) and (max-width: 90em) {
div[data-gadget-scope='panel'] {
box-shadow: 5px 0 5px rgba(0, 0, 0, 0.15);
}
}
@media only screen and (max-width: 45em), only screen and (min-width: 45em) and (max-width: 90em) {
div[data-gadget-scope='panel'] {
left: -186pt;
transition: transform 200ms ease-out;
transform: translate3d(0, 0, 0);
}
div[data-gadget-scope='panel'].visible {
transform: translate3d(186pt, 0, 0);
}
}
div[data-gadget-scope='panel'] div[data-role="header"] {
display: flex;
justify-content: flex-start;
}
div[data-gadget-scope='panel'] div[data-role="header"] .panel_img {
text-align: center;
flex: 1;
height: 30pt;
}
div[data-gadget-scope='panel'] div[data-role="header"] button,
div[data-gadget-scope='panel'] div[data-role="header"] a {
width: 3em;
overflow: hidden;
text-indent: -9999px;
white-space: nowrap;
background-color: #444444;
display: block;
line-height: 30pt;
color: #FFFFFF;
}
div[data-gadget-scope='panel'] div[data-role="header"] button::before,
div[data-gadget-scope='panel'] div[data-role="header"] a::before {
float: left;
text-indent: 0;
margin-left: 12pt;
}
@media only screen and (min-width: 90em) {
div[data-gadget-scope='panel'] div[data-role="header"] button[data-i18n="Close"],
div[data-gadget-scope='panel'] div[data-role="header"] a[data-i18n="Close"] {
display: none;
}
}
div[data-gadget-scope='panel'] img {
text-align: left;
height: 100%;
}
div[data-gadget-scope='panel'] ul li a {
color: #FFFFFF;
padding: 3pt;
padding-left: 12pt;
display: block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
div[data-gadget-scope='panel'] ul li a::before {
width: 24pt;
}
div[data-gadget-scope='panel'] ul li a:hover,
div[data-gadget-scope='panel'] ul li a:active {
background-color: #2b2b2b;
}
/**********************************************
* Gadget: editor panel
**********************************************/
div[data-gadget-scope='editor_panel'] {
background-color: #FFFFFF;
width: 180pt;
min-height: 100%;
max-height: 100%;
overflow-y: auto;
position: fixed;
top: 0;
display: block;
z-index: 3000;
}
@media only screen and (min-width: 90em) {
div[data-gadget-scope='editor_panel'] {
left: -186pt;
transition: transform 200ms ease-out;
transform: translate3d(0, 0, 0);
box-shadow: 5px 0 5px rgba(0, 0, 0, 0.15);
}
div[data-gadget-scope='editor_panel'].visible {
transform: translate3d(186pt, 0, 0);
}
}
@media only screen and (max-width: 45em), only screen and (min-width: 45em) and (max-width: 90em) {
div[data-gadget-scope='editor_panel'] {
right: -186pt;
transition: transform 200ms ease-out;
transform: translate3d(0, 0, 0);
box-shadow: -5px 0 5px rgba(0, 0, 0, 0.15);
}
div[data-gadget-scope='editor_panel'].visible {
transform: translate3d(-186pt, 0, 0);
}
}
div[data-gadget-scope='editor_panel'] div[data-role="header"] {
display: flex;
justify-content: space-between;
flex-direction: row-reverse;
}
div[data-gadget-scope='editor_panel'] div[data-role="header"] h1 {
text-align: left;
line-height: 30pt;
max-height: 30pt;
}
div[data-gadget-scope='editor_panel'] div[data-role="header"] button,
div[data-gadget-scope='editor_panel'] div[data-role="header"] a {
width: 3em;
overflow: hidden;
text-indent: -9999px;
white-space: nowrap;
display: block;
line-height: 30pt;
}
div[data-gadget-scope='editor_panel'] div[data-role="header"] button::before,
div[data-gadget-scope='editor_panel'] div[data-role="header"] a::before {
float: left;
text-indent: 0;
margin-left: 12pt;
}
div[data-gadget-scope='editor_panel'] section {
padding: 12pt;
}
div[data-gadget-scope='editor_panel'] section fieldset > div {
display: inline-block;
}
div[data-gadget-scope='editor_panel'] section fieldset label {
display: inline-block;
text-align: center;
}
div[data-gadget-scope='editor_panel'] section fieldset input[type="radio"] {
display: inline-block;
}
div[data-gadget-scope='editor_panel'] section .filter_item_container > div,
div[data-gadget-scope='editor_panel'] section .sort_item_container > div {
display: flex;
align-items: flex-start;
padding: 6pt 0;
}
div[data-gadget-scope='editor_panel'] section .filter_item_container > div .filter_item,
div[data-gadget-scope='editor_panel'] section .sort_item_container > div .filter_item,
div[data-gadget-scope='editor_panel'] section .filter_item_container > div .sort_item,
div[data-gadget-scope='editor_panel'] section .sort_item_container > div .sort_item {
flex: 1;
}
div[data-gadget-scope='editor_panel'] section button {
padding: 3pt 6pt;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em;
margin-right: 6pt;
width: 2em;
overflow: hidden;
text-indent: -9999px;
white-space: nowrap;
}
div[data-gadget-scope='editor_panel'] section button:last-of-type {
margin-right: 0;
}
div[data-gadget-scope='editor_panel'] section button::before {
margin-right: 6pt;
float: left;
text-indent: 0;
}
/**********************************************
* Gadget: header
**********************************************/
div[data-gadget-scope='header'] .ui-header {
position: fixed;
z-index: 1000;
text-align: center;
display: flex;
flex-flow: row wrap;
width: 100%;
color: #FFFFFF;
background-color: #0E81C2;
}
@media only screen and (min-width: 90em) {
div[data-gadget-scope='header'] .ui-header {
margin-left: 180pt;
}
}
div[data-gadget-scope='header'] .ui-header button,
div[data-gadget-scope='header'] .ui-header a {
color: #FFFFFF;
transition: background-color 0.2s ease-out;
}
div[data-gadget-scope='header'] .ui-header button:hover,
div[data-gadget-scope='header'] .ui-header a:hover,
div[data-gadget-scope='header'] .ui-header button:active,
div[data-gadget-scope='header'] .ui-header a:active {
background-color: #0e90d8;
}
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls button,
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls a {
display: block;
transition: background-color 0.2s ease-out;
line-height: 30pt;
}
@media only screen and (min-width: 90em) {
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls button,
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls a {
background-color: #0E81C2;
}
}
@media only screen and (max-width: 45em), only screen and (min-width: 45em) and (max-width: 90em) {
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls button,
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls a {
background-color: #085078;
}
}
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls button:hover,
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls a:hover,
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls button:active,
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls a:active {
background-color: #0e90d8;
}
@media only screen and (min-width: 45em) and (max-width: 90em) {
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls button,
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls a {
width: 8em;
}
}
@media only screen and (max-width: 45em) {
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls button,
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls a {
width: 3em;
overflow: hidden;
text-indent: -9999px;
white-space: nowrap;
}
}
@media only screen and (min-width: 45em) and (max-width: 90em), only screen and (min-width: 90em) {
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls button::before,
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls a::before {
width: 1em;
margin-right: 6pt;
text-align: center;
}
}
@media only screen and (max-width: 45em) {
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls button::before,
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls a::before {
float: left;
text-indent: 0;
margin-left: 12pt;
}
}
@media only screen and (min-width: 90em) {
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls button[name="panel"],
div[data-gadget-scope='header'] .ui-header .ui-controlgroup-controls a[name="panel"] {
display: none;
}
}
div[data-gadget-scope='header'] .ui-header > .ui-btn-left button,
div[data-gadget-scope='header'] .ui-header > .ui-btn-left a {
border-right: 1px solid rgba(255, 255, 255, 0.55);
}
@media only screen and (max-width: 45em), only screen and (min-width: 45em) and (max-width: 90em) {
div[data-gadget-scope='header'] .ui-header > .ui-btn-right button,
div[data-gadget-scope='header'] .ui-header > .ui-btn-right a {
border-left: 1px solid rgba(255, 255, 255, 0.55);
}
}
@media only screen and (min-width: 90em) {
div[data-gadget-scope='header'] .ui-header > .ui-btn-right button,
div[data-gadget-scope='header'] .ui-header > .ui-btn-right a {
padding-left: 24pt;
padding-right: 24pt;
min-width: 5em;
}
}
div[data-gadget-scope='header'] .ui-header h1 {
text-align: left;
line-height: 30pt;
flex: 1;
background-color: #085078;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/*
@media only screen and (min-width: 90em) {
div[data-gadget-scope='header'] .ui-header h1 {
flex: 1 100%;
}
}
div[data-gadget-scope='header'] .ui-header h1 > span {
padding-left: 42pt;
}
@media only screen and (max-width: 45em) {
div[data-gadget-scope='header'] .ui-header h1 > span {
padding-left: 30pt;
}
}
@media only screen and (min-width: 90em) {
div[data-gadget-scope='header'] .ui-header h1 > span {
padding-left: 24pt;
}
}
*/
div[data-gadget-scope='header'] .ui-header h1 a {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
div[data-gadget-scope='header'] .ui-header h1 a::before {
display: inline-block;
width: 42pt;
}
@media only screen and (min-width: 45em) and (max-width: 90em), only screen and (max-width: 45em) {
div[data-gadget-scope='header'] .ui-header h1 a::before {
text-align: center;
}
}
@media only screen and (min-width: 90em) {
div[data-gadget-scope='header'] .ui-header h1 a::before {
padding-left: 24pt;
}
}
@media only screen and (max-width: 45em) {
div[data-gadget-scope='header'] .ui-header h1 a::before {
width: 30pt;
}
}
@media only screen and (min-width: 90em) {
div[data-gadget-scope='header'] .ui-header .ui-subheader {
text-align: left;
flex: 1;
}
}
@media only screen and (max-width: 45em), only screen and (min-width: 45em) and (max-width: 90em) {
div[data-gadget-scope='header'] .ui-header .ui-subheader {
flex: 1 100%;
}
}
div[data-gadget-scope='header'] .ui-header ul {
display: flex;
}
@media only screen and (min-width: 45em) and (max-width: 90em), only screen and (max-width: 45em) {
div[data-gadget-scope='header'] .ui-header ul li {
flex: 1;
border-left: 1px solid rgba(0, 0, 0, 0.55);
}
div[data-gadget-scope='header'] .ui-header ul li:first-child {
border-left: none;
}
div[data-gadget-scope='header'] .ui-header ul li a {
display: block;
}
}
@media only screen and (min-width: 45em) and (max-width: 90em) {
div[data-gadget-scope='header'] .ui-header ul li a {
padding-top: 3pt;
padding-bottom: 3pt;
white-space: nowrap;
overflow: hidden;
}
div[data-gadget-scope='header'] .ui-header ul li a::before {
font-size: 1.2em;
display: block;
}
}
@media only screen and (max-width: 45em) {
div[data-gadget-scope='header'] .ui-header ul li a {
text-align: center;
vertical-align: middle;
font-size: 1.5em;
padding-top: 6pt;
padding-bottom: 6pt;
overflow: hidden;
text-indent: -9999px;
white-space: nowrap;
}
div[data-gadget-scope='header'] .ui-header ul li a::before {
float: left;
text-indent: 0;
width: 100%;
}
}
@media only screen and (min-width: 90em) {
div[data-gadget-scope='header'] .ui-header ul li a {
display: block;
padding-left: 24pt;
padding-right: 24pt;
min-width: 5em;
line-height: 30pt;
white-space: nowrap;
overflow: hidden;
}
div[data-gadget-scope='header'] .ui-header ul li a::before {
display: none;
}
}
/**********************************************
* Gadget: main
**********************************************/
.gadget-content {
padding: 24pt;
padding-top: 66pt;
/*
@media @smartphone {
.ui-field-contain {
// padding: 0.8em 0;
// make sure there is a bottom border
// XXX TODO: border should be visible only if not input
// XXX How to not show it on last field?
border-bottom: 1px solid rgba(0, 0, 0, 0.15);
}
}
*/
/* form validation (assuming label>span is used) */
}
.gadget-content div[data-gadget-scope='m'] {
animation: fadein 0.2s ease-out;
}
.gadget-content input[type='submit'] {
padding: 6pt;
background-color: #444444;
color: #FFFFFF;
border-radius: 0.325em;
border-width: 1px;
border-style: solid;
min-width: 8em;
}
.gadget-content input[type='submit']:hover,
.gadget-content input[type='submit']:focus {
background-color: #5e5e5e;
}
.gadget-content input[type='submit']:active {
background-color: #777777;
}
@media only screen and (min-width: 90em) {
.gadget-content {
margin-left: 180pt;
}
}
@media only screen and (min-width: 45em) and (max-width: 90em) {
.gadget-content {
padding-top: 7em;
}
}
@media only screen and (max-width: 45em) {
.gadget-content {
padding: 6pt;
padding-top: 6em;
}
}
.gadget-content .ui-field-contain {
padding: 6pt 0;
}
.gadget-content .ui-field-contain div {
width: 100%;
}
.gadget-content .ui-content-header-plain {
font-size: 150%;
}
.gadget-content ul.document-listview:not(:last-of-type) {
margin-bottom: 12pt;
}
.gadget-content ul.document-listview:first-child {
margin-top: 6pt;
}
.gadget-content ul.document-listview li {
border-color: rgba(0, 0, 0, 0.3);
border-width: 1px;
border-style: solid;
border-bottom-style: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.gadget-content ul.document-listview li:not(.ui-li-has-count) a:after {
font-family: FontAwesome;
content: "\f0da";
text-align: right;
float: right;
position: absolute;
right: 12pt;
}
.gadget-content ul.document-listview li a {
display: block;
position: relative;
padding: 6pt 12pt;
padding-right: 24pt;
color: #222222;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.gadget-content ul.document-listview li a:hover,
.gadget-content ul.document-listview li a:active {
background-color: #e0e0e0;
}
.gadget-content ul.document-listview li:first-child {
border-top-left-radius: 0.325em;
border-top-right-radius: 0.325em;
}
.gadget-content ul.document-listview li:last-child {
border-bottom-left-radius: 0.325em;
border-bottom-right-radius: 0.325em;
border-bottom-style: solid;
}
.gadget-content ul.document-listview li .ui-li-count {
float: right;
padding: 0 6pt;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em;
position: absolute;
right: 6pt;
}
@media only screen and (min-width: 45em) and (max-width: 90em), only screen and (min-width: 90em) {
.gadget-content .left,
.gadget-content .right {
vertical-align: top;
display: inline-block;
width: 50%;
}
.gadget-content .right {
padding-left: 24pt;
}
}
@media only screen and (min-width: 45em) and (max-width: 90em), only screen and (min-width: 90em) {
.gadget-content .ui-field-contain {
display: flex;
}
.gadget-content .ui-field-contain label {
flex: 1;
}
.gadget-content .ui-field-contain label + div {
flex: 3;
}
}
@media only screen and (min-width: 45em) and (max-width: 90em), only screen and (min-width: 90em) {
.gadget-content .center .ui-field-contain label + div {
flex: 7;
}
}
.gadget-content form label {
position: relative;
}
.gadget-content form label span span {
animation: fadein 0.2s ease-out;
}
@media only screen and (min-width: 90em), only screen and (min-width: 45em) and (max-width: 90em) {
.gadget-content form label span span {
background-color: #FF6600;
color: #f8fff3;
left: 110%;
position: absolute;
bottom: 130%;
white-space: pre;
padding: 6pt;
border-radius: 0.325em;
width: auto;
z-index: 1001;
}
.gadget-content form label span span:before {
position: absolute;
top: 100%;
left: 2em;
display: inline-block;
border-right: 6pt solid transparent;
border-top: 6pt solid #FF6600;
border-left: 6pt solid transparent;
content: '';
}
}
@media only screen and (max-width: 45em) {
.gadget-content form label span span {
margin-left: 6pt;
color: #FF6600;
}
}
/**********************************************
* Gadget: relation field
**********************************************/
.relation-input {
display: flex;
}
.relation-input a {
width: 24pt;
overflow: hidden;
text-indent: -9999px;
white-space: nowrap;
display: block;
padding-top: 3pt;
padding-bottom: 3pt;
}
.relation-input a::before {
float: left;
text-indent: 0;
margin-left: 6pt;
}
.relation-input div {
position: relative;
}
.relation-input ul {
position: absolute;
display: block;
width: 100%;
z-index: 501;
}
.relation-input ul li {
cursor: pointer;
background-color: #444444;
color: #FFFFFF;
padding: 3pt;
padding-left: 6pt;
display: block;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.relation-input ul li::before {
padding-right: 6pt;
}
.relation-input ul li:hover,
.relation-input ul li:active {
background-color: #2b2b2b;
}
/**********************************************
* Gadget: datetime field
**********************************************/
.datetimefield {
display: flex;
}
.datetimefield div[data-gadget-scope=INPUT] {
flex: 2;
}
.datetimefield div[data-gadget-scope=SELECT] {
flex: 1;
}
/**********************************************
* Gadget: float/integer field
**********************************************/
.floatfield p,
.integerfield p,
.floatfield input,
.integerfield input {
text-align: right;
}
/**********************************************
* Listbox
**********************************************/
div[data-gadget-scope='erp5_searchfield'] {
padding-top: 6pt;
}
div[data-gadget-scope='erp5_searchfield'] .ui-input-text {
display: flex;
}
div[data-gadget-scope='erp5_searchfield'] .ui-input-text div[data-gadget-scope='input'] {
width: 100%;
}
div[data-gadget-scope='erp5_searchfield'] button {
padding: 3pt;
}
.document-table .ui-table-header {
display: flex;
padding-bottom: 6pt;
}
@media only screen and (max-width: 45em) {
.document-table .ui-table-header {
border-bottom: 2px solid rgba(0, 0, 0, 0.14902);
}
}
.document-table .ui-table-header h1 {
color: #777777;
flex: 2;
align-self: flex-end;
}
.document-table .ui-table-header button {
padding: 3pt;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em;
margin-right: 6pt;
}
.document-table .ui-table-header button:last-of-type {
margin-right: 0;
}
@media only screen and (max-width: 45em) {
.document-table .ui-table-header button {
width: 2em;
overflow: hidden;
text-indent: -9999px;
white-space: nowrap;
}
}
.document-table .ui-table-header button::before {
margin-right: 6pt;
}
@media only screen and (max-width: 45em) {
.document-table .ui-table-header button::before {
float: left;
text-indent: 0;
}
}
.document-table table {
width: 100%;
text-align: left;
}
.document-table table th,
.document-table table td {
vertical-align: middle;
}
.document-table table thead {
background-color: #0E81C2;
color: #FFFFFF;
}
.document-table table thead tr th {
padding: 6pt 3pt;
}
@media only screen and (max-width: 45em) {
.document-table table thead {
display: none;
}
}
.document-table table a {
color: #1F1F1F;
}
.document-table table tbody {
animation: fadein 0.2s ease-out;
}
.document-table table tbody tr:nth-child(even) {
background-color: #f2f2f2;
}
.document-table table tbody tr:hover,
.document-table table tbody tr:active {
background-color: #e0e0e0;
}
@media only screen and (min-width: 90em), only screen and (min-width: 45em) and (max-width: 90em) {
.document-table table tbody a {
display: block;
padding: 3pt;
}
}
@media only screen and (max-width: 45em) {
.document-table table tbody tr {
display: block;
overflow: hidden;
width: 100%;
height: 4em;
position: relative;
}
.document-table table tbody tr td,
.document-table table tbody tr th {
display: inline-block;
}
.document-table table tbody tr td:first-child,
.document-table table tbody tr th:first-child {
display: inline-block;
width: 100%;
}
.document-table table tbody tr td:first-child a,
.document-table table tbody tr th:first-child a {
position: absolute;
width: 100%;
top: 0;
bottom: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.document-table table tbody tr td:first-child a:after,
.document-table table tbody tr th:first-child a:after {
font-family: FontAwesome;
content: "\f0da";
font-size: 1.25em;
position: absolute;
right: 6pt;
top: 50%;
margin-top: -0.75em;
background-color: #FFFFFF;
border-radius: 0.5em;
width: 1em;
text-align: center;
line-height: 1em;
}
.document-table table tbody tr td:first-child ~ th,
.document-table table tbody tr th:first-child ~ th,
.document-table table tbody tr td:first-child ~ td,
.document-table table tbody tr th:first-child ~ td {
font-size: 0.8em;
}
.document-table table tbody tr td:first-child ~ th a,
.document-table table tbody tr th:first-child ~ th a,
.document-table table tbody tr td:first-child ~ td a,
.document-table table tbody tr th:first-child ~ td a {
pointer-events: none;
}
.document-table table tbody tr td:first-child ~ th:not(:last-child) a:not(:empty):after,
.document-table table tbody tr th:first-child ~ th:not(:last-child) a:not(:empty):after,
.document-table table tbody tr td:first-child ~ td:not(:last-child) a:not(:empty):after,
.document-table table tbody tr th:first-child ~ td:not(:last-child) a:not(:empty):after {
content: " ~ ";
}
}
.document-table table tfoot .ui-controlgroup-controls {
display: flex;
padding-top: 6pt;
border-top: 2px solid rgba(0, 0, 0, 0.14902);
}
.document-table table tfoot .ui-controlgroup-controls span {
opacity: .3;
flex: 2;
text-align: right;
}
.document-table table tfoot .ui-controlgroup-controls a {
padding: 6pt;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em;
margin-right: 6pt;
}
.document-table table tfoot .ui-controlgroup-controls a:last-of-type {
margin-right: 0;
}
.document-table table tfoot .ui-controlgroup-controls a:hover,
.document-table table tfoot .ui-controlgroup-controls a:active {
background-color: #e0e0e0;
}
@media only screen and (max-width: 45em) {
.document-table table tfoot .ui-controlgroup-controls a {
overflow: hidden;
text-indent: -9999px;
white-space: nowrap;
}
}
.document-table table tfoot .ui-controlgroup-controls a::before {
margin-right: 6pt;
}
@media only screen and (max-width: 45em) {
.document-table table tfoot .ui-controlgroup-controls a::before {
float: left;
text-indent: 6pt;
}
}
/**********************************************
* Notification
**********************************************/
div[data-gadget-scope='notification'] {
position: fixed;
z-index: 99999;
bottom: 12pt;
right: -192pt;
transition: transform 200ms ease-out;
transform: translate3d(0, 0, 0);
}
div[data-gadget-scope='notification'].visible {
transform: translate3d(-216pt, 0, 0);
}
@media only screen and (max-width: 45em) {
div[data-gadget-scope='notification'].visible {
transform: translate3d(-198pt, 0, 0);
}
}
div[data-gadget-scope='notification'] button {
text-align: left;
width: 180pt;
padding: 12pt;
background-color: #FF6600;
color: #f8fff3;
border-radius: 0.325em;
}
/**********************************************
* JQM
**********************************************/
.ui-disabled {
opacity: .3;
cursor: default;
pointer-events: none;
}
.ui-screen-hidden {
display: none;
}
/**********************************************
* First loader
**********************************************/
.first-loader {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 300%;
}
/**********************************************
* Keyframes
**********************************************/
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/*!
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
/* FONT PATH
* -------------------------- */
@font-face {
font-family: 'FontAwesome';
font-weight: normal;
font-style: normal;
}
.fa {
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* makes the font 33% larger relative to the icon container */
.fa-lg {
font-size: 1.33333333em;
line-height: 0.75em;
vertical-align: -15%;
}
.fa-2x {
font-size: 2em;
}
.fa-3x {
font-size: 3em;
}
.fa-4x {
font-size: 4em;
}
.fa-5x {
font-size: 5em;
}
.fa-fw {
width: 1.28571429em;
text-align: center;
}
.fa-ul {
padding-left: 0;
margin-left: 2.14285714em;
list-style-type: none;
}
.fa-ul > li {
position: relative;
}
.fa-li {
position: absolute;
left: -2.14285714em;
width: 2.14285714em;
top: 0.14285714em;
text-align: center;
}
.fa-li.fa-lg {
left: -1.85714286em;
}
.fa-border {
padding: .2em .25em .15em;
border: solid 0.08em #eeeeee;
border-radius: .1em;
}
.fa-pull-left {
float: left;
}
.fa-pull-right {
float: right;
}
.fa.fa-pull-left {
margin-right: .3em;
}
.fa.fa-pull-right {
margin-left: .3em;
}
/* Deprecated as of 4.4.0 */
.pull-right {
float: right;
}
.pull-left {
float: left;
}
.fa.pull-left {
margin-right: .3em;
}
.fa.pull-right {
margin-left: .3em;
}
.fa-spin {
-webkit-animation: fa-spin 2s infinite linear;
animation: fa-spin 2s infinite linear;
}
.fa-pulse {
-webkit-animation: fa-spin 1s infinite steps(8);
animation: fa-spin 1s infinite steps(8);
}
@-webkit-keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
.fa-rotate-90 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.fa-rotate-180 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
.fa-rotate-270 {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
-webkit-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
}
.fa-flip-horizontal {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
-webkit-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
transform: scale(-1, 1);
}
.fa-flip-vertical {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
-webkit-transform: scale(1, -1);
-ms-transform: scale(1, -1);
transform: scale(1, -1);
}
:root .fa-rotate-90,
:root .fa-rotate-180,
:root .fa-rotate-270,
:root .fa-flip-horizontal,
:root .fa-flip-vertical {
filter: none;
}
.fa-stack {
position: relative;
display: inline-block;
width: 2em;
height: 2em;
line-height: 2em;
vertical-align: middle;
}
.fa-stack-1x,
.fa-stack-2x {
position: absolute;
left: 0;
width: 100%;
text-align: center;
}
.fa-stack-1x {
line-height: inherit;
}
.fa-stack-2x {
font-size: 2em;
}
.fa-inverse {
color: #ffffff;
}
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
readers do not read off random characters that represent icons */
.fa-glass:before {
content: "\f000";
}
.fa-music:before {
content: "\f001";
}
.fa-search:before {
content: "\f002";
}
.fa-envelope-o:before {
content: "\f003";
}
.fa-heart:before {
content: "\f004";
}
.fa-star:before {
content: "\f005";
}
.fa-star-o:before {
content: "\f006";
}
.fa-user:before {
content: "\f007";
}
.fa-film:before {
content: "\f008";
}
.fa-th-large:before {
content: "\f009";
}
.fa-th:before {
content: "\f00a";
}
.fa-th-list:before {
content: "\f00b";
}
.fa-check:before {
content: "\f00c";
}
.fa-remove:before,
.fa-close:before,
.fa-times:before {
content: "\f00d";
}
.fa-search-plus:before {
content: "\f00e";
}
.fa-search-minus:before {
content: "\f010";
}
.fa-power-off:before {
content: "\f011";
}
.fa-signal:before {
content: "\f012";
}
.fa-gear:before,
.fa-cog:before {
content: "\f013";
}
.fa-trash-o:before {
content: "\f014";
}
.fa-home:before {
content: "\f015";
}
.fa-file-o:before {
content: "\f016";
}
.fa-clock-o:before {
content: "\f017";
}
.fa-road:before {
content: "\f018";
}
.fa-download:before {
content: "\f019";
}
.fa-arrow-circle-o-down:before {
content: "\f01a";
}
.fa-arrow-circle-o-up:before {
content: "\f01b";
}
.fa-inbox:before {
content: "\f01c";
}
.fa-play-circle-o:before {
content: "\f01d";
}
.fa-rotate-right:before,
.fa-repeat:before {
content: "\f01e";
}
.fa-refresh:before {
content: "\f021";
}
.fa-list-alt:before {
content: "\f022";
}
.fa-lock:before {
content: "\f023";
}
.fa-flag:before {
content: "\f024";
}
.fa-headphones:before {
content: "\f025";
}
.fa-volume-off:before {
content: "\f026";
}
.fa-volume-down:before {
content: "\f027";
}
.fa-volume-up:before {
content: "\f028";
}
.fa-qrcode:before {
content: "\f029";
}
.fa-barcode:before {
content: "\f02a";
}
.fa-tag:before {
content: "\f02b";
}
.fa-tags:before {
content: "\f02c";
}
.fa-book:before {
content: "\f02d";
}
.fa-bookmark:before {
content: "\f02e";
}
.fa-print:before {
content: "\f02f";
}
.fa-camera:before {
content: "\f030";
}
.fa-font:before {
content: "\f031";
}
.fa-bold:before {
content: "\f032";
}
.fa-italic:before {
content: "\f033";
}
.fa-text-height:before {
content: "\f034";
}
.fa-text-width:before {
content: "\f035";
}
.fa-align-left:before {
content: "\f036";
}
.fa-align-center:before {
content: "\f037";
}
.fa-align-right:before {
content: "\f038";
}
.fa-align-justify:before {
content: "\f039";
}
.fa-list:before {
content: "\f03a";
}
.fa-dedent:before,
.fa-outdent:before {
content: "\f03b";
}
.fa-indent:before {
content: "\f03c";
}
.fa-video-camera:before {
content: "\f03d";
}
.fa-photo:before,
.fa-image:before,
.fa-picture-o:before {
content: "\f03e";
}
.fa-pencil:before {
content: "\f040";
}
.fa-map-marker:before {
content: "\f041";
}
.fa-adjust:before {
content: "\f042";
}
.fa-tint:before {
content: "\f043";
}
.fa-edit:before,
.fa-pencil-square-o:before {
content: "\f044";
}
.fa-share-square-o:before {
content: "\f045";
}
.fa-check-square-o:before {
content: "\f046";
}
.fa-arrows:before {
content: "\f047";
}
.fa-step-backward:before {
content: "\f048";
}
.fa-fast-backward:before {
content: "\f049";
}
.fa-backward:before {
content: "\f04a";
}
.fa-play:before {
content: "\f04b";
}
.fa-pause:before {
content: "\f04c";
}
.fa-stop:before {
content: "\f04d";
}
.fa-forward:before {
content: "\f04e";
}
.fa-fast-forward:before {
content: "\f050";
}
.fa-step-forward:before {
content: "\f051";
}
.fa-eject:before {
content: "\f052";
}
.fa-chevron-left:before {
content: "\f053";
}
.fa-chevron-right:before {
content: "\f054";
}
.fa-plus-circle:before {
content: "\f055";
}
.fa-minus-circle:before {
content: "\f056";
}
.fa-times-circle:before {
content: "\f057";
}
.fa-check-circle:before {
content: "\f058";
}
.fa-question-circle:before {
content: "\f059";
}
.fa-info-circle:before {
content: "\f05a";
}
.fa-crosshairs:before {
content: "\f05b";
}
.fa-times-circle-o:before {
content: "\f05c";
}
.fa-check-circle-o:before {
content: "\f05d";
}
.fa-ban:before {
content: "\f05e";
}
.fa-arrow-left:before {
content: "\f060";
}
.fa-arrow-right:before {
content: "\f061";
}
.fa-arrow-up:before {
content: "\f062";
}
.fa-arrow-down:before {
content: "\f063";
}
.fa-mail-forward:before,
.fa-share:before {
content: "\f064";
}
.fa-expand:before {
content: "\f065";
}
.fa-compress:before {
content: "\f066";
}
.fa-plus:before {
content: "\f067";
}
.fa-minus:before {
content: "\f068";
}
.fa-asterisk:before {
content: "\f069";
}
.fa-exclamation-circle:before {
content: "\f06a";
}
.fa-gift:before {
content: "\f06b";
}
.fa-leaf:before {
content: "\f06c";
}
.fa-fire:before {
content: "\f06d";
}
.fa-eye:before {
content: "\f06e";
}
.fa-eye-slash:before {
content: "\f070";
}
.fa-warning:before,
.fa-exclamation-triangle:before {
content: "\f071";
}
.fa-plane:before {
content: "\f072";
}
.fa-calendar:before {
content: "\f073";
}
.fa-random:before {
content: "\f074";
}
.fa-comment:before {
content: "\f075";
}
.fa-magnet:before {
content: "\f076";
}
.fa-chevron-up:before {
content: "\f077";
}
.fa-chevron-down:before {
content: "\f078";
}
.fa-retweet:before {
content: "\f079";
}
.fa-shopping-cart:before {
content: "\f07a";
}
.fa-folder:before {
content: "\f07b";
}
.fa-folder-open:before {
content: "\f07c";
}
.fa-arrows-v:before {
content: "\f07d";
}
.fa-arrows-h:before {
content: "\f07e";
}
.fa-bar-chart-o:before,
.fa-bar-chart:before {
content: "\f080";
}
.fa-twitter-square:before {
content: "\f081";
}
.fa-facebook-square:before {
content: "\f082";
}
.fa-camera-retro:before {
content: "\f083";
}
.fa-key:before {
content: "\f084";
}
.fa-gears:before,
.fa-cogs:before {
content: "\f085";
}
.fa-comments:before {
content: "\f086";
}
.fa-thumbs-o-up:before {
content: "\f087";
}
.fa-thumbs-o-down:before {
content: "\f088";
}
.fa-star-half:before {
content: "\f089";
}
.fa-heart-o:before {
content: "\f08a";
}
.fa-sign-out:before {
content: "\f08b";
}
.fa-linkedin-square:before {
content: "\f08c";
}
.fa-thumb-tack:before {
content: "\f08d";
}
.fa-external-link:before {
content: "\f08e";
}
.fa-sign-in:before {
content: "\f090";
}
.fa-trophy:before {
content: "\f091";
}
.fa-github-square:before {
content: "\f092";
}
.fa-upload:before {
content: "\f093";
}
.fa-lemon-o:before {
content: "\f094";
}
.fa-phone:before {
content: "\f095";
}
.fa-square-o:before {
content: "\f096";
}
.fa-bookmark-o:before {
content: "\f097";
}
.fa-phone-square:before {
content: "\f098";
}
.fa-twitter:before {
content: "\f099";
}
.fa-facebook-f:before,
.fa-facebook:before {
content: "\f09a";
}
.fa-github:before {
content: "\f09b";
}
.fa-unlock:before {
content: "\f09c";
}
.fa-credit-card:before {
content: "\f09d";
}
.fa-feed:before,
.fa-rss:before {
content: "\f09e";
}
.fa-hdd-o:before {
content: "\f0a0";
}
.fa-bullhorn:before {
content: "\f0a1";
}
.fa-bell:before {
content: "\f0f3";
}
.fa-certificate:before {
content: "\f0a3";
}
.fa-hand-o-right:before {
content: "\f0a4";
}
.fa-hand-o-left:before {
content: "\f0a5";
}
.fa-hand-o-up:before {
content: "\f0a6";
}
.fa-hand-o-down:before {
content: "\f0a7";
}
.fa-arrow-circle-left:before {
content: "\f0a8";
}
.fa-arrow-circle-right:before {
content: "\f0a9";
}
.fa-arrow-circle-up:before {
content: "\f0aa";
}
.fa-arrow-circle-down:before {
content: "\f0ab";
}
.fa-globe:before {
content: "\f0ac";
}
.fa-wrench:before {
content: "\f0ad";
}
.fa-tasks:before {
content: "\f0ae";
}
.fa-filter:before {
content: "\f0b0";
}
.fa-briefcase:before {
content: "\f0b1";
}
.fa-arrows-alt:before {
content: "\f0b2";
}
.fa-group:before,
.fa-users:before {
content: "\f0c0";
}
.fa-chain:before,
.fa-link:before {
content: "\f0c1";
}
.fa-cloud:before {
content: "\f0c2";
}
.fa-flask:before {
content: "\f0c3";
}
.fa-cut:before,
.fa-scissors:before {
content: "\f0c4";
}
.fa-copy:before,
.fa-files-o:before {
content: "\f0c5";
}
.fa-paperclip:before {
content: "\f0c6";
}
.fa-save:before,
.fa-floppy-o:before {
content: "\f0c7";
}
.fa-square:before {
content: "\f0c8";
}
.fa-navicon:before,
.fa-reorder:before,
.fa-bars:before {
content: "\f0c9";
}
.fa-list-ul:before {
content: "\f0ca";
}
.fa-list-ol:before {
content: "\f0cb";
}
.fa-strikethrough:before {
content: "\f0cc";
}
.fa-underline:before {
content: "\f0cd";
}
.fa-table:before {
content: "\f0ce";
}
.fa-magic:before {
content: "\f0d0";
}
.fa-truck:before {
content: "\f0d1";
}
.fa-pinterest:before {
content: "\f0d2";
}
.fa-pinterest-square:before {
content: "\f0d3";
}
.fa-google-plus-square:before {
content: "\f0d4";
}
.fa-google-plus:before {
content: "\f0d5";
}
.fa-money:before {
content: "\f0d6";
}
.fa-caret-down:before {
content: "\f0d7";
}
.fa-caret-up:before {
content: "\f0d8";
}
.fa-caret-left:before {
content: "\f0d9";
}
.fa-caret-right:before {
content: "\f0da";
}
.fa-columns:before {
content: "\f0db";
}
.fa-unsorted:before,
.fa-sort:before {
content: "\f0dc";
}
.fa-sort-down:before,
.fa-sort-desc:before {
content: "\f0dd";
}
.fa-sort-up:before,
.fa-sort-asc:before {
content: "\f0de";
}
.fa-envelope:before {
content: "\f0e0";
}
.fa-linkedin:before {
content: "\f0e1";
}
.fa-rotate-left:before,
.fa-undo:before {
content: "\f0e2";
}
.fa-legal:before,
.fa-gavel:before {
content: "\f0e3";
}
.fa-dashboard:before,
.fa-tachometer:before {
content: "\f0e4";
}
.fa-comment-o:before {
content: "\f0e5";
}
.fa-comments-o:before {
content: "\f0e6";
}
.fa-flash:before,
.fa-bolt:before {
content: "\f0e7";
}
.fa-sitemap:before {
content: "\f0e8";
}
.fa-umbrella:before {
content: "\f0e9";
}
.fa-paste:before,
.fa-clipboard:before {
content: "\f0ea";
}
.fa-lightbulb-o:before {
content: "\f0eb";
}
.fa-exchange:before {
content: "\f0ec";
}
.fa-cloud-download:before {
content: "\f0ed";
}
.fa-cloud-upload:before {
content: "\f0ee";
}
.fa-user-md:before {
content: "\f0f0";
}
.fa-stethoscope:before {
content: "\f0f1";
}
.fa-suitcase:before {
content: "\f0f2";
}
.fa-bell-o:before {
content: "\f0a2";
}
.fa-coffee:before {
content: "\f0f4";
}
.fa-cutlery:before {
content: "\f0f5";
}
.fa-file-text-o:before {
content: "\f0f6";
}
.fa-building-o:before {
content: "\f0f7";
}
.fa-hospital-o:before {
content: "\f0f8";
}
.fa-ambulance:before {
content: "\f0f9";
}
.fa-medkit:before {
content: "\f0fa";
}
.fa-fighter-jet:before {
content: "\f0fb";
}
.fa-beer:before {
content: "\f0fc";
}
.fa-h-square:before {
content: "\f0fd";
}
.fa-plus-square:before {
content: "\f0fe";
}
.fa-angle-double-left:before {
content: "\f100";
}
.fa-angle-double-right:before {
content: "\f101";
}
.fa-angle-double-up:before {
content: "\f102";
}
.fa-angle-double-down:before {
content: "\f103";
}
.fa-angle-left:before {
content: "\f104";
}
.fa-angle-right:before {
content: "\f105";
}
.fa-angle-up:before {
content: "\f106";
}
.fa-angle-down:before {
content: "\f107";
}
.fa-desktop:before {
content: "\f108";
}
.fa-laptop:before {
content: "\f109";
}
.fa-tablet:before {
content: "\f10a";
}
.fa-mobile-phone:before,
.fa-mobile:before {
content: "\f10b";
}
.fa-circle-o:before {
content: "\f10c";
}
.fa-quote-left:before {
content: "\f10d";
}
.fa-quote-right:before {
content: "\f10e";
}
.fa-spinner:before {
content: "\f110";
}
.fa-circle:before {
content: "\f111";
}
.fa-mail-reply:before,
.fa-reply:before {
content: "\f112";
}
.fa-github-alt:before {
content: "\f113";
}
.fa-folder-o:before {
content: "\f114";
}
.fa-folder-open-o:before {
content: "\f115";
}
.fa-smile-o:before {
content: "\f118";
}
.fa-frown-o:before {
content: "\f119";
}
.fa-meh-o:before {
content: "\f11a";
}
.fa-gamepad:before {
content: "\f11b";
}
.fa-keyboard-o:before {
content: "\f11c";
}
.fa-flag-o:before {
content: "\f11d";
}
.fa-flag-checkered:before {
content: "\f11e";
}
.fa-terminal:before {
content: "\f120";
}
.fa-code:before {
content: "\f121";
}
.fa-mail-reply-all:before,
.fa-reply-all:before {
content: "\f122";
}
.fa-star-half-empty:before,
.fa-star-half-full:before,
.fa-star-half-o:before {
content: "\f123";
}
.fa-location-arrow:before {
content: "\f124";
}
.fa-crop:before {
content: "\f125";
}
.fa-code-fork:before {
content: "\f126";
}
.fa-unlink:before,
.fa-chain-broken:before {
content: "\f127";
}
.fa-question:before {
content: "\f128";
}
.fa-info:before {
content: "\f129";
}
.fa-exclamation:before {
content: "\f12a";
}
.fa-superscript:before {
content: "\f12b";
}
.fa-subscript:before {
content: "\f12c";
}
.fa-eraser:before {
content: "\f12d";
}
.fa-puzzle-piece:before {
content: "\f12e";
}
.fa-microphone:before {
content: "\f130";
}
.fa-microphone-slash:before {
content: "\f131";
}
.fa-shield:before {
content: "\f132";
}
.fa-calendar-o:before {
content: "\f133";
}
.fa-fire-extinguisher:before {
content: "\f134";
}
.fa-rocket:before {
content: "\f135";
}
.fa-maxcdn:before {
content: "\f136";
}
.fa-chevron-circle-left:before {
content: "\f137";
}
.fa-chevron-circle-right:before {
content: "\f138";
}
.fa-chevron-circle-up:before {
content: "\f139";
}
.fa-chevron-circle-down:before {
content: "\f13a";
}
.fa-html5:before {
content: "\f13b";
}
.fa-css3:before {
content: "\f13c";
}
.fa-anchor:before {
content: "\f13d";
}
.fa-unlock-alt:before {
content: "\f13e";
}
.fa-bullseye:before {
content: "\f140";
}
.fa-ellipsis-h:before {
content: "\f141";
}
.fa-ellipsis-v:before {
content: "\f142";
}
.fa-rss-square:before {
content: "\f143";
}
.fa-play-circle:before {
content: "\f144";
}
.fa-ticket:before {
content: "\f145";
}
.fa-minus-square:before {
content: "\f146";
}
.fa-minus-square-o:before {
content: "\f147";
}
.fa-level-up:before {
content: "\f148";
}
.fa-level-down:before {
content: "\f149";
}
.fa-check-square:before {
content: "\f14a";
}
.fa-pencil-square:before {
content: "\f14b";
}
.fa-external-link-square:before {
content: "\f14c";
}
.fa-share-square:before {
content: "\f14d";
}
.fa-compass:before {
content: "\f14e";
}
.fa-toggle-down:before,
.fa-caret-square-o-down:before {
content: "\f150";
}
.fa-toggle-up:before,
.fa-caret-square-o-up:before {
content: "\f151";
}
.fa-toggle-right:before,
.fa-caret-square-o-right:before {
content: "\f152";
}
.fa-euro:before,
.fa-eur:before {
content: "\f153";
}
.fa-gbp:before {
content: "\f154";
}
.fa-dollar:before,
.fa-usd:before {
content: "\f155";
}
.fa-rupee:before,
.fa-inr:before {
content: "\f156";
}
.fa-cny:before,
.fa-rmb:before,
.fa-yen:before,
.fa-jpy:before {
content: "\f157";
}
.fa-ruble:before,
.fa-rouble:before,
.fa-rub:before {
content: "\f158";
}
.fa-won:before,
.fa-krw:before {
content: "\f159";
}
.fa-bitcoin:before,
.fa-btc:before {
content: "\f15a";
}
.fa-file:before {
content: "\f15b";
}
.fa-file-text:before {
content: "\f15c";
}
.fa-sort-alpha-asc:before {
content: "\f15d";
}
.fa-sort-alpha-desc:before {
content: "\f15e";
}
.fa-sort-amount-asc:before {
content: "\f160";
}
.fa-sort-amount-desc:before {
content: "\f161";
}
.fa-sort-numeric-asc:before {
content: "\f162";
}
.fa-sort-numeric-desc:before {
content: "\f163";
}
.fa-thumbs-up:before {
content: "\f164";
}
.fa-thumbs-down:before {
content: "\f165";
}
.fa-youtube-square:before {
content: "\f166";
}
.fa-youtube:before {
content: "\f167";
}
.fa-xing:before {
content: "\f168";
}
.fa-xing-square:before {
content: "\f169";
}
.fa-youtube-play:before {
content: "\f16a";
}
.fa-dropbox:before {
content: "\f16b";
}
.fa-stack-overflow:before {
content: "\f16c";
}
.fa-instagram:before {
content: "\f16d";
}
.fa-flickr:before {
content: "\f16e";
}
.fa-adn:before {
content: "\f170";
}
.fa-bitbucket:before {
content: "\f171";
}
.fa-bitbucket-square:before {
content: "\f172";
}
.fa-tumblr:before {
content: "\f173";
}
.fa-tumblr-square:before {
content: "\f174";
}
.fa-long-arrow-down:before {
content: "\f175";
}
.fa-long-arrow-up:before {
content: "\f176";
}
.fa-long-arrow-left:before {
content: "\f177";
}
.fa-long-arrow-right:before {
content: "\f178";
}
.fa-apple:before {
content: "\f179";
}
.fa-windows:before {
content: "\f17a";
}
.fa-android:before {
content: "\f17b";
}
.fa-linux:before {
content: "\f17c";
}
.fa-dribbble:before {
content: "\f17d";
}
.fa-skype:before {
content: "\f17e";
}
.fa-foursquare:before {
content: "\f180";
}
.fa-trello:before {
content: "\f181";
}
.fa-female:before {
content: "\f182";
}
.fa-male:before {
content: "\f183";
}
.fa-gittip:before,
.fa-gratipay:before {
content: "\f184";
}
.fa-sun-o:before {
content: "\f185";
}
.fa-moon-o:before {
content: "\f186";
}
.fa-archive:before {
content: "\f187";
}
.fa-bug:before {
content: "\f188";
}
.fa-vk:before {
content: "\f189";
}
.fa-weibo:before {
content: "\f18a";
}
.fa-renren:before {
content: "\f18b";
}
.fa-pagelines:before {
content: "\f18c";
}
.fa-stack-exchange:before {
content: "\f18d";
}
.fa-arrow-circle-o-right:before {
content: "\f18e";
}
.fa-arrow-circle-o-left:before {
content: "\f190";
}
.fa-toggle-left:before,
.fa-caret-square-o-left:before {
content: "\f191";
}
.fa-dot-circle-o:before {
content: "\f192";
}
.fa-wheelchair:before {
content: "\f193";
}
.fa-vimeo-square:before {
content: "\f194";
}
.fa-turkish-lira:before,
.fa-try:before {
content: "\f195";
}
.fa-plus-square-o:before {
content: "\f196";
}
.fa-space-shuttle:before {
content: "\f197";
}
.fa-slack:before {
content: "\f198";
}
.fa-envelope-square:before {
content: "\f199";
}
.fa-wordpress:before {
content: "\f19a";
}
.fa-openid:before {
content: "\f19b";
}
.fa-institution:before,
.fa-bank:before,
.fa-university:before {
content: "\f19c";
}
.fa-mortar-board:before,
.fa-graduation-cap:before {
content: "\f19d";
}
.fa-yahoo:before {
content: "\f19e";
}
.fa-google:before {
content: "\f1a0";
}
.fa-reddit:before {
content: "\f1a1";
}
.fa-reddit-square:before {
content: "\f1a2";
}
.fa-stumbleupon-circle:before {
content: "\f1a3";
}
.fa-stumbleupon:before {
content: "\f1a4";
}
.fa-delicious:before {
content: "\f1a5";
}
.fa-digg:before {
content: "\f1a6";
}
.fa-pied-piper-pp:before {
content: "\f1a7";
}
.fa-pied-piper-alt:before {
content: "\f1a8";
}
.fa-drupal:before {
content: "\f1a9";
}
.fa-joomla:before {
content: "\f1aa";
}
.fa-language:before {
content: "\f1ab";
}
.fa-fax:before {
content: "\f1ac";
}
.fa-building:before {
content: "\f1ad";
}
.fa-child:before {
content: "\f1ae";
}
.fa-paw:before {
content: "\f1b0";
}
.fa-spoon:before {
content: "\f1b1";
}
.fa-cube:before {
content: "\f1b2";
}
.fa-cubes:before {
content: "\f1b3";
}
.fa-behance:before {
content: "\f1b4";
}
.fa-behance-square:before {
content: "\f1b5";
}
.fa-steam:before {
content: "\f1b6";
}
.fa-steam-square:before {
content: "\f1b7";
}
.fa-recycle:before {
content: "\f1b8";
}
.fa-automobile:before,
.fa-car:before {
content: "\f1b9";
}
.fa-cab:before,
.fa-taxi:before {
content: "\f1ba";
}
.fa-tree:before {
content: "\f1bb";
}
.fa-spotify:before {
content: "\f1bc";
}
.fa-deviantart:before {
content: "\f1bd";
}
.fa-soundcloud:before {
content: "\f1be";
}
.fa-database:before {
content: "\f1c0";
}
.fa-file-pdf-o:before {
content: "\f1c1";
}
.fa-file-word-o:before {
content: "\f1c2";
}
.fa-file-excel-o:before {
content: "\f1c3";
}
.fa-file-powerpoint-o:before {
content: "\f1c4";
}
.fa-file-photo-o:before,
.fa-file-picture-o:before,
.fa-file-image-o:before {
content: "\f1c5";
}
.fa-file-zip-o:before,
.fa-file-archive-o:before {
content: "\f1c6";
}
.fa-file-sound-o:before,
.fa-file-audio-o:before {
content: "\f1c7";
}
.fa-file-movie-o:before,
.fa-file-video-o:before {
content: "\f1c8";
}
.fa-file-code-o:before {
content: "\f1c9";
}
.fa-vine:before {
content: "\f1ca";
}
.fa-codepen:before {
content: "\f1cb";
}
.fa-jsfiddle:before {
content: "\f1cc";
}
.fa-life-bouy:before,
.fa-life-buoy:before,
.fa-life-saver:before,
.fa-support:before,
.fa-life-ring:before {
content: "\f1cd";
}
.fa-circle-o-notch:before {
content: "\f1ce";
}
.fa-ra:before,
.fa-resistance:before,
.fa-rebel:before {
content: "\f1d0";
}
.fa-ge:before,
.fa-empire:before {
content: "\f1d1";
}
.fa-git-square:before {
content: "\f1d2";
}
.fa-git:before {
content: "\f1d3";
}
.fa-y-combinator-square:before,
.fa-yc-square:before,
.fa-hacker-news:before {
content: "\f1d4";
}
.fa-tencent-weibo:before {
content: "\f1d5";
}
.fa-qq:before {
content: "\f1d6";
}
.fa-wechat:before,
.fa-weixin:before {
content: "\f1d7";
}
.fa-send:before,
.fa-paper-plane:before {
content: "\f1d8";
}
.fa-send-o:before,
.fa-paper-plane-o:before {
content: "\f1d9";
}
.fa-history:before {
content: "\f1da";
}
.fa-circle-thin:before {
content: "\f1db";
}
.fa-header:before {
content: "\f1dc";
}
.fa-paragraph:before {
content: "\f1dd";
}
.fa-sliders:before {
content: "\f1de";
}
.fa-share-alt:before {
content: "\f1e0";
}
.fa-share-alt-square:before {
content: "\f1e1";
}
.fa-bomb:before {
content: "\f1e2";
}
.fa-soccer-ball-o:before,
.fa-futbol-o:before {
content: "\f1e3";
}
.fa-tty:before {
content: "\f1e4";
}
.fa-binoculars:before {
content: "\f1e5";
}
.fa-plug:before {
content: "\f1e6";
}
.fa-slideshare:before {
content: "\f1e7";
}
.fa-twitch:before {
content: "\f1e8";
}
.fa-yelp:before {
content: "\f1e9";
}
.fa-newspaper-o:before {
content: "\f1ea";
}
.fa-wifi:before {
content: "\f1eb";
}
.fa-calculator:before {
content: "\f1ec";
}
.fa-paypal:before {
content: "\f1ed";
}
.fa-google-wallet:before {
content: "\f1ee";
}
.fa-cc-visa:before {
content: "\f1f0";
}
.fa-cc-mastercard:before {
content: "\f1f1";
}
.fa-cc-discover:before {
content: "\f1f2";
}
.fa-cc-amex:before {
content: "\f1f3";
}
.fa-cc-paypal:before {
content: "\f1f4";
}
.fa-cc-stripe:before {
content: "\f1f5";
}
.fa-bell-slash:before {
content: "\f1f6";
}
.fa-bell-slash-o:before {
content: "\f1f7";
}
.fa-trash:before {
content: "\f1f8";
}
.fa-copyright:before {
content: "\f1f9";
}
.fa-at:before {
content: "\f1fa";
}
.fa-eyedropper:before {
content: "\f1fb";
}
.fa-paint-brush:before {
content: "\f1fc";
}
.fa-birthday-cake:before {
content: "\f1fd";
}
.fa-area-chart:before {
content: "\f1fe";
}
.fa-pie-chart:before {
content: "\f200";
}
.fa-line-chart:before {
content: "\f201";
}
.fa-lastfm:before {
content: "\f202";
}
.fa-lastfm-square:before {
content: "\f203";
}
.fa-toggle-off:before {
content: "\f204";
}
.fa-toggle-on:before {
content: "\f205";
}
.fa-bicycle:before {
content: "\f206";
}
.fa-bus:before {
content: "\f207";
}
.fa-ioxhost:before {
content: "\f208";
}
.fa-angellist:before {
content: "\f209";
}
.fa-cc:before {
content: "\f20a";
}
.fa-shekel:before,
.fa-sheqel:before,
.fa-ils:before {
content: "\f20b";
}
.fa-meanpath:before {
content: "\f20c";
}
.fa-buysellads:before {
content: "\f20d";
}
.fa-connectdevelop:before {
content: "\f20e";
}
.fa-dashcube:before {
content: "\f210";
}
.fa-forumbee:before {
content: "\f211";
}
.fa-leanpub:before {
content: "\f212";
}
.fa-sellsy:before {
content: "\f213";
}
.fa-shirtsinbulk:before {
content: "\f214";
}
.fa-simplybuilt:before {
content: "\f215";
}
.fa-skyatlas:before {
content: "\f216";
}
.fa-cart-plus:before {
content: "\f217";
}
.fa-cart-arrow-down:before {
content: "\f218";
}
.fa-diamond:before {
content: "\f219";
}
.fa-ship:before {
content: "\f21a";
}
.fa-user-secret:before {
content: "\f21b";
}
.fa-motorcycle:before {
content: "\f21c";
}
.fa-street-view:before {
content: "\f21d";
}
.fa-heartbeat:before {
content: "\f21e";
}
.fa-venus:before {
content: "\f221";
}
.fa-mars:before {
content: "\f222";
}
.fa-mercury:before {
content: "\f223";
}
.fa-intersex:before,
.fa-transgender:before {
content: "\f224";
}
.fa-transgender-alt:before {
content: "\f225";
}
.fa-venus-double:before {
content: "\f226";
}
.fa-mars-double:before {
content: "\f227";
}
.fa-venus-mars:before {
content: "\f228";
}
.fa-mars-stroke:before {
content: "\f229";
}
.fa-mars-stroke-v:before {
content: "\f22a";
}
.fa-mars-stroke-h:before {
content: "\f22b";
}
.fa-neuter:before {
content: "\f22c";
}
.fa-genderless:before {
content: "\f22d";
}
.fa-facebook-official:before {
content: "\f230";
}
.fa-pinterest-p:before {
content: "\f231";
}
.fa-whatsapp:before {
content: "\f232";
}
.fa-server:before {
content: "\f233";
}
.fa-user-plus:before {
content: "\f234";
}
.fa-user-times:before {
content: "\f235";
}
.fa-hotel:before,
.fa-bed:before {
content: "\f236";
}
.fa-viacoin:before {
content: "\f237";
}
.fa-train:before {
content: "\f238";
}
.fa-subway:before {
content: "\f239";
}
.fa-medium:before {
content: "\f23a";
}
.fa-yc:before,
.fa-y-combinator:before {
content: "\f23b";
}
.fa-optin-monster:before {
content: "\f23c";
}
.fa-opencart:before {
content: "\f23d";
}
.fa-expeditedssl:before {
content: "\f23e";
}
.fa-battery-4:before,
.fa-battery:before,
.fa-battery-full:before {
content: "\f240";
}
.fa-battery-3:before,
.fa-battery-three-quarters:before {
content: "\f241";
}
.fa-battery-2:before,
.fa-battery-half:before {
content: "\f242";
}
.fa-battery-1:before,
.fa-battery-quarter:before {
content: "\f243";
}
.fa-battery-0:before,
.fa-battery-empty:before {
content: "\f244";
}
.fa-mouse-pointer:before {
content: "\f245";
}
.fa-i-cursor:before {
content: "\f246";
}
.fa-object-group:before {
content: "\f247";
}
.fa-object-ungroup:before {
content: "\f248";
}
.fa-sticky-note:before {
content: "\f249";
}
.fa-sticky-note-o:before {
content: "\f24a";
}
.fa-cc-jcb:before {
content: "\f24b";
}
.fa-cc-diners-club:before {
content: "\f24c";
}
.fa-clone:before {
content: "\f24d";
}
.fa-balance-scale:before {
content: "\f24e";
}
.fa-hourglass-o:before {
content: "\f250";
}
.fa-hourglass-1:before,
.fa-hourglass-start:before {
content: "\f251";
}
.fa-hourglass-2:before,
.fa-hourglass-half:before {
content: "\f252";
}
.fa-hourglass-3:before,
.fa-hourglass-end:before {
content: "\f253";
}
.fa-hourglass:before {
content: "\f254";
}
.fa-hand-grab-o:before,
.fa-hand-rock-o:before {
content: "\f255";
}
.fa-hand-stop-o:before,
.fa-hand-paper-o:before {
content: "\f256";
}
.fa-hand-scissors-o:before {
content: "\f257";
}
.fa-hand-lizard-o:before {
content: "\f258";
}
.fa-hand-spock-o:before {
content: "\f259";
}
.fa-hand-pointer-o:before {
content: "\f25a";
}
.fa-hand-peace-o:before {
content: "\f25b";
}
.fa-trademark:before {
content: "\f25c";
}
.fa-registered:before {
content: "\f25d";
}
.fa-creative-commons:before {
content: "\f25e";
}
.fa-gg:before {
content: "\f260";
}
.fa-gg-circle:before {
content: "\f261";
}
.fa-tripadvisor:before {
content: "\f262";
}
.fa-odnoklassniki:before {
content: "\f263";
}
.fa-odnoklassniki-square:before {
content: "\f264";
}
.fa-get-pocket:before {
content: "\f265";
}
.fa-wikipedia-w:before {
content: "\f266";
}
.fa-safari:before {
content: "\f267";
}
.fa-chrome:before {
content: "\f268";
}
.fa-firefox:before {
content: "\f269";
}
.fa-opera:before {
content: "\f26a";
}
.fa-internet-explorer:before {
content: "\f26b";
}
.fa-tv:before,
.fa-television:before {
content: "\f26c";
}
.fa-contao:before {
content: "\f26d";
}
.fa-500px:before {
content: "\f26e";
}
.fa-amazon:before {
content: "\f270";
}
.fa-calendar-plus-o:before {
content: "\f271";
}
.fa-calendar-minus-o:before {
content: "\f272";
}
.fa-calendar-times-o:before {
content: "\f273";
}
.fa-calendar-check-o:before {
content: "\f274";
}
.fa-industry:before {
content: "\f275";
}
.fa-map-pin:before {
content: "\f276";
}
.fa-map-signs:before {
content: "\f277";
}
.fa-map-o:before {
content: "\f278";
}
.fa-map:before {
content: "\f279";
}
.fa-commenting:before {
content: "\f27a";
}
.fa-commenting-o:before {
content: "\f27b";
}
.fa-houzz:before {
content: "\f27c";
}
.fa-vimeo:before {
content: "\f27d";
}
.fa-black-tie:before {
content: "\f27e";
}
.fa-fonticons:before {
content: "\f280";
}
.fa-reddit-alien:before {
content: "\f281";
}
.fa-edge:before {
content: "\f282";
}
.fa-credit-card-alt:before {
content: "\f283";
}
.fa-codiepie:before {
content: "\f284";
}
.fa-modx:before {
content: "\f285";
}
.fa-fort-awesome:before {
content: "\f286";
}
.fa-usb:before {
content: "\f287";
}
.fa-product-hunt:before {
content: "\f288";
}
.fa-mixcloud:before {
content: "\f289";
}
.fa-scribd:before {
content: "\f28a";
}
.fa-pause-circle:before {
content: "\f28b";
}
.fa-pause-circle-o:before {
content: "\f28c";
}
.fa-stop-circle:before {
content: "\f28d";
}
.fa-stop-circle-o:before {
content: "\f28e";
}
.fa-shopping-bag:before {
content: "\f290";
}
.fa-shopping-basket:before {
content: "\f291";
}
.fa-hashtag:before {
content: "\f292";
}
.fa-bluetooth:before {
content: "\f293";
}
.fa-bluetooth-b:before {
content: "\f294";
}
.fa-percent:before {
content: "\f295";
}
.fa-gitlab:before {
content: "\f296";
}
.fa-wpbeginner:before {
content: "\f297";
}
.fa-wpforms:before {
content: "\f298";
}
.fa-envira:before {
content: "\f299";
}
.fa-universal-access:before {
content: "\f29a";
}
.fa-wheelchair-alt:before {
content: "\f29b";
}
.fa-question-circle-o:before {
content: "\f29c";
}
.fa-blind:before {
content: "\f29d";
}
.fa-audio-description:before {
content: "\f29e";
}
.fa-volume-control-phone:before {
content: "\f2a0";
}
.fa-braille:before {
content: "\f2a1";
}
.fa-assistive-listening-systems:before {
content: "\f2a2";
}
.fa-asl-interpreting:before,
.fa-american-sign-language-interpreting:before {
content: "\f2a3";
}
.fa-deafness:before,
.fa-hard-of-hearing:before,
.fa-deaf:before {
content: "\f2a4";
}
.fa-glide:before {
content: "\f2a5";
}
.fa-glide-g:before {
content: "\f2a6";
}
.fa-signing:before,
.fa-sign-language:before {
content: "\f2a7";
}
.fa-low-vision:before {
content: "\f2a8";
}
.fa-viadeo:before {
content: "\f2a9";
}
.fa-viadeo-square:before {
content: "\f2aa";
}
.fa-snapchat:before {
content: "\f2ab";
}
.fa-snapchat-ghost:before {
content: "\f2ac";
}
.fa-snapchat-square:before {
content: "\f2ad";
}
.fa-pied-piper:before {
content: "\f2ae";
}
.fa-first-order:before {
content: "\f2b0";
}
.fa-yoast:before {
content: "\f2b1";
}
.fa-themeisle:before {
content: "\f2b2";
}
.fa-google-plus-circle:before,
.fa-google-plus-official:before {
content: "\f2b3";
}
.fa-fa:before,
.fa-font-awesome:before {
content: "\f2b4";
}
.fa-handshake-o:before {
content: "\f2b5";
}
.fa-envelope-open:before {
content: "\f2b6";
}
.fa-envelope-open-o:before {
content: "\f2b7";
}
.fa-linode:before {
content: "\f2b8";
}
.fa-address-book:before {
content: "\f2b9";
}
.fa-address-book-o:before {
content: "\f2ba";
}
.fa-vcard:before,
.fa-address-card:before {
content: "\f2bb";
}
.fa-vcard-o:before,
.fa-address-card-o:before {
content: "\f2bc";
}
.fa-user-circle:before {
content: "\f2bd";
}
.fa-user-circle-o:before {
content: "\f2be";
}
.fa-user-o:before {
content: "\f2c0";
}
.fa-id-badge:before {
content: "\f2c1";
}
.fa-drivers-license:before,
.fa-id-card:before {
content: "\f2c2";
}
.fa-drivers-license-o:before,
.fa-id-card-o:before {
content: "\f2c3";
}
.fa-quora:before {
content: "\f2c4";
}
.fa-free-code-camp:before {
content: "\f2c5";
}
.fa-telegram:before {
content: "\f2c6";
}
.fa-thermometer-4:before,
.fa-thermometer:before,
.fa-thermometer-full:before {
content: "\f2c7";
}
.fa-thermometer-3:before,
.fa-thermometer-three-quarters:before {
content: "\f2c8";
}
.fa-thermometer-2:before,
.fa-thermometer-half:before {
content: "\f2c9";
}
.fa-thermometer-1:before,
.fa-thermometer-quarter:before {
content: "\f2ca";
}
.fa-thermometer-0:before,
.fa-thermometer-empty:before {
content: "\f2cb";
}
.fa-shower:before {
content: "\f2cc";
}
.fa-bathtub:before,
.fa-s15:before,
.fa-bath:before {
content: "\f2cd";
}
.fa-podcast:before {
content: "\f2ce";
}
.fa-window-maximize:before {
content: "\f2d0";
}
.fa-window-minimize:before {
content: "\f2d1";
}
.fa-window-restore:before {
content: "\f2d2";
}
.fa-times-rectangle:before,
.fa-window-close:before {
content: "\f2d3";
}
.fa-times-rectangle-o:before,
.fa-window-close-o:before {
content: "\f2d4";
}
.fa-bandcamp:before {
content: "\f2d5";
}
.fa-grav:before {
content: "\f2d6";
}
.fa-etsy:before {
content: "\f2d7";
}
.fa-imdb:before {
content: "\f2d8";
}
.fa-ravelry:before {
content: "\f2d9";
}
.fa-eercast:before {
content: "\f2da";
}
.fa-microchip:before {
content: "\f2db";
}
.fa-snowflake-o:before {
content: "\f2dc";
}
.fa-superpowers:before {
content: "\f2dd";
}
.fa-wpexplorer:before {
content: "\f2de";
}
.fa-meetup:before {
content: "\f2e0";
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.sr-only-focusable:active,
.sr-only-focusable:focus {
position: static;
width: auto;
height: auto;
margin: 0;
overflow: visible;
clip: auto;
}
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Style" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_nojquery.css</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_nojquery_css</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Style</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>RenderJS Gadget ERP5 CSS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OfficeJS Chat</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="jiodev.js"></script>
<script src="gadget_global.js" ></script>
<script src="fast_priority_queue.js"></script>
<script src="gadget_erp5_page_chat_box.js"></script>
</head>
<body>
<div class="chat-box">
<div class="chat-left-panel">
<h3 class="center">Contacts</h3>
<ul class="contact-list">
</ul>
<div class="chat-form-panel">
<form class="sync-form">
<input type="submit" value="Synchronize!" />
</form>
<form class="edit-form">
<input type="submit" value="Edit contact!" />
</form>
<form class="join-form">
<input type="text" name="content" />
<input type="submit" value="Add new contact (join existing room as guest)!" />
</form>
<form class="make-form">
<input type="text" name="content" />
<input type="submit" value="Add new room (make new room as host)!" />
</form>
</div>
</div>
<div class="chat-right-panel">
<div class="chat-max-height-wrapper">
<div class="chat-right-panel-chat">
<h3 class="chat-title center"></h3>
<ul class="chat-list">
</ul>
<form class="send-form">
<input type="text" name="content" />
<input type="submit" name="send" value="Send!" />
</form>
</div>
</div>
</div>
</div>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
<string>contributor/person_module/1</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_chat_box.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_chat_box_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Page Chat Box</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*jslint nomen: true, indent: 2, maxerr: 300, maxlen: 80*/
/*global window, document, RSVP, rJS, FastPriorityQueue*/
(function (window, document, RSVP, rJS, FastPriorityQueue) {
"use strict";
/* Settings required:
* - jio_storage_description, or be redirected
* - user_email
*/
/* Abstract Data Types:
* message is a JSON object representing a message with all its metadata.
* chat is messageToChat(message) for displaying an element in the chat log.
* Message Types:
* message is a typical message, with an author and timestamp.
* notification is a system-generated message, with no author.
* bundle is a list of new messages to be added to the local archive.
* request is a dictionary of the times of the last messages of each peer.
*/
/* Program Workflow:
* JIO storage (the "archive") can only bepag accessed through two functions:
* - getLocalArchive takes the archive and stores it in the list
* - storeArchive stores a message in the archive
* Local storage (the "list") is always in sorted order and
* can only be accessed through three functions:
* - sendLocalArchive takes the list and sends it over WebRTC
* - storeList stores a message in the list
* - refreshChat sorts the entire list
* The displayed chat log can only be accessed through two functions:
* - appendMessage turns a message into a chat and appends it to the log
* - refreshChat takes the list to overwrite chat log
* Peer interaction via WebRTC can only be accessed through four functions:
* - deployMessage sends a message over WebRTC
* - getMessage gets a message over WebRTC
* - sendLocalArchive sends the list over WebRTC
* - getRemoteArchive gets a list over WebRTC
*/
/* Summary:
* - getLocalArchive takes archive, calls storeList, appendMessage on each
* - sendLocalArchive sends the list to peer
* - getRemoteArchive gets a list from peer, calls storeArchive on each
* - deployMessage sends a message to peer, calls getMessage
* - getMessage gets peer, calls storeArchive, storeList, appendMessage
* - storeArchive stores a message in the archive
* - storeList stores a message in the list
* - appendMessage calls messageToChat, appends it to the chat log
* - refreshChat takes the list, overwrites the chat log
*/
function logError(error) {
console.log(error);
}
function logQueue(action) {
return new RSVP.Queue()
.push(function () {
return action;
})
.push(null, logError);
}
function styleElementByQuery(gadget, query, style) {
gadget.element
.querySelector(query).style.display = style;
}
function resetInputValue(element) {
const value = element.value;
element.value = "";
return value;
}
function getQueryValue(query_list, query_string) {
for (let i = 0, i_len = query_list.length; i < i_len; i++) {
const query = query_list[i];
if (query_string.indexOf(query + "=") !== -1) {
const start = query_string.indexOf(query + "=") + query.length + 1;
let end = query_string.indexOf("&", start);
if (end === -1) {
end = query_string.length;
}
return query_string.slice(start, end);
}
}
return null;
}
function setQueryValue(query_list, query_string, element) {
const value = getQueryValue(query_list, query_string);
if (value) {
element.value = value;
}
}
function pollUntilNotNull(gadget, delay_ms, timeout_ms,
nullableFunction, callbackFunction) {
if (callbackFunction === undefined) {
callbackFunction = function () {};
}
return new RSVP.Queue()
.push(function () {
return nullableFunction();
})
.push(function (result) {
if (result !== null) {
return callbackFunction(result);
} else {
return RSVP.any([
RSVP.timeout(timeout_ms),
promiseDoWhile(function () {
return new RSVP.Queue()
.push(function () {
return RSVP.delay(delay_ms);
})
.push(function () {
return nullableFunction();
})
.push(function (result) {
if (result === null) {
return null;
} else {
return callbackFunction(result);
}
})
.push(function (nullable) {
return nullable === null;
})
.push(null, logError);
})
]);
}
});
}
/* Translate an arbitrary name to a querySelector-safe ID.
* Parameters:
* - name: the name to use as an ID
* Returns: name with all non-alphanumeric characters replaced by underscores
* Effects: none
*/
function nameToRoom(name) {
return name.replace(/\W/gi, "_");
}
/* Check if a string ends with another string.
* Parameters:
* - string: the string that may or may not end with the suffix string
* - suffix: the substring that may or may not be in the end of the string
* Returns: true if string ends with suffix, otherwise false
* Effects: none
*/
function stringEndsWith(string, suffix) {
return string.indexOf(suffix, string.length - suffix.length) !== -1;
}
/* Get the creation epoch time of a message.
* Parameters:
* - message: the message whose creation time is desired
* Returns: the creation time of the message, in milliseconds since 1970.
* Effects: none
*/
function getTime(message) {
return new Date(message.time).getTime();
}
/* Check if a message is new.
* Parameters:
* - message: the message that may or may not be new
* - last_time: the time in milliseconds since 1970 that splits old from new
* Returns: true if the message was created after last_time, otherwise false
* Effects: none
*/
function isNewMessage(message, last_time) {
return last_time === undefined || last_time < getTime(message);
}
/* Check if two messages are the same.
* Parameters:
* - lhs, rhs: the two messages that may or may not be the same
* Returns: true if both messages exist and have equivalent properties.
* Effects: none
*/
function isSameMessage(lhs, rhs) {
return lhs !== undefined && rhs !== undefined
&& lhs.name === rhs.name && lhs.content === rhs.content
&& lhs.room === rhs.room && getTime(lhs) === getTime(rhs);
}
/* Keep messages in chronological order, for FastPriorityQueue.
* Parameters:
* - ascending_order: true if the order is ascending, otherwise false
* Returns: a comparator for sorting messages using FastPriorityQueue
* Effects: none
*/
function messageTimeCompare(ascending_order) {
if (ascending_order) {
return function (lhs, rhs) {
return getTime(lhs) < getTime(rhs);
};
}
return function (lhs, rhs) {
return getTime(lhs) > getTime(rhs);
};
}
/* Keep messages in chronologically ascending order, for refreshChat.
* Parameters:
* - lhs, rhs: two messages to compare during the sorting algorithm
* Returns: the correct value for sorting messages using sort()
* Effects: none
*/
function messageTimeComparator(lhs, rhs) {
if (getTime(lhs) < getTime(rhs)) {
return -1;
}
if (getTime(lhs) > getTime(rhs)) {
return 1;
}
return 0;
}
/* Translate a JSON message to an HTML chat element.
* Parameters:
* - message: the JavaScript object to display in HTML
* Returns: a HTML representation of the given message
* Effects: none
*/
function messageToChat(message) {
var matches, i, i_len, j, j_len, image, link,
link_string, dot_count, is_image, absolute_url,
chat = document.createElement("li"),
image_extensions = [".jpg", ".png", ".gif", ".bmp", ".tif", ".svg"],
url_regex =
/((?:https?:\/\/)?(?:[\-a-zA-Z0-9_~\/]+\.)+[\-a-zA-Z0-9_~#?&=\/]+)/g;
switch (message.type) {
case "bundle":
break;
case "notification":
chat.appendChild(document.createTextNode(message.content));
chat.style.color = message.colour;
return chat;
case "message":
// Put message in the format "[3/24/2017, 11:30:52 AM] user: message"
chat.appendChild(document.createTextNode("["
+ new Date(message.time).toLocaleString() + "] " + message.name + ": "
));
// Loop through all potential URLs in the message
// matches[i] is the non-link content, matches[i + 1] is the actual link
matches = message.content.split(url_regex);
for (i = 0, i_len = matches.length - 1; i < i_len; i += 2) {
chat.appendChild(document.createTextNode(matches[i]));
link_string = matches[i + 1];
dot_count = link_string.match(/\./g).length;
// If 2d + 1 >= L, then the potential URL has only single letters,
// so ignore it to eliminate acronyms like e.g., i.e., U.S.A., etc.
if (2 * dot_count + 1 >= link_string.length) {
chat.appendChild(document.createTextNode(link_string));
} else {
is_image = false;
// Add a protocol to transform relative URLs into absolute URLs
if (link_string.indexOf(":") !== -1) {
absolute_url = link_string;
} else {
absolute_url = "http://" + link_string;
}
// Create an image if the URL ends with one of the image extensions
for (j = 0, j_len = image_extensions.length; j < j_len; j += 1) {
if (stringEndsWith(link_string, image_extensions[j])) {
image = document.createElement("img");
image.src = absolute_url;
chat.appendChild(image);
is_image = true;
break;
}
}
// Otherwise, create a link
if (!is_image) {
link = document.createElement("a");
link.href = absolute_url;
link.innerHTML = link_string;
chat.appendChild(link);
}
}
}
// Add the last non-link content and return the resulting chat element
chat.appendChild(document.createTextNode(matches[matches.length - 1]));
chat.style.color = message.colour;
return chat;
}
}
rJS(window)
.setState({
name: null,
auth: null,
dropbox_folder: null,
erp5_url: null,
room: null,
element: null,
initialized: false,
// A set of names of current rooms; as a guest, only notify that
// you have joined once you have received the host's remote archive,
// so that your notification doesn't override all previous messages
// room_set["room"] = false if joined, true if also notified
room_set: {},
// A dictionary of messages based on the current room, e.g.
// message_list_dict["room"] = [message1, message2, ...]
message_list_dict: {},
// A dictionary of epoch times of the last messages received, e.g.
// last_message_dict["room"]["name"] = 1234567890
last_message_dict: {},
alert_icon: "https://pricespy.co.nz/favicon.ico",
default_icon: "https://softinst75770.host.vifib.net/"
+ "erp5/web_site_module/web_chat/favicon.ico"
})
.declareService(function () {
var gadget = this;
return gadget.render();
})
// The following functions are all acquired from erp5_launcher_nojqm.js
.declareAcquiredMethod("updateHeader", "updateHeader")
.declareAcquiredMethod("getSetting", "getSetting")
.declareAcquiredMethod("requireSetting", "requireSetting")
.declareAcquiredMethod("jio_allDocs", "jio_allDocs")
.declareAcquiredMethod("jio_put", "jio_put")
.declareAcquiredMethod("jio_repair", "jio_repair")
/* Get the contact's jIO ID from their email address.
* Parameters:
* - param_list[0]: the email address of the contact
* Returns: the ID of the contact if found in jIO storage, otherwise null
* Effects: none
*/
.allowPublicAcquisition("getContactByEmail", function (param_list) {
var gadget = this,
email = param_list[0], i, i_len;
return new RSVP.Queue()
.push(function () {
return gadget.jio_allDocs({
limit: [0, 1000000],
query: 'portal_type: "Person"',
sort_on: [["last_modified", "descending"]],
select_list: ["default_email_coordinate_text"],
});
})
.push(function (person_list) {
for (i = 0, i_len = person_list.data.total_rows; i < i_len; i += 1) {
if (person_list.data.rows[i].value
.default_email_coordinate_text === email) {
return person_list.data.rows[i].id;
}
}
return null;
})
.push(null, logError);
})
/* Render the gadget
* Parameters:
* - get setting: jio_storage_description, user_email
* Effects:
* - update header, page_title to "OfficeJS Chat"
* - redirect if no jIO storage available
* - join the user home room whose name is the user name
*/
.declareMethod("render", function () {
var gadget = this;
return gadget.requireSetting(
"jio_storage_description",
"jio_configurator",
new RSVP.Queue()
.push(function () {
return gadget.updateHeader({
page_title: "OfficeJS Chat",
});
})
.push(function () {
return gadget.getSetting("user_email");
})
.push(function (email) {
gadget.state.room = email.replace(/[^0-9a-z\-]/gi, "-");
gadget.state.name = gadget.state.room;
gadget.element.querySelector(".send-form input[type='text']").onfocus =
function () {
return logQueue(
gadget.notifyStatus(gadget.state.room, false));
};
})
.push(function () {
return gadget.createContact({
room: gadget.state.room, role: "host"});
})
);
})
/* Send a message via the appropriate WebRTC channel.
* Parameters:
* - message: the message to send, whose room is the selected WebRTC room
* - source: the WebRTC data channel that sent the original message
* Returns: nothing
* Effects: calls sendMessage on the WebRTC gadget
*/
.declareMethod("sendMessage", function (message, source) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget("webrtc_gadget_" + message.room);
})
.push(function (webrtc_gadget) {
return webrtc_gadget.sendMessage.apply(
webrtc_gadget, [message, source]);
})
.push(null, logError);
})
.declareMethod("chooseRoom", function (param_dict) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.declareGadget(
"gadget_erp5_chat_webrtc.html", {
scope: "webrtc_gadget_" + param_dict.room});
})
.push(function (webrtc_gadget) {
var fields = webrtc_gadget.element
.querySelector(".contact-form").elements;
fields.dropbox_folder.value = param_dict.dropbox_folder
|| gadget.state.dropbox_folder;
fields.erp5_url.value = param_dict.erp5_url
|| gadget.state.erp5_url;
webrtc_gadget.element.querySelector(".auth-form")
.auth.value = param_dict.auth || gadget.state.auth;
gadget.element.querySelector(".chat-right-panel").insertBefore(
webrtc_gadget.element,
gadget.element.querySelector(".chat-max-height-wrapper"));
webrtc_gadget.state.login_dict = {
room: param_dict.room,
name: gadget.state.name,
role: param_dict.role,
};
webrtc_gadget.state.dataChannelOnopen = function () {
return gadget.changeRoom(param_dict.room);
};
webrtc_gadget.state.dataChannelOnmessage =
function (event) {
var message = JSON.parse(event.data);
var source = event.srcElement;
if (message.type === "notification"
|| message.type === "message") {
return new RSVP.Queue()
.push(function () {
return gadget.getMessage(message);
})
.push(function () {
if (param_dict.role === "host") {
return webrtc_gadget.sendMessage(message, source);
} else {
return;
}
})
.push(null, logError);
} else if (message.type === "bundle") {
if (param_dict.role === "host") {
webrtc_gadget.state.archive_amount += 1;
if (webrtc_gadget.state.archive_amount >=
webrtc_gadget.state.guest_amount) {
webrtc_gadget.state.archive_amount = 0;
return new RSVP.Queue()
.push(function () {
return gadget.getRemoteArchive(message);
})
.push(function () {
return gadget.requestRequest();
})
.push(null, logError);
}
} else {
return gadget.getRemoteArchive(message);
}
} else if (message.type === "request") {
return gadget.sendLocalArchive(message, source);
} else if (message.type === "doubler") {
return gadget.sendRequest();
}
};
return webrtc_gadget.render();
})
.push(null, logError);
})
/*
return gadget.createJio({
remote: fields.remote.value,
dropbox_folder: fields.remote_dropbox_folder.value,
erp5_url: fields.remote_erp5_url.value,
dav_url: fields.remote_dav_url.value,
dav_user: fields.remote_dav_user.value,
dav_pass: fields.remote_dav_pass.value,
});
})
.push(function () {
return gadget.jio_repair();
})
*/
/*
.declareMethod("createJio", function (param_dict) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
var remote_config = {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: "officejs-chat",
},
},
};
var remote_post = true;
// We want the remote ERP5 storage to be canonical, so true for ERP5.
// Post is not implemented in drivetojiomapping, so false for others.
switch (param_dict.remote) {
case "dropbox":
var start = document.URL.indexOf("access_token=") + 13;
var end = document.URL.indexOf("&", start);
var token = document.URL.slice(start, end);
remote_config = {
type: "query",
sub_storage: {
type: "drivetojiomapping",
sub_storage: {
type: "dropbox",
access_token: token,
root: "dropbox",
},
},
};
remote_post = false;
break;
case "dav":
remote_config = {
type: "query",
sub_storage: {
type: "drivetojiomapping",
sub_storage: {
type: "dav",
url: param_dict.dav_url,
basic_login: btoa(param_dict.dav_user
+ ":" + param_dict.dav_pass),
with_credentials: true,
},
},
};
remote_post = false;
break;
case "erp5":
remote_config = {
type: "erp5",
url: (new URI("hateoas"))
.absoluteTo(param_dict.erp5_url).toString(),
default_view_reference: "view",
};
remote_post = true;
break;
}
return gadget.createJio({
type: "replicate",
use_remote_post: remote_post,
conflict_handling: 2,
query: {
limit: [0, 1000000000],
query: "portal_type: "Person" OR portal_type: "Text Post"",
},
local_sub_storage: {
type: "query",
sub_storage: {
type: "uuid",
sub_storage: {
type: "indexeddb",
database: "officejs-chat",
},
},
},
remote_sub_storage: remote_config,
});
})
.push(null, logError);
})
*/
// Get all messages stored in archive and add them to the list in order,
// using storeList because the messages are already in the archive
.declareMethod("getLocalArchive", function () {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.jio_allDocs({
limit: [0, 1000000],
query: 'portal_type: "Text Post"',
sort_on: [["last_modified", "descending"]],
select_list: ["content"],
});
})
.push(function (message_list) {
var message_queue = new FastPriorityQueue(messageTimeCompare(true)),
i, i_len;
for (i = 0, i_len = message_list.data.total_rows; i < i_len; i += 1) {
try {
var message = JSON.parse(list[i]);
if (message && typeof message === "object") {
message_queue.add(message);
}
} catch (error) {}
}
var promise_list = [];
var message_dict = gadget.state.last_message_dict;
while (!message_queue.isEmpty()) {
var message = message_queue.poll();
if (message.room in gadget.state.room_set) {
promise_list.push(gadget.storeList(message));
promise_list.push(gadget.appendMessage(message));
}
}
gadget.state.initialized = true;
return RSVP.all(promise_list);
})
.push(null, logError);
})
// Send all requested messages in the list, in sorted order, to peer
.declareMethod("sendLocalArchive", function (request, source) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
var request_dict = {};
var room;
for (room in request.content.room_set) {
if (request.content.room_set.hasOwnProperty(room)
&& gadget.state.message_list_dict[room]) {
request_dict[room] = [];
var list = gadget.state.message_list_dict[room], i, i_len;
for (i = 0, i_len = list.length; i < i_len; i += 1) {
if (isNewMessage(
list[i], request.content.dict[room][list[i].name])) {
request_dict[room].push(list[i]);
}
}
}
}
return gadget.createMessage({type: "bundle", content: request_dict});
})
.push(function (message) {
return gadget.sendMessage(message, source);
})
.push(null, logError);
})
// Get all new messages from the sorted list of peer,
// add them to both the archive and list using storeArchive,
// and refresh, dedupe, and resort the list using refreshChat
.declareMethod("getRemoteArchive", function (bundle) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
var promise_list = [], room;
for (room in bundle.content) {
if (bundle.content.hasOwnProperty(room)) {
var list = gadget.state.message_list_dict[room];
var remote_list = bundle.content[room], i, i_len;
for (i = 0, i_len = remote_list.length; i < i_len; i += 1) {
promise_list.push(gadget.storeList(remote_list[i]));
promise_list.push(gadget.storeArchive(remote_list[i]));
}
}
}
return RSVP.all(promise_list);
})
.push(function () {
if (!gadget.state.room_set
[gadget.state.room]) {
gadget.state.room_set
[gadget.state.room] = true;
return gadget.deployMessage({
type: "notification",
content: gadget.state.name + " has joined.",
colour: "orange",
});
} else {
return;
}
})
.push(function () {
return gadget.refreshChat();
})
.push(null, logError);
})
// Ask a peer to send over a request
.declareMethod("requestRequest", function () {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.createMessage({type: "doubler"});
})
.push(function (message) {
return gadget.sendMessage(message);
})
.push(null, logError);
})
// Send a request to update the local archive
.declareMethod("sendRequest", function () {
var gadget = this;
return pollUntilNotNull(gadget, 1000, 30000, function () {
return gadget.state.initialized;
}, function () {
return new RSVP.Queue()
.push(function () {
return gadget.createMessage({
type: "request",
content: {
room_set: gadget.state.room_set,
dict: gadget.state.last_message_dict,
},
});
})
.push(function (message) {
return gadget.sendMessage(message);
})
.push(null, logError);
});
})
// Create new message from its parameters
.declareMethod("createMessage", function (param_dict) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return {
type: param_dict.type || "message",
name: param_dict.name || gadget.state.name,
room: param_dict.room || gadget.state.room,
time: param_dict.time || new Date(),
content: param_dict.content || "",
colour: param_dict.colour || "black",
};
})
.push(null, logError);
})
// Create new message and send it to peer
.declareMethod("deployMessage", function (param_dict) {
var gadget = this;
var message;
return new RSVP.Queue()
.push(function () {
return gadget.createMessage(param_dict);
})
.push(function (result) {
message = result;
return gadget.storeList(message);
})
.push(function () {
return gadget.appendMessage(message);
})
.push(function () {
return gadget.storeArchive(message);
})
.push(function () {
return gadget.sendMessage(message);
})
.push(null, logError);
})
// Create new notification and keep it on own machine
.declareMethod("deployNotification", function (param_dict) {
var gadget = this;
var notification;
return new RSVP.Queue()
.push(function () {
param_dict.type = "notification";
return gadget.createMessage(param_dict);
})
.push(function (result) {
notification = result;
return gadget.storeList(notification);
})
.push(function () {
return gadget.appendMessage(notification);
})
.push(null, logError);
})
// Get message from peer, store it in archive and list
.declareMethod("getMessage", function (message) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.notifyStatus(message.room, true);
})
.push(function () {
return gadget.storeList(message);
})
.push(function () {
return gadget.appendMessage(message);
})
.push(function () {
return gadget.storeArchive(message);
})
.push(null, logError);
})
// Store message in the archive
.declareMethod("storeArchive", function (message) {
var gadget = this;
var id = message.room + "_" + message.name + "_"
+ getTime(message).toString();
return new RSVP.Queue()
.push(function () {
return gadget.jio_put(id, {
portal_type: "Text Post",
parent_relative_url: "post_text_module",
reference: id,
author: message.name,
date_ms: getTime(message),
content: JSON.stringify(message),
});
})
.push(null, logError);
})
// Add message to the list
.declareMethod("storeList", function (message) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
if (isNewMessage(message,
gadget.state.last_message_dict
[message.room][message.name])) {
gadget.state.last_message_dict
[message.room][message.name]
= getTime(message);
}
gadget.state
.message_list_dict[message.room].push(message);
})
.push(null, logError);
})
// Appends a message to the chat box
.declareMethod("appendMessage", function (message) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
if (message.room === gadget.state.room) {
var container = gadget.element.querySelector(".chat-list");
container.appendChild(messageToChat(message));
container.scrollTop = container.scrollHeight;
/*
gadget.element
.querySelector(".chat-right-panel").style.height =
gadget.element
.querySelector(".chat-left-panel").style.height;
*/
}
})
.push(null, logError);
})
// Sort the list, dedupe, and overwrite the chat box,
// efficient because the archive is originally sorted
.declareMethod("refreshChat", function () {
var gadget = this;
return new RSVP.Queue()
.push(function () {
var container = gadget.element.querySelector(".chat-list");
while (container.firstChild) {
container.removeChild(container.firstChild);
}
var old_list = gadget.state.message_list_dict
[gadget.state.room];
old_list.sort(messageTimeComparator);
var new_list = [];
var last_message, i, i_len;
for (i = 0, i_len = old_list.length; i < i_len; i += 1) {
var message = old_list[i];
if (isSameMessage(last_message, message)) {
continue;
}
last_message = message;
new_list.push(message);
container.appendChild(messageToChat(message));
}
gadget.state.message_list_dict
[gadget.state.room] = new_list;
container.scrollTop = container.scrollHeight;
/*
gadget.element
.querySelector(".chat-right-panel").style.height =
gadget.element
.querySelector(".chat-left-panel").style.height;
*/
})
.push(null, logError);
})
// Notify when a new message appears and denotify when it is seen
.declareMethod("notifyStatus", function (room, notify) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
var favicon_url;
var class_name;
if (notify) {
favicon_url = gadget.state.alert_icon;
class_name = "notify";
} else {
favicon_url = gadget.state.default_icon;
class_name = "current";
}
var link = document.querySelector("link[rel*='icon']")
|| document.createElement("link");
link.type = "image/x-icon";
link.rel = "shortcut icon";
link.href = favicon_url;
document.head.appendChild(link);
var contact = gadget.element.querySelector("#chat-contact-" + room);
contact.className = class_name;
})
.push(null, logError);
})
// Join a different room
.declareMethod("changeRoom", function (room) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
styleElementByQuery(gadget,
"#webrtc-gadget-" + gadget.state.room, "none");
styleElementByQuery(gadget, ".chat-right-panel-chat", "flex");
gadget.element.querySelector(".chat-title")
.textContent = "Room: " + room;
gadget.state.room = room;
if (!(room in gadget.state.room_set)) {
return new RSVP.Queue()
.push(function () {
gadget.state.room_set[room] = false;
gadget.state.message_list_dict[room] = [];
gadget.state.last_message_dict[room] = {};
})
.push(function () {
return gadget.getLocalArchive();
})
.push(function () {
return gadget.requestRequest();
})
.push(null, logError);
}
})
.push(function () {
return gadget.refreshChat();
})
.push(null, logError);
})
// Create a new contact
.declareMethod("createContact", function (param_dict) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.appendContact(param_dict.room);
})
.push(function () {
return gadget.chooseRoom(param_dict);
})
.push(function () {
return gadget.editContact(param_dict.room);
})
.push(null, logError);
})
// Add a contact as some HTML element
.declareMethod("appendContact", function (room) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
if (!room.trim()) {
throw "An invisible name is not allowed! You couldn't click on it!";
}
if (gadget.element.querySelector("#chat-contact-" + room)) {
throw "A contact with the same name already exists!";
}
var contact = document.createElement("li");
contact.appendChild(document.createTextNode(room));
contact.setAttribute("id", "chat-contact-" + room);
gadget.element.querySelector(".contact-list").appendChild(contact);
gadget.element.querySelector(
"#chat-contact-" + gadget.state.room).className = "";
contact.className = "current";
contact.addEventListener("click", function (event) {
return new RSVP.Queue()
.push(function () {
return gadget.notifyStatus(room, false);
})
.push(function () {
gadget.element.querySelector(
"#chat-contact-" + gadget.state.room).className = "";
gadget.element.querySelector(
"#chat-contact-" + room).className = "current";
})
.push(function () {
if (gadget.state.room_set[room]) {
return gadget.changeRoom(room);
} else {
return gadget.editContact(room);
}
})
.push(null, logError);
}, false);
return;
})
})
// Edit a contact in the right panel
.declareMethod("editContact", function (room) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
styleElementByQuery(gadget, ".chat-right-panel-chat", "none");
styleElementByQuery(gadget,
"#webrtc-gadget-" + gadget.state.room, "none");
styleElementByQuery(gadget, "#webrtc-gadget-" + room, "block");
gadget.state.room = room;
gadget.element.querySelector(".chat-title")
.textContent = "Contact: " + room;
})
.push(null, logError);
})
// Parse chat commands
.declareMethod("parseCommand", function (chat) {
var gadget = this;
return new RSVP.Queue()
.push(function () {
var split = chat.slice(1).split(" ");
var command = split.shift();
var argument = split.join(" ");
switch (command) {
case "join":
if (gadget.state.room_set[argument]) {
return gadget.changeRoom(argument);
} else {
return gadget.deployNotification({
content: "You must first be connected to room '"
+ argument + "' via WebRTC to join it!",
colour: "red",
});
}
case "leave":
return new RSVP.Queue()
.push(function () {
if (gadget.state.room === gadget.state.name) {
return gadget.deployNotification({
content: "You cannot leave your own room!",
colour: "red"
});
} else {
delete gadget.state.room_set
[gadget.state.room];
return gadget.deployMessage({
type: "notification",
content: gadget.state.name + " has quit.",
colour: "orange",
});
}
})
.push(function () {
return gadget.changeRoom(gadget.state.name);
})
.push(null, logError);
case "quit":
window.location.reload();
break;
case "help":
var help_list = [
"Available commands:",
"/join [room]: connects you to [room]",
"/help: displays this help box",
"/leave: disconnects you from the current room",
"/quit: disconnects from the chat and refreshes the page",
];
var promise_list = [];
return new RSVP.Queue()
.push(function () {
var i, i_len;
for (i = 0, i_len = help_list.length; i < i_len; i += 1) {
promise_list.push(gadget.deployNotification({
content: help_list[i],
colour: "green",
}));
}
return RSVP.all(promise_list);
})
.push(null, logError);
}
});
})
.onEvent("submit", function (event) {
var gadget = this;
switch (event.target.className) {
case "sync-form":
return new RSVP.Queue()
.push(function () {
return gadget.jio_repair();
})
.push(function () {
return gadget.getLocalArchive();
})
.push(function () {
return gadget.requestRequest();
})
.push(null, logError);
case "edit-form":
return gadget.editContact(gadget.state.room);
case "join-form":
var contact = resetInputValue(event.target.elements.content);
return gadget.createContact({room: contact, role: "guest"});
case "make-form":
var room = resetInputValue(event.target.elements.content);
return gadget.createContact({room: room, role: "host"});
case "send-form":
var content = resetInputValue(event.target.elements.content);
if (content.indexOf("/") === 0) {
return gadget.parseCommand(content);
} else {
return gadget.deployMessage({content: content});
}
}
})
.declareService(function () {
var gadget = this;
return new RSVP.Queue()
.push(function () {
return promiseEventListener(window, "beforeunload", true);
})
.push(function () {
if (gadget.state.initialized) {
var promise_list = [], room;
for (room in gadget.state.room_set) {
if (gadget.state.room_set.hasOwnProperty(room)
&& gadget.state.room_set[room]) {
promise_list.push(gadget.deployMessage({
type: "notification",
content: gadget.state.name + " has quit.",
room: room,
colour: "orange",
}));
}
}
return RSVP.all(promise_list);
} else {
return;
}
})
.push(null, logError);
});
}(window, document, RSVP, rJS, FastPriorityQueue));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
<string>contributor/person_module/1</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_chat_box.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_chat_box_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Page Chat Box JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>OfficeJS Chat Connect</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="gadget_erp5_page_chat_connect.js"></script>
</head>
<body>
<form class="connect-form">
<label>E-mail Address:</label>
<input type="email" name="user_email" required="required" />
<label>Default WebRTC Authentication Method:</label>
<br />
<input type="radio" name="default_webrtc_storage" value="erp5">ERP5</input>
<input type="radio" name="default_webrtc_storage" value="dropbox">Dropbox</input>
<br />
<label>Default Dropbox Folder:</label>
<input type="text" name="default_dropbox_folder" placeholder="/Apps/OfficeJS Chat/" />
<label>Default ERP5 URL:</label>
<input type="text" name="default_erp5_url", placeholder="https://softinst75770.host.vifib.net/erp5/webrtc_rooms_module/" />
<input type="submit" />
</form>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_chat_connect.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_chat_connect_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Chat Connect</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/* global window, RSVP, rJS */
/* jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
(function (window, RSVP, rJS) {
'use strict';
/* Settings modified:
* - user_email
* - default_webrtc_storage
* - default_dropbox_folder
* - default_erp5_url
*/
/* Store the given connection settings
* Parameters: all from the connect form,
* - user_email: an email, which acts as a username, example: eugene@abc.xyz
* - default_webrtc_storage: the type of storage to select by default
* for sharing WebRTC negotiations, example: dropbox
* - default_dropbox_folder: the Dropbox folder to select by default
* for sharing WebRTC negotiations, example: /Apps/OfficeJS Chat/
* - default_erp5_url: the ERP5 URL to select by default for sharing,
* example: https://softinst75770.host.vifib.net/erp5/web_site_module/
* Effects:
* - set setting: user_email, default_webrtc_storage,
* default_dropbox_folder, default_erp5_url
* - redirect to the front page
*/
function setConnectConfiguration(gadget, event) {
const fields = ['user_email', 'default_webrtc_storage',
'default_dropbox_folder', 'default_erp5_url'];
return new RSVP.Queue()
.push(function () {
const queue = new RSVP.Queue();
for (let i = 0, i_len = fields.length; i < i_len; i++) {
const field = fields[i];
if (event.target.hasOwnProperty(field) && event.target[field].value) {
queue.push(function () {
return gadget.setSetting(field, event.target[field].value);
});
}
}
return queue;
})
.push(function () {
return gadget.redirect();
});
}
rJS(window)
// Neither state to set nor ready to initialize
// The following functions are all acquired from erp5_launcher_nojqm.js
.declareAcquiredMethod('updateHeader', 'updateHeader')
.declareAcquiredMethod('redirect', 'redirect')
.declareAcquiredMethod('setSetting', 'setSetting')
/* Render the gadget
* Parameters: nothing
* Effects:
* - update header, page_title to 'Connect to Chat'
*/
.declareMethod('render', function () {
var gadget = this;
return gadget.updateHeader({
page_title: 'Connect to Chat',
submit_action: true
});
})
/* Manually click submit button when the right button is clicked,
* so that HTML5 form validation is automatically used
*/
.declareMethod('triggerSubmit', function (event) {
this.element.querySelector('input[type="submit"]').click();
})
// Call setConnectionConfiguration when either proceed button is clicked
.onEvent('submit', function (event) {
const gadget = this;
return setConnectConfiguration(gadget, event);
});
}(window, RSVP, rJS));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_chat_connect.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_chat_connect_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Chat Connect JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>jIO Configurator Gadget</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="gadget_erp5_page_contact_add.js"></script>
</head>
<body>
</body>
</html>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>contributor/person_module/1</string>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>creators</string> </key>
<value>
<tuple>
<string>cedric.le.ninivin</string>
</tuple>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_contact_add.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_contact_add_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>modification_date</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1455284351.46</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Contact Add</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*globals window, RSVP, rJS*/
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80*/
(function (window, RSVP, rJS) {
'use strict';
rJS(window)
// Neither state to set nor ready to initialize
// The following functions are all acquired from erp5_launcher_nojqm.js
.declareAcquiredMethod('updateHeader', 'updateHeader')
.declareAcquiredMethod('redirect', 'redirect')
.declareAcquiredMethod('getSetting', 'getSetting')
.declareAcquiredMethod('jio_post', 'jio_post')
/* Render the gadget
* Parameters: nothing
* XXX: portal_Type, document_title, parent_relative_url
* Returns: nothing
* Effects:
* - update header, page title to 'Add Contact'
* - post a new contact to jIO storage
*/
.declareMethod('render', function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.updateHeader({page_title: 'New Contact'});
})
.push(function () {
const doc = {
parent_relative_url: 'person_module',
portal_type: 'Person',
};
return gadget.jio_post(doc);
})
.push(function (id) {
console.log(id);
return gadget.redirect({
command: 'display',
options: {
jio_key: id,
page: 'jio_person_view',
},
});
});
});
}(window, RSVP, rJS));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>contributor/person_module/1</string>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>creators</string> </key>
<value>
<tuple>
<string>cedric.le.ninivin</string>
</tuple>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_contact_add.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_contact_add_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>modification_date</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1455284351.49</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Contact Add JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>OfficeJS Chat Contact List</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="gadget_erp5_page_contact_list.js"></script>
</head>
<body>
<div data-gadget-url="gadget_erp5_field_listbox_widget.html"
data-gadget-scope="listbox"
data-gadget-sandbox="public"></div>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>contributor/person_module/1</string>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>creators</string> </key>
<value>
<tuple>
<string>cedric.le.ninivin</string>
</tuple>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_contact_list.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_contact_list_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>modification_date</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1455284352.14</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Contact List</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*globals window, RSVP, rJS*/
/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80*/
(function (window, RSVP, rJS) {
'use strict';
/* Settings required:
* - jio_storage_description, or be redirected
*/
// XXX add more customization to allow reuse across different portal types
rJS(window)
// Neither state to set nor ready to initialize
// The following functions are all acquired from erp5_launcher_nojqm.js
.declareAcquiredMethod('updateHeader', 'updateHeader')
.declareAcquiredMethod('redirect', 'redirect')
.declareAcquiredMethod('getSetting', 'getSetting')
.declareAcquiredMethod('requireSetting', 'requireSetting')
.declareAcquiredMethod('jio_post', 'jio_post')
/* Render the gadget
* Parameters:
* - get setting: jio_storage_description
* Returns: nothing
* Effects:
* - update header, page title to 'Contact List'
* - call listbox.render hardcoded, not jio_general_query
* - redirect if no jIO storage available
*/
.declareMethod('render', function () {
const gadget = this;
return gadget.requireSetting(
'jio_storage_description',
'jio_configurator',
new RSVP.Queue()
.push(function () {
return gadget.updateHeader({
page_title: 'Contact List',
add_action: true,
});
})
.push(function () {
return gadget.getDeclaredGadget('listbox');
})
.push(function (listbox) {
return listbox.render({
gadget_title: 'Contact List',
gadget_query: {
limit: [0, 1000000],
query: 'portal_type: "Person"',
sort_on: [
['jio_configuration', 'descending'],
['modification_date', 'descending'],
],
select_list: [
'jio_configuration',
'default_email_coordinate_text',
'first_name',
'last_name',
],
},
column_list: [{
select: 'default_email_coordinate_text',
title: 'Email',
}, {
select: 'last_name',
title: 'Last Name',
}, {
select: 'first_name',
title: 'First Name',
}],
});
})
);
})
/* Add a new contact when the right button is clicked
* Parameters: nothing
* Returns: nothing
* Effects:
* - post a new contact to jIO storage
* - redirect to the contact view page
*/
.declareMethod('triggerSubmit', function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.jio_post({
parent_relative_url: 'person_module',
portal_type: 'Person',
});
})
.push(function (id) {
return gadget.redirect({
command: 'display',
options: {
jio_key: id,
page: 'jio_person_view',
},
});
});
});
}(window, RSVP, rJS));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>creators</string> </key>
<value>
<tuple>
<string>cedric.le.ninivin</string>
</tuple>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_contact_list.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_contact_list_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>modification_date</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1455284352.18</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Contact List JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>jIO DAV Configurator Gadget</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="gadget_erp5_page_jio_dav_configurator.js"></script>
</head>
<body>
<article class="configurator-erp5">
<header class="configurator-header">
<h3>
<span class="fa fa-database" aria-hidden="true"></span>
Your WebDAV Parameters
</h3>
</header>
<section class="configurator-section">
<form>
<label>Connection URL:</label>
<input type="url" name="dav_url" required="required" placeholder="https://softinst75722.host.vifib.net/share" />
<label>WebDAV username:</label>
<input type="text" name="dav_user" required="required" placeholder="eyqs" />
<label>WebDAV password:</label>
<input type="password" name="dav_pass" required="required" placeholder="correct horse battery staple" />
<button class="grid-box configurator-button" type="submit">Connect</button>
</form>
</section>
</article>
<div data-gadget-url="gadget_erp5_page_jio_document_configurator.html"
data-gadget-scope="document_configurator"
data-gadget-sandbox="public"></div>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_jio_dav_configurator.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_jio_dav_configurator_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>jIO DAV Configurator Gadget</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/* global window, RSVP, rJS, btoa */
/* jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
(function (window, RSVP, rJS, btoa) {
'use strict';
/* Settings required:
* - jio_general_query
* - user_email, or be redirected
* Settings used:
* - connect_page, default: ''
* - indexeddb_database_name, default: 'jio'
* Settings modified:
* - jio_storage_name, to: 'dav'
* - jio_storage_description
* - redirect_after_reload, to: {command: 'display', options: {page: 'sync'}}
*/
/* Generate a generic jIO configuration for WebDAV storage
* Parameters:
* - dav_url: the WebDAV URL from the configuration form,
* example: https://softinst75722.host.vifib.net/share
* - dav_user: the WebDAV username, example: eyqs
* - dav_pass: the WebDAV password, example: correct horse battery staple
* - get setting: jio_general_query, indexeddb_database_name
* Effects:
* - set setting:
* jio_storage_name, jio_storage_description, redirect_after_reload
* - reload, then redirect to sync
*/
function setDavConfiguration(gadget, event) {
return new RSVP.Queue()
.push(function () {
return gadget.setSetting('jio_storage_name', 'dav');
})
.push(function () {
return RSVP.all([
gadget.getSetting('jio_general_query'),
gadget.getSetting('indexeddb_database_name', 'jio'),
]);
})
.push(function (setting_list) {
const dav_url = event.target.dav_url.value;
const dav_user = event.target.dav_user.value;
const dav_pass = event.target.dav_pass.value;
const configuration = {
type: 'replicate',
query: {
query: setting_list[0],
limit: [0, 16777215],
},
use_remote_post: true,
conflict_handling: 2,
check_local_deletion: false,
local_sub_storage: {
type: 'query',
sub_storage: {
type: 'uuid',
sub_storage: {
type: 'indexeddb',
database: setting_list[1],
},
},
},
remote_sub_storage: {
type: 'query',
sub_storage: {
type: 'drivetojiomapping',
sub_storage: {
type: 'dav',
url: dav_url,
basic_login: btoa(dav_user + ':' + dav_pass),
with_credentials: true,
},
},
},
};
return RSVP.all([
gadget.setSetting('jio_storage_description', configuration),
gadget.setSetting('redirect_after_reload', {
command: 'display',
options: {page: 'sync'},
})
]);
})
.push(function () {
return gadget.reload();
})
}
rJS(window)
// Neither state to set nor ready to initialize
// The following functions are all acquired from erp5_launcher_nojqm.js
.declareAcquiredMethod('updateHeader', 'updateHeader')
.declareAcquiredMethod('redirect', 'redirect')
.declareAcquiredMethod('reload', 'reload')
.declareAcquiredMethod('setSetting', 'setSetting')
.declareAcquiredMethod('getSetting', 'getSetting')
.declareAcquiredMethod('requireSetting', 'requireSetting')
/* Render the gadget
* Parameters:
* - get setting: jio_storage_name, jio_storage_description, user_email
* Effects:
* - update header, page title to 'Configure WebDAV Storage'
* - hide document_configurator gadget based on jio_storage_name
* - pre-fill input field if jio_storage_description already defined
* - redirect if user not logged in
*/
.declareMethod('render', function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getSetting('connect_page', 'none');
})
.push(function (connect_page) {
return gadget.requireSetting(
'user_email',
connect_page,
new RSVP.Queue()
.push(function () {
return gadget.updateHeader({
page_title: 'Configure WebDAV Storage',
back_url: '#!display?n.page=jio_configurator',
});
})
.push(function () {
return gadget.getSetting('jio_storage_name', 'none');
})
.push(function (storage_name) {
if (storage_name === 'dav') {
return new RSVP.Queue()
.push(function () {
return gadget.getSetting('jio_storage_description');
})
.push(function (configuration) {
gadget.element.querySelector('input[name="dav_url"]')
.value = configuration.remote_sub_storage
.sub_storage.sub_storage.url;
return;
});
} else {
gadget.element.querySelector('div[data-gadget-scope='
+ 'document_configurator').style.display = 'none';
}
return;
})
);
});
})
// Call setDavConfiguration when the form is submitted
.onEvent('submit', function (event) {
const gadget = this;
return setDavConfiguration(gadget, event);
});
}(window, RSVP, rJS, btoa));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_jio_dav_configurator.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_jio_dav_configurator_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>jiO DAV Configurator Gadget JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>jIO Configurator Document Gadget</title>
</head>
<body>
<article class="configurator-document">
<header class="configurator-header">
<h3>
<span class="fa fa-file-text-o" aria-hidden="true"></span>
Your Documents
</h3>
</header>
<section class="configurator-section">
<p>
</p>
<p class="configurator-command">
Continue by <strong>synchronizing</strong> with your remote storage or <strong>accessing</strong> your documents.
</p>
<div class="configurator-grid">
<a class="grid-box configurator-button" href="#!display?n.page=sync">Synchronize</a>
<a class="grid-box configurator-button" href="#!display?n.page=contact_list">Access Document List</a>
</div>
</section>
</article>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_jio_document_configurator.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_jio_document_configurator_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>jIO Document Configurator Gadget</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>jIO ERP5 Configurator Gadget</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="gadget_erp5_page_jio_erp5_configurator.js"></script>
</head>
<body>
<article class="configurator-erp5">
<header class="configurator-header">
<h3>
<span class="fa fa-database" aria-hidden="true"></span>
Your ERP5 Parameters
</h3>
</header>
<section class="configurator-section">
<p>
You must already be logged into the following ERP5 instance before you synchronize!
</p>
<form>
<label>Connection URL:</label>
<input type="url" name="erp5_url" required="required" placeholder="https://nexedi.erp5.net/web_site_module/">
<button class="grid-box configurator-button" type="submit">Connect</button>
</form>
</section>
</article>
<div data-gadget-url="gadget_erp5_page_jio_document_configurator.html"
data-gadget-scope="document_configurator"
data-gadget-sandbox="public"></div>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_jio_erp5_configurator.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_jio_erp5_configurator_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>jIO ERP5 Configurator Gadget</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/* global window, RSVP, rJS, URI */
/* jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
(function (window, RSVP, rJS, URI) {
'use strict';
/* Settings required:
* - either erp5_jio_query or jio_general_query
* - user_email, or be redirected
* Settings used:
* - connect_page, default: ''
* - indexeddb_database_name, default: 'jio'
* Settings modified:
* - jio_storage_name, to: 'erp5'
* - jio_storage_description
* - redirect_after_reload, to: {command: 'display', options: {page: 'sync'}}
*/
/* Generate a generic jIO configuration for ERP5 storage
* Parameters:
* - erp5_url: the ERP5 URL from the configuration form,
* example: https://softinst75770.host.vifib.net/web_page_module/
* - get setting: jio_erp5_query, jio_general_query, indexeddb_database_name
* Effects:
* - set setting:
* jio_storage_name, jio_storage_description, redirect_after_reload
* - reload, then redirect to sync
*/
function setErp5Configuration(gadget, event) {
return new RSVP.Queue()
.push(function () {
return gadget.setSetting('jio_storage_name', 'erp5');
})
.push(function () {
return gadget.getSetting('jio_general_query', 'none');
})
.push(function (general_query) {
if (general_query === 'none') {
general_query = null;
}
return RSVP.all([
gadget.getSetting('jio_erp5_query', general_query),
gadget.getSetting('indexeddb_database_name', 'jio'),
]);
})
.push(function (setting_list) {
const erp5_url = event.target.erp5_url.value;
const configuration = {
type: 'replicate',
query: {
query: setting_list[0],
limit: [0, 16777215],
},
use_remote_post: true,
conflict_handling: 2,
check_local_deletion: false,
local_sub_storage: {
type: 'query',
sub_storage: {
type: 'uuid',
sub_storage: {
type: 'indexeddb',
database: setting_list[1],
},
},
},
remote_sub_storage: {
type: 'erp5',
erp5_url: erp5_url,
url: (new URI('hateoas')).absoluteTo(erp5_url).toString(),
default_view_reference: 'jio_view',
},
};
return RSVP.all([
gadget.setSetting('jio_storage_description', configuration),
gadget.setSetting('redirect_after_reload', {
command: 'display',
options: {page: 'sync'},
})
]);
})
.push(function () {
return gadget.reload();
})
}
rJS(window)
// Neither state to set nor ready to initialize
// The following functions are all acquired from erp5_launcher_nojqm.js
.declareAcquiredMethod('updateHeader', 'updateHeader')
.declareAcquiredMethod('redirect', 'redirect')
.declareAcquiredMethod('reload', 'reload')
.declareAcquiredMethod('setSetting', 'setSetting')
.declareAcquiredMethod('getSetting', 'getSetting')
.declareAcquiredMethod('requireSetting', 'requireSetting')
/* Render the gadget
* Parameters:
* - get setting: jio_storage_name, jio_storage_description, user_email
* Effects:
* - update header, page title to 'Configure ERP5 Storage'
* - hide document_configurator gadget based on jio_storage_name
* - pre-fill input field if jio_storage_description already defined
* - redirect if user not logged in
*/
.declareMethod('render', function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
return gadget.getSetting('connect_page', 'none');
})
.push(function (connect_page) {
return gadget.requireSetting(
'user_email',
connect_page,
new RSVP.Queue()
.push(function () {
return gadget.updateHeader({
page_title: 'Configure ERP5 Storage',
back_url: '#!display?n.page=jio_configurator',
});
})
.push(function () {
return gadget.getSetting('jio_storage_name', 'none');
})
.push(function (storage_name) {
if (storage_name === 'erp5') {
return new RSVP.Queue()
.push(function () {
return gadget.getSetting('jio_storage_description');
})
.push(function (configuration) {
gadget.element.querySelector('input[name="erp5_url"]')
.value = configuration.remote_sub_storage.erp5_url;
return;
});
} else {
gadget.element.querySelector('div[data-gadget-scope='
+ 'document_configurator').style.display = 'none';
}
return;
})
);
});
})
// Call setErp5Configuration when the form is submitted
.onEvent('submit', function (event) {
const gadget = this;
return setErp5Configuration(gadget, event);
});
}(window, RSVP, rJS, URI));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_jio_erp5_configurator.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_jio_erp5_configurator_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>jIO ERP5 Configurator Gadget JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OfficeJS Contact View</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="handlebars.js"></script>
<script src="gadget_erp5_page_jio_person_view.js"></script>
<script class="contact_template" type="text/x-handlebars-template">
<form class="contact-form">
<label>E-mail Address:</label>
<input type="text" name="default_email_coordinate_text"
value="{{default_email_coordinate_text}}" />
<label>Last Name:</label>
<input type="text" name="last_name" value="{{last_name}}" />
<label>First Name:</label>
<input type="text" name="first_name" value="{{first_name}}" />
<label>jIO Configuration:</label>
<input type="text" name="jio_configuration"
value="{{jio_configuration}}" />
</form>
</script>
</head>
<body>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_jio_person_view.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_jio_person_view_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>ERP5 jIO Person View</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/*globals window, rJS, Handlebars, RSVP, loopEventListener, console*/
/*jslint indent: 2, nomen: true, maxlen: 80*/
(function (window, RSVP, rJS, Handlebars) {
'use strict';
/* Settings required:
* - jio_storage_description, or be redirected
*/
// Precompile Handlebars templates while loading the first gadget instance
const gadget_klass = rJS(window);
const contact_template = Handlebars.compile(gadget_klass
.__template_element.querySelector('.contact_template').innerHTML);
gadget_klass
.setState({
// Document metadata to store in jIO
doc: null,
jio_key: null,
})
// The following functions are all acquired from erp5_launcher_nojqm.js
.declareAcquiredMethod('updateHeader', 'updateHeader')
.declareAcquiredMethod('redirect', 'redirect')
.declareAcquiredMethod('translateHtml', 'translateHtml')
.declareAcquiredMethod('requireSetting', 'requireSetting')
.declareAcquiredMethod('jio_allDocs', 'jio_allDocs')
.declareAcquiredMethod('jio_put', 'jio_put')
.declareAcquiredMethod('jio_get', 'jio_get')
/* Render the gadget
* Parameters: all in option_dict,
* - jio_key: the id of the document in the jIO storage
* - get setting: jio_storage_description
* Effects:
* - jIO get: the contact info associated with jio_key
* - change state: add the contact info from jIO
* - run the contact info through Handlebars to render the forms
* - redirect if no jIO storage available
*/
.declareMethod('render', function (option_dict) {
const gadget = this;
return gadget.requireSetting(
'jio_storage_description',
'jio_configurator',
new RSVP.Queue()
.push(function () {
return gadget.jio_get(option_dict.jio_key);
})
.push(function (result) {
return gadget.changeState({
jio_key: option_dict.jio_key,
doc: result
});
})
.push(function () {
return gadget.translateHtml(contact_template(gadget.state.doc));
})
.push(function (translated_html) {
gadget.element.innerHTML = translated_html;
let title;
if (gadget.state.doc.default_email_coordinate_text) {
title = gadget.state.doc.default_email_coordinate_text
+ ' | Contact';
} else {
title = 'Anonymous Contact';
}
return gadget.updateHeader({
page_title: title,
save_action: true,
});
})
);
})
/* Save current contact information when the right button is clicked
* Parameters:
* - the current values from the contact form
* Effects:
* - jIO put: the contact info from the contact form
* - redirect to the same page for a visual indication of success
*/
.declareMethod('triggerSubmit', function () {
const gadget = this;
return new RSVP.Queue()
.push(function () {
const fields = gadget.element.querySelector('.contact-form').elements;
for (let i = 0, i_len = fields.length; i < i_len; i++) {
gadget.state.doc[fields[i].name] = fields[i].value;
}
// ERP5: can not store property: modification_date
// gadget.state.doc.modification_date = new Date().toISOString();
})
.push(function () {
return gadget.jio_put(gadget.state.jio_key, gadget.state.doc);
})
.push(function () {
return gadget.redirect({
command: 'display',
options: {
jio_key: gadget.state.jio_key,
page: 'jio_person_view',
},
});
});
});
}(window, RSVP, rJS, Handlebars));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_jio_person_view.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_jio_person_view_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>ERP5 jIO Person View JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>jIO Configurator Gadget</title>
<script src="rsvp.js"></script>
<script src="renderjs.js"></script>
<script src="handlebars.js"></script>
<script src="gadget_erp5_page_sync.js"></script>
</head>
<body>
<article class="configurator-sync">
<header class="configurator-header">
<h3>
<span class="fa fa-refresh" aria-hidden="true"></span>
Synchronize Your Data
</h3>
</header>
<form>
<button class="configurator-button" type="submit">Launch Synchronization</button>
</form>
</article>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_sync.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_sync_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Sync Page</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
/* global window, RSVP, rJS */
/* jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
(function (window, RSVP, rJS) {
'use strict';
/* Settings required:
* - jio_storage_description, or be redirected
*/
/* Synchronize between the local and remote storages in replicate storage
* Parameters: nothing
* Returns: nothing
* Effects:
* - repair replicate storage
* - redirect to sync
*/
function launchSynchronization(gadget, event) {
return new RSVP.Queue()
.push(function () {
event.target.elements[0].disabled = true;
return gadget.jio_repair();
})
.push(function (result_dict) {
if (result_dict && result_dict.hasOwnProperty('redirect')) {
return gadget.redirect({
command: 'display',
options: {page: result_dict.redirect},
});
} else {
return gadget.redirect();
}
});
}
rJS(window)
// Neither state to set nor ready to initialize
// The following functions are all acquired from erp5_launcher_nojqm.js
.declareAcquiredMethod('updateHeader', 'updateHeader')
.declareAcquiredMethod('redirect', 'redirect')
.declareAcquiredMethod('jio_repair', 'jio_repair')
.declareAcquiredMethod('requireSetting', 'requireSetting')
/* Render the gadget
* Parameters:
* - get setting: jio_storage_description
* Effects:
* - update header, page_title to 'Synchronize'
* - redirect if no jIO storage available
*/
.declareMethod('render', function () {
const gadget = this;
return gadget.requireSetting(
'jio_storage_description',
'jio_configurator',
new RSVP.Queue()
.push(function () {
return gadget.updateHeader({page_title: 'Synchronize'});
})
);
})
// Call launchSynchronization when the form is submitted
.onEvent('submit', function (event) {
const gadget = this;
return launchSynchronization(gadget, event);
});
}(window, RSVP, rJS));
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_sync.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_erp5_page_sync_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Script</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Sync Page JS</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
erp5_base
erp5_web
erp5_web_renderjs_ui
\ No newline at end of file
eugene.shen
\ No newline at end of file
web_page_module/adapter_js
web_page_module/erp5_page_launcher*
web_page_module/fast_priority_queue_js
web_page_module/gadget_erp5_chat_panel_*
web_page_module/gadget_erp5_chat_webrtc_*
web_page_module/gadget_erp5_nojquery_css
web_page_module/gadget_erp5_page_chat_*
web_page_module/gadget_erp5_page_contact_*
web_page_module/gadget_erp5_page_field_listbox_widget_*
web_page_module/gadget_erp5_page_jio_*_configurator_*
web_page_module/gadget_erp5_page_jio_person_view_*
web_page_module/gadget_erp5_page_sync_*
\ No newline at end of file
officejs_eyqs
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment