Commit 659eeb0d authored by Cédric Le Ninivin's avatar Cédric Le Ninivin

Move to jIO Storage for Service Worker

parent 191a947d
......@@ -2,20 +2,19 @@
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Jio Gadget</title>
<link rel="http://www.renderjs.org/rel/interface" href="interface_jio.html">
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>Crib SW interface Gadget</title>
<!-- renderjs -->
<script src="./lib/rsvp.js" type="text/javascript"></script>
<script src="./lib/renderjs.js" type="text/javascript"></script>
<script src="./lib/jio-latest.js" type="text/javascript"></script>
<!-- custom script -->
<script src="gadget_jio_crib.js" type="text/javascript"></script>
<script src="./crib-enable.js" type="text/javascript"></script>
</head>
<body>
Couscosu
</body>
</html>
\ No newline at end of file
/*global window, rJS, RSVP, XMLHttpRequest, MessageChannel,
console, navigator, Blob
*/
/*jslint indent: 2, maxerr: 3 */
(function (rJS, RSVP, jIO) {
"use strict";
/**
* Send request with XHR and return a promise. xhr.onload: The promise is
* resolved when the status code is lower than 400 with the xhr object as
* first parameter. xhr.onerror: reject with xhr object as first
* parameter. xhr.onprogress: notifies the xhr object.
*
* @param {Object} param The parameters
* @param {String} [param.type="GET"] The request method
* @param {String} [param.dataType=""] The data type to retrieve
* @param {String} param.url The url
* @param {Any} [param.data] The data to send
* @param {Function} [param.beforeSend] A function called just before the
* send request. The first parameter of this function is the XHR object.
* @return {Promise} The promise
*/
function ajax(param) {
var xhr = new XMLHttpRequest();
return new RSVP.Promise(function (resolve, reject, notify) {
var k;
xhr.open(param.type || "GET", param.url, true);
xhr.addEventListener("load", function (e) {
var answer = {};
if (e.target.status >= 400) {
return reject(e);
}
answer.responseText = this.responseText;
answer.responseType = this.getResponseHeader("content-type");
answer.responseURL = param.url;
resolve(answer);
});
xhr.addEventListener("error", reject);
xhr.addEventListener("progress", notify);
if (typeof param.xhrFields === 'object' && param.xhrFields !== null) {
for (k in param.xhrFields) {
if (param.xhrFields.hasOwnProperty(k)) {
xhr[k] = param.xhrFields[k];
}
}
}
xhr.send();
}, function () {
xhr.abort();
});
}
function sendMessage(message) {
// This wraps the message posting/response in a promise, which will resolve if the response doesn't
// contain an error, and reject with the error if it does. If you'd prefer, it's possible to call
// controller.postMessage() and set up the onmessage handler independently of a promise, but this is
// a convenient wrapper.
return new RSVP.Promise(function (resolve, reject, notify) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function (event) {
console.log(event);
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
// This sends the message data as well as transferring messageChannel.port2 to the service worker.
// The service worker can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
return navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
});
}
function setStatus(statusMessage) {
console.log(statusMessage);
}
rJS(window)
.ready(function (g) {
g.props = {};
return g.getElement()
.push(function (element) {
g.props.element = element;
});
})
.ready(function (gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {};
if ('serviceWorker' in navigator) {
// XXX Hack to not add a new service worker when one is already declared
if (!navigator.serviceWorker.controller) {
return new RSVP.Promise(function (resolve, reject, notify) {
navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(
function () {
if (navigator.serviceWorker.controller) {
resolve();
} else {
reject("Please reload this page to allow Service Worker to control this page");
}
})
.then(undefined, function (error) {
reject(error);
});
});
}
} else {
throw "Service Worker are not available in your browser";
}
})
/**
* allDocs return the list of document in the cache
*
* @params {Object} Not taken into account
* @return {} Return the data url of the document
*/
.declareMethod('allDocs', function (params) {
if (params && params.cached_only) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'keys'
});
});
}
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'allDocs'
});
});
})
/**
* get Return a data url. Cannot return a blob as the data
* is transmitted through an iFrame
*
* @url {string} url of the document to retrieve
* @return {data_url} Return the data url of the document
*/
.declareMethod('get', function (url) {
return new RSVP.Queue()
.push(function () {
return ajax({
url: url
});
})
.push(function (result) {
return new Blob([result.responseText], {type: result.responseType});
})
.push(function (result) {
return jIO.util.readBlobAsDataURL(result);
})
.push(function (e) {
return e.target.result;
});
})
/**
* put Return a data url. Cannot provide a blob as the data
* is transmitted through an iFrame
*
* @url {string} url of the document to update
* @parameter {data_url} data url of the document to put, it will be transformed in a blob
* @return {data_url} Return the data url of the document
*/
.declareMethod('put', function (url, parameter) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'add',
url: url,
information: jIO.util.dataURItoBlob(parameter)
});
}).push(function () {
// If the promise resolves, just display a success message.
console.log("Done adding " + url);
return 'Added to cache: ' + url + ' at ' + Date();
}).fail(setStatus);
})
/**
* Remove an url from the cache
*
* @url {string} url of the document to remove
* @return {}
*/
.declareMethod('remove', function (url) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'delete',
url: url
});
});
});
}(rJS, RSVP, jIO));
\ No newline at end of file
......@@ -2,19 +2,20 @@
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>Crib SW interface Gadget</title>
<meta name="viewport" content="width=device-width" />
<title>Jio Gadget</title>
<link rel="http://www.renderjs.org/rel/interface" href="interface_jio.html">
<!-- renderjs -->
<script src="./lib/rsvp.js" type="text/javascript"></script>
<script src="./lib/renderjs.js" type="text/javascript"></script>
<script src="./lib/jio-latest.js" type="text/javascript"></script>
<!-- custom script -->
<script src="./crib-enable.js" type="text/javascript"></script>
<script src="crib-enable.js" type="text/javascript"></script>
</head>
<body>
Couscosu
</body>
</html>
\ No newline at end of file
/*global window, rJS, RSVP, XMLHttpRequest, MessageChannel,
console, navigator, Blob
*/
/*global window, rJS, jIO, FormData */
/*jslint indent: 2, maxerr: 3 */
(function (rJS, RSVP, jIO) {
(function (window, rJS, jIO) {
"use strict";
/**
* Send request with XHR and return a promise. xhr.onload: The promise is
* resolved when the status code is lower than 400 with the xhr object as
* first parameter. xhr.onerror: reject with xhr object as first
* parameter. xhr.onprogress: notifies the xhr object.
*
* @param {Object} param The parameters
* @param {String} [param.type="GET"] The request method
* @param {String} [param.dataType=""] The data type to retrieve
* @param {String} param.url The url
* @param {Any} [param.data] The data to send
* @param {Function} [param.beforeSend] A function called just before the
* send request. The first parameter of this function is the XHR object.
* @return {Promise} The promise
*/
function ajax(param) {
var xhr = new XMLHttpRequest();
return new RSVP.Promise(function (resolve, reject, notify) {
var k;
xhr.open(param.type || "GET", param.url, true);
xhr.addEventListener("load", function (e) {
var answer = {};
if (e.target.status >= 400) {
return reject(e);
}
answer.responseText = this.responseText;
answer.responseType = this.getResponseHeader("content-type");
answer.responseURL = param.url;
resolve(answer);
});
xhr.addEventListener("error", reject);
xhr.addEventListener("progress", notify);
if (typeof param.xhrFields === 'object' && param.xhrFields !== null) {
for (k in param.xhrFields) {
if (param.xhrFields.hasOwnProperty(k)) {
xhr[k] = param.xhrFields[k];
}
function waitForServiceWorkerActive(registration) {
var serviceWorker;
if (registration.installing) {
serviceWorker = registration.installing;
} else if (registration.waiting) {
serviceWorker = registration.waiting;
} else if (registration.active) {
serviceWorker = registration.active;
}
if (serviceWorker.state !== "activated") {
return RSVP.Promise(function (resolve, reject) {
serviceWorker.addEventListener('statechange', function (e) {
if (e.target.state === "activated") {
resolve();
}
xhr.send();
}, function () {
xhr.abort();
});
}
function sendMessage(message) {
// This wraps the message posting/response in a promise, which will resolve if the response doesn't
// contain an error, and reject with the error if it does. If you'd prefer, it's possible to call
// controller.postMessage() and set up the onmessage handler independently of a promise, but this is
// a convenient wrapper.
return new RSVP.Promise(function (resolve, reject, notify) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function (event) {
console.log(event);
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
// This sends the message data as well as transferring messageChannel.port2 to the service worker.
// The service worker can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
return navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
RSVP.delay(500).then(function () {
reject(new Error("Timeout service worker install"));
});
});
}
function setStatus(statusMessage) {
console.log(statusMessage);
}
rJS(window)
.ready(function (g) {
g.props = {};
return g.getElement()
.push(function (element) {
g.props.element = element;
});
})
.ready(function (gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {};
gadget.state_parameter_dict.default_document = window.location.protocol +
"//" + window.location.host +
window.location.pathname.substring(
0,
window.location.pathname.length - "gadget_jio_crib.html".length
) + "/";
console.log(gadget.state_parameter_dict.default_document);
this.state_parameter_dict.jio_storage = jIO.createJIO({
"type": "indexeddb",
"database": "ojs_source_code"
});
return this.state_parameter_dict.jio_storage.put(gadget.state_parameter_dict.default_document, {})
})
.ready(function (gadget) {
if ('serviceWorker' in navigator) {
// XXX Hack to not add a new service worker when one is already declared
if (!navigator.serviceWorker.controller) {
return new RSVP.Promise(function (resolve, reject, notify) {
navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(
function () {
if (navigator.serviceWorker.controller) {
resolve();
} else {
reject("Please reload this page to allow Service Worker to control this page");
}
return new RSVP.Queue()
.push(function () {
return navigator.serviceWorker.register(
'/gadget_cribjs_bootloader_serviceworker.js',
{scope: '/'}
);
})
.push(function (registration) {
return waitForServiceWorkerActive(registration);
})
.then(undefined, function (error) {
reject(error);
});
});
}
} else {
throw "Service Worker are not available in your browser";
}
})
/**
* allDocs return the list of document in the cache
*
* @params {Object} Not taken into account
* @return {} Return the data url of the document
*/
.declareMethod('allDocs', function (params) {
if (params && params.cached_only) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'keys'
});
});
}
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'allDocs'
});
});
})
/**
* get Return a data url. Cannot return a blob as the data
* is transmitted through an iFrame
*
* @url {string} url of the document to retrieve
* @return {data_url} Return the data url of the document
*/
.declareMethod('get', function (url) {
return new RSVP.Queue()
.push(function () {
return ajax({
url: url
});
})
.push(function (result) {
return new Blob([result.responseText], {type: result.responseType});
})
var storage = this.state_parameter_dict.jio_storage;
return storage.getAttachment(this.state_parameter_dict.default_document, url)
.push(function (result) {
return jIO.util.readBlobAsDataURL(result);
})
......@@ -159,44 +74,32 @@
return e.target.result;
});
})
/**
* put Return a data url. Cannot provide a blob as the data
* is transmitted through an iFrame
*
* @url {string} url of the document to update
* @parameter {data_url} data url of the document to put, it will be transformed in a blob
* @return {data_url} Return the data url of the document
*/
.declareMethod('put', function (url, parameter) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'add',
url: url,
information: jIO.util.dataURItoBlob(parameter)
});
}).push(function () {
// If the promise resolves, just display a success message.
console.log("Done adding " + url);
return 'Added to cache: ' + url + ' at ' + Date();
}).fail(setStatus);
.declareMethod('put', function (url, data_uri) {
var storage = this.state_parameter_dict.jio_storage;
data_uri = jIO.util.dataURItoBlob(data_uri);
console.log(this.state_parameter_dict.default_document);
console.log(url);
return storage.putAttachment(
this.state_parameter_dict.default_document,
url,
data_uri
);
})
.declareMethod('allDocs', function () {
var storage = this.state_parameter_dict.jio_storage;
console.log(this.state_parameter_dict.default_document);
return storage.allAttachments(this.state_parameter_dict.default_document)
.push(function(result) {
console.log(result);
return result;
})
})
/**
* Remove an url from the cache
*
* @url {string} url of the document to remove
* @return {}
*/
.declareMethod('remove', function (url) {
return new RSVP.Queue()
.push(function () {
return sendMessage({
command: 'delete',
url: url
});
});
var storage = this.state_parameter_dict.jio_storage;
return storage.removeAttachment(
this.state_parameter_dict.default_document,
url
);
});
}(rJS, RSVP, jIO));
\ No newline at end of file
}(window, rJS, jIO));
\ No newline at end of file
......@@ -21,7 +21,8 @@
}
function loadZipIntoCrib(crib_sw_gadget, zip, from_path, path_to_load) {
var promise_list = [], url_number = 0;
var promise_list = [], url_number = 0,
site_url = window.location.protocol + "//" + window.location.host;
zip.forEach(function (relativePath, zipEntry) {
var end_url;
url_number += 1;
......@@ -32,12 +33,11 @@
return;
}
relativePath = relativePath.substring(from_path.length);
if (!relativePath.startsWith("/") && !path_to_load.endsWith("/")) {
end_url = path_to_load + "/" + relativePath;
} else if (relativePath.startsWith("/") && path_to_load.endsWith("/")) {
end_url = path_to_load + relativePath.substring(1);
console.log(relativePath);
if (relativePath.startsWith("/")) {
end_url = relativePath.substring(1);
} else {
end_url = path_to_load + relativePath;
end_url = relativePath;
}
promise_list.push(
new RSVP.Queue()
......
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>CribJS Loader</title>
<script src="../lib/rsvp.js"></script>
<script src="../lib/renderjs.js"></script>
<script src="../lib/jszip.js" type="text/javascript"></script>
<script src="./gadget_landing_cribjs2.js"></script>
</head>
<body>
<div data-gadget-url="./crib-sw-gadget.html"
data-gadget-scope="crib_sw_gadget"
data-gadget-sandbox="public"></div>
<div data-gadget-url="./gadget_jio.html"
data-gadget-scope="jio_gadget"
data-gadget-sandbox="public"></div>
</body>
</html>
\ No newline at end of file
/*jslint browser: true, indent: 2, maxlen: 80*/
/*globals RSVP, rJS,
location, console, fetch, Promise*/
(function (window, document, RSVP, rJS, location, console, JSZip) {
"use strict";
function getExtension(url) {
var extension = url.split('.').pop();
if (extension.endsWith('/')) {
return ".html";
}
return "." + extension;
}
function getSetting(gadget, key, default_value) {
if (key === "site_editor_gadget_url") {
return window.location.protocol + "//" + window.location.host +
"/gadget_jio_crib.html";
}
return default_value;
}
function loadZipIntoCrib(crib_sw_gadget, zip, from_path, path_to_load) {
var promise_list = [], url_number = 0,
site_url = window.location.protocol + "//" + window.location.host;
zip.forEach(function (relativePath, zipEntry) {
var end_url;
url_number += 1;
if (zipEntry.dir) {
return;
}
if (!relativePath.startsWith(from_path)) {
return;
}
relativePath = relativePath.substring(from_path.length);
console.log(relativePath);
if (relativePath.startsWith("/")) {
end_url = relativePath.substring(1);
} else {
end_url = relativePath;
}
promise_list.push(
new RSVP.Queue()
.push(function () {
return zipEntry.async('blob');
})
.push(function (result) {
if (end_url.endsWith(".js")) {
// This is a ugly hack as mimetype needs to be correct for JS
result = result.slice(0, result.size, "application/javascript");
} else if (end_url.endsWith(".html")) {
// This is a ugly hack as mimetype needs to be correct for JS
result = result.slice(0, result.size, "text/html");
} else if (end_url.endsWith(".css")) {
// This is a ugly hack as mimetype needs to be correct for JS
result = result.slice(0, result.size, "text/css");
}
return crib_sw_gadget.put(end_url, {blob: result});
})
);
});
return RSVP.all(promise_list);
}
function loadContentFromZIPFile(gadget, options) {
var path_to_load = options.to_path,
from_path = options.from_path,
file_list = options.file_list;
if (file_list.length === 0) {
return "No File to Load";
}
return new RSVP.Queue()
.push(function () {
return RSVP.all([
gadget.getDeclaredGadget('crib_sw_gadget'),
JSZip.loadAsync(file_list[0])
]);
})
.push(function (result_list) {
return loadZipIntoCrib(
result_list[0], result_list[1], from_path, path_to_load
);
})
.push(console.log, console.log);
}
function loadContentFromZIPURL(gadget, options) {
var path_to_load = options.to_path, file_list, crib_sw_gadget,
from_path = options.from_path, zip_url = options.zip_url,
jio_gadget, url_list = [], url_number = 0;
return new RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget('crib_sw_gadget');
})
.push(function (returned_gadget) {
crib_sw_gadget = returned_gadget;
return fetch(zip_url)
.then(function (response) { // 2) filter on 200 OK
if (response.status === 200 || response.status === 0) {
return Promise.resolve(response.blob());
} else {
return Promise.reject(new Error(response.statusText));
}
});
})
.push(JSZip.loadAsync)
.push(function (zip) {
return loadZipIntoCrib(crib_sw_gadget, zip, from_path, path_to_load);
})
.push(console.log, console.log);
}
rJS(window)
.ready(function (g) {
g.props = {};
})
.declareMethod('loadFromZipUrl', function (options) {
return loadContentFromZIPURL(this, options);
})
.declareMethod('loadFromZipFile', function (options) {
return loadContentFromZIPFile(this, options);
})
.allowPublicAcquisition("getSetting", function (argument_list) {
return getSetting(this, argument_list[0], argument_list[1]);
});
}(window, document, RSVP, rJS, location, console, JSZip));
\ No newline at end of file
......@@ -52,7 +52,6 @@ var global = self, window = self;
self.addEventListener("fetch", function (event) {
var relative_url = event.request.url.split("#")[0]
.replace(self.registration.scope, "")
.replace(self.version_url, "");
if (relative_url[relative_url.length - 1] === "/" || relative_url === "") {
relative_url = relative_url + "index.html";
}
......@@ -60,7 +59,8 @@ var global = self, window = self;
event.respondWith(new Response(self.cache_list));
return;
}
else if (event.request !== undefined && event.request.referrer === self.registration.scope) {
else if (event.request !== undefined &&
new URL(event.request.url).hostname !== window.location.hostname) {
event.respondWith(
new self.RSVP.Queue()
.push(function () {
......
/*global window, rJS, jIO, FormData */
/*jslint indent: 2, maxerr: 3 */
(function (window, rJS, jIO) {
"use strict";
function waitForServiceWorkerActive(registration) {
var serviceWorker;
if (registration.installing) {
serviceWorker = registration.installing;
} else if (registration.waiting) {
serviceWorker = registration.waiting;
} else if (registration.active) {
serviceWorker = registration.active;
}
if (serviceWorker.state !== "activated") {
return RSVP.Promise(function (resolve, reject) {
serviceWorker.addEventListener('statechange', function (e) {
if (e.target.state === "activated") {
resolve();
}
});
RSVP.delay(500).then(function () {
reject(new Error("Timeout service worker install"));
});
});
}
}
rJS(window)
.ready(function (gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {};
gadget.state_parameter_dict.default_document = window.location.protocol +
"//" + window.location.host +
window.location.pathname.substring(
0,
window.location.pathname.length - "gadget_jio_crib.html".length
) + "/";
console.log(gadget.state_parameter_dict.default_document);
this.state_parameter_dict.jio_storage = jIO.createJIO({
"type": "indexeddb",
"database": "ojs_source_code"
});
return this.state_parameter_dict.jio_storage.put(gadget.state_parameter_dict.default_document, {})
})
.ready(function (gadget) {
if ('serviceWorker' in navigator) {
// XXX Hack to not add a new service worker when one is already declared
if (!navigator.serviceWorker.controller) {
return new RSVP.Queue()
.push(function () {
return navigator.serviceWorker.register(
'/gadget_cribjs_bootloader_serviceworker.js',
{scope: '/'}
);
})
.push(function (registration) {
return waitForServiceWorkerActive(registration);
})
}
} else {
throw "Service Worker are not available in your browser";
}
})
.declareMethod('get', function (url) {
var storage = this.state_parameter_dict.jio_storage;
return storage.getAttachment(this.state_parameter_dict.default_document, url)
.push(function (result) {
return jIO.util.readBlobAsDataURL(result);
})
.push(function (e) {
return e.target.result;
});
})
.declareMethod('put', function (url, data_uri) {
var storage = this.state_parameter_dict.jio_storage;
data_uri = jIO.util.dataURItoBlob(data_uri);
console.log(this.state_parameter_dict.default_document);
console.log(url);
return storage.putAttachment(
this.state_parameter_dict.default_document,
url,
data_uri
);
})
.declareMethod('allDocs', function () {
var storage = this.state_parameter_dict.jio_storage;
console.log(this.state_parameter_dict.default_document);
return storage.allAttachments(this.state_parameter_dict.default_document)
.push(function(result) {
console.log(result);
return result;
})
})
.declareMethod('remove', function (url) {
var storage = this.state_parameter_dict.jio_storage;
return storage.removeAttachment(
this.state_parameter_dict.default_document,
url
);
});
}(window, rJS, jIO));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>CribJS Loader</title>
<link rel="stylesheet" href="./lib/bootstrap/bootstrap.min.css">
<link rel="stylesheet" href="./landing.css">
<script src="./lib/rsvp.js"></script>
<script src="./lib/renderjs.js"></script>
<script src="./gadget/gadget_global.js"></script>
<script src="./landing2.js"></script>
</head>
<body>
<div data-gadget-url="./gadget/gadget_landing_cribjs2.html"
data-gadget-scope="jio_cribjs"
data-gadget-sandbox="public"></div>
<div class="nav_content container">
<h1>CribJS Loader</h1>
<p>Load your Crib from here</p>
</div>
<div class="nav_content container">
<h3>Start Editing this Site</h3>
<a class="edit-url btn btn-primary" type="text" size="60" href="">Start Editing This Site Now</a>
</div>
<div class="nav_content container">
<form class="crib-load-from-zip-url form-inline">
<h3>Fill This site from a zip URL</h3>
<div class="form-group">
<label>Zip Url:
<input name="load-zip-url" class="load-zip-url form-control" type="text" size="60"></label>
</div>
<div class="form-group">
<label> Path in Zip:
<input name="load-from-zip-path" class="load-from-zip-path form-control" type="text" size="30" value="/">
</label>
</div>
<div class="form-group">
<label> to path:
<input name="load-zip-to-path" class="load-zip-to-path form-control" type="text" size="30" value="/">
</label>
</div>
<div class="form-group">
<label> And redirect to:
<input name="redirect-url" class="redirect-url form-control" type="text" size="30" value="">
</label>
</div>
<button name="load-zip-contents" class="load-zip-contents btn btn-default" type="submit">Import from URL</button>
</form>
</div>
<div class="nav_content container">
<div><span class="info crib-save-to-zip-status"></span></div>
<form class="crib-load-from-zip-file form-inline">
<h3>Import from zip File</h3>
<div class="form-group">
<label>Zip File:
<input name="load-zip-file" class="load-zip-file form-control" type="file" size="30"></label>
</div>
<div class="form-group">
<label> Path in Zip:
<input name="load-zip-path" class="load-from-zip-path form-control" type="text" size="30" value="/">
</label>
</div>
<div class="form-group">
<label> to path:
<input name="load-zip-to-path" class="load-zip-to-path form-control" type="text" size="30" value="/">
</label>
</div>
<div class="form-group">
<label> And redirect to:
<input name="redirect-url" class="redirect-url form-control" type="text" size="30" value="">
</label>
</div>
<button name="load-zip-contents" class="load-zip-contents btn btn-default" type="submit">Import from File</button>
</form>
</div>
<div class="nav_content container">
<p>You can check where it started, and start a crib from the beginning: <a href="jungle.html">The Jungle :)</a>. This one is a simple editor with no import/export functions. First challenge is to use that to add an import/export function :).</p>
<p>A simple editor can be found <a href="base.html">here</a></p>
</div>
</body>
</html>
......@@ -58,6 +58,7 @@
})
})
.push(function (url_list) {
//window.location.reload();
document.location = gadget.props.element.querySelector("form.crib-load-from-zip-url .redirect-url").value;
})
.push(console.log, console.log);
......@@ -89,13 +90,14 @@
gadget.props.element.querySelector("form.crib-load-from-zip-url .load-zip-url").value = "https://lab.nexedi.com/cedric.leninivin/cribjs-editor/-/archive/master/cribjs-editor-master.zip";
}
if ( params.hasOwnProperty("redirect_url") ) {
gadget.props.element.querySelector("form.crib-load-from-zip-url .redirect-url").value = params.redirect_url;
gadget.props.element.querySelector("form.crib-load-from-zip-url .redirect-url").value = decodeURIComponent(params.redirect_url);
} else {
gadget.props.element.querySelector("form.crib-load-from-zip-url .redirect-url").value = site + "/index.html";
gadget.props.element.querySelector("form.crib-load-from-zip-url .redirect-url").value = site ;
}
return RSVP.Queue()
.push(function () {
var promise_list = []
promise_list.push(loadCribJSFromZipUrl(gadget, undefined));
promise_list.push(loopEventListener(
gadget.props.element.querySelector("form.crib-load-from-zip-url"),
'submit',
......
/*jslint nomen: true, indent: 2, maxerr: 3 */
/*global window, rJS */
(function (window, rJS, loopEventListener) {
"use strict";
function getParameterDict () {
var hash = window.location.hash.substring(1),
params = {};
hash.split('&').map(hk => {
let temp = hk.split('=');
params[temp[0]] = temp[1];
});
return params;
}
function makeid(length) {
var result = '';
var characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
function loadCribJSFromZipFile (gadget, event) {
return RSVP.Queue()
.push(function() {
return gadget.getDeclaredGadget("jio_cribjs");
})
.push(function(jio_cribjs_gadget) {
return jio_cribjs_gadget.loadFromZipFile({
path: document.location.href,
file_list: gadget.props.element.querySelector("form.crib-load-from-zip-file .load-zip-file").files,
from_path: gadget.props.element.querySelector("form.crib-load-from-zip-file .load-from-zip-path").value,
to_path: gadget.props.element.querySelector("form.crib-load-from-zip-file .load-zip-to-path").value,
application_id: "cribjs"
})
})
.push(function (url_list) {
document.location = gadget.props.element.querySelector("form.crib-load-from-zip-file .redirect-url").value;
})
.push(console.log, console.log);
}
function loadCribJSFromZipUrl (gadget, event) {
return RSVP.Queue()
.push(function() {
return gadget.getDeclaredGadget("jio_cribjs");
})
.push(function(jio_cribjs_gadget) {
return jio_cribjs_gadget.loadFromZipUrl({
path: document.location.href,
zip_url: gadget.props.element.querySelector("form.crib-load-from-zip-url .load-zip-url").value,
from_path: gadget.props.element.querySelector("form.crib-load-from-zip-url .load-from-zip-path").value,
to_path: gadget.props.element.querySelector("form.crib-load-from-zip-url .load-zip-to-path").value,
application_id: "cribjs"
})
})
.push(function (url_list) {
document.location = gadget.props.element.querySelector("form.crib-load-from-zip-url .redirect-url").value;
})
.push(console.log, console.log);
}
rJS(window)
.declareMethod('render', function (options) {
var gadget = this,
getURL = window.location,
site = getURL.protocol + "//" + getURL.host,
params = getParameterDict(),
edit_url="https://" + makeid(10) + ".cribjs.nexedi.net/crib-editor4/#page=editor&url="
+ site + "&crib_enable_url=" + site + "/crib-enable.html";
gadget.props.element.querySelector("a.edit-url").href = edit_url ;
if ( params.hasOwnProperty("from_path") ) {
gadget.props.element.querySelector("form.crib-load-from-zip-url .load-from-zip-path").value = params.from_path;
} else {
gadget.props.element.querySelector("form.crib-load-from-zip-url .load-from-zip-path").value = "cribjs-editor-master/";
}
if ( params.hasOwnProperty("to_path") ) {
gadget.props.element.querySelector("form.crib-load-from-zip-url .load-zip-to-path").value = params.to_path;
} else {
gadget.props.element.querySelector("form.crib-load-from-zip-url .load-zip-to-path").value = site;
}
if ( params.hasOwnProperty("zip_url") ) {
gadget.props.element.querySelector("form.crib-load-from-zip-url .load-zip-url").value = params.zip_url;
} else {
gadget.props.element.querySelector("form.crib-load-from-zip-url .load-zip-url").value = "https://lab.nexedi.com/cedric.leninivin/cribjs-editor/-/archive/master/cribjs-editor-master.zip";
}
if ( params.hasOwnProperty("redirect_url") ) {
gadget.props.element.querySelector("form.crib-load-from-zip-url .redirect-url").value = params.redirect_url;
} else {
gadget.props.element.querySelector("form.crib-load-from-zip-url .redirect-url").value = site + "/index.html";
}
return RSVP.Queue()
.push(function () {
var promise_list = []
promise_list.push(loopEventListener(
gadget.props.element.querySelector("form.crib-load-from-zip-url"),
'submit',
false,
function (event) {loadCribJSFromZipUrl(gadget, event)}
));
promise_list.push(loopEventListener(
gadget.props.element.querySelector("form.crib-load-from-zip-file"),
'submit',
false,
function (event) {loadCribJSFromZipFile(gadget, event)}
));
return RSVP.all(promise_list)
})
.fail(function(e){console.log(e)})
})
.ready(function (g) {
g.props = {};
return g.getElement()
.push(function (element) {
g.props.element = element;
}).push(function() {
g.render({})
})
;
});
}(window, rJS, loopEventListener));
\ No newline at end of file
// Via https://github.com/coonsta/cache-polyfill/blob/master/dist/serviceworker-cache-polyfill.js
// Adds in some functionality missing in Chrome 40.
if (!Cache.prototype.add) {
Cache.prototype.add = function add(request) {
return this.addAll([request]);
};
}
if (!Cache.prototype.addAll) {
Cache.prototype.addAll = function addAll(requests) {
var cache = this;
// Since DOMExceptions are not constructable:
function NetworkError(message) {
this.name = 'NetworkError';
this.code = 19;
this.message = message;
}
NetworkError.prototype = Object.create(Error.prototype);
return Promise.resolve().then(function() {
if (arguments.length < 1) throw new TypeError();
// Simulate sequence<(Request or USVString)> binding:
var sequence = [];
requests = requests.map(function(request) {
if (request instanceof Request) {
return request;
}
else {
return String(request); // may throw TypeError
}
});
return Promise.all(
requests.map(function(request) {
if (typeof request === 'string') {
request = new Request(request);
}
var scheme = new URL(request.url).protocol;
if (scheme !== 'http:' && scheme !== 'https:') {
throw new NetworkError("Invalid scheme");
}
return fetch(request.clone());
})
);
}).then(function(responses) {
// TODO: check that requests don't overwrite one another
// (don't think this is possible to polyfill due to opaque responses)
return Promise.all(
responses.map(function(response, i) {
return cache.put(requests[i], response);
})
);
}).then(function() {
return undefined;
});
};
}
if (!CacheStorage.prototype.match) {
// This is probably vulnerable to race conditions (removing caches etc)
CacheStorage.prototype.match = function match(request, opts) {
var caches = this;
return this.keys().then(function(cacheNames) {
var match;
return cacheNames.reduce(function(chain, cacheName) {
return chain.then(function() {
return match || caches.open(cacheName).then(function(cache) {
return cache.match(request, opts);
}).then(function(response) {
match = response;
return match;
});
});
}, Promise.resolve());
});
};
}
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// This polyfill provides Cache.add(), Cache.addAll(), and CacheStorage.match(),
// which are not implemented in Chrome 40.
// Should not be needed for Chromium > 47 And Firefox > 39
// See https://developer.mozilla.org/en-US/docs/Web/API/Cache
importScripts('./serviceworker-cache-polyfill.js');
// While overkill for this specific sample in which there is only one cache,
// this is one best practice that can be followed in general to keep track of
// multiple caches used by a given service worker, and keep them all versioned.
// It maps a shorthand identifier for a cache to a specific, versioned cache name.
// Note that since global state is discarded in between service worker restarts, these
// variables will be reinitialized each time the service worker handles an event, and you
// should not attempt to change their values inside an event handler. (Treat them as constants.)
// If at any point you want to force pages that use this service worker to start using a fresh
// cache, then increment the CACHE_VERSION value. It will kick off the service worker update
// flow and the old cache(s) will be purged as part of the activate event handler when the
// updated service worker is activated.
var CACHE_VERSION = 1;
var CURRENT_CACHES = {
'cribjs': 'post-message-cache-v' + CACHE_VERSION
};
var URL_LIST;
self.addEventListener('activate', function(event) {
// Delete all caches that aren't named in CURRENT_CACHES.
// While there is only one cache in this example, the same logic will handle the case where
// there are multiple versioned caches.
var expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {
return CURRENT_CACHES[key];
});
URL_LIST = {};
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (expectedCacheNames.indexOf(cacheName) == -1) {
// If this cache name isn't present in the array of "expected" cache names, then delete it.
console.log('Deleting out of date cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
self.addEventListener('fetch', function(event) {
//console.log('Handling fetch event for', event.request.url);
if (event.request.method === "GET") {
/// XXX Why is URL_LIST undefined in some cases???
if (URL_LIST === undefined)
URL_LIST = {};
if (URL_LIST[event.request.url] === undefined) {
URL_LIST[event.request.url] = {
cached: false,
request_number: 0,
url: event.request.url,
};
}
URL_LIST[event.request.url].request_number = URL_LIST[event.request.url].request_number + 1;
event.respondWith(
caches.open(CURRENT_CACHES['cribjs']).then(function(cache) {
return cache.match(event.request).then(function(response) {
if (response) {
// If there is an entry in the cache for event.request, then response will be defined
// and we can just return it. Note that in this example, only font resources are cached.
//console.log(' Found response in cache:', response.url);
URL_LIST[event.request.url].cached = true;
return response;
} else {
// Otherwise, if there is no entry in the cache for event.request, response will be
// undefined, and we need to fetch() the resource.
//console.log(' No response for %s found in cache. About to fetch from network...', event.request.url);
// We call .clone() on the request since we might use it in a call to cache.put() later on.
// Both fetch() and cache.put() "consume" the request, so we need to make a copy.
// (see https://fetch.spec.whatwg.org/#dom-request-clone)
return fetch(event.request.clone()).then(function(response) {
//console.log(' Response for %s from network is: %O', event.request.url, response.url);
// Return the original response object, which will be used to fulfill the resource request.
//cache.put(event.request, response.clone());
return response;
});
}
}).catch(function(error) {
// This catch() will handle exceptions that arise from the match() or fetch() operations.
// Note that a HTTP error response (e.g. 404) will NOT trigger an exception.
// It will return a normal response object that has the appropriate error code set.
console.error(' Error in fetch handler:', error);
throw error;
});
})
);
} else {
event.respondWith(fetch(event.request));
}
});
self.addEventListener('message', function(event) {
var request;
console.log('Handling message event:', event);
// URL_LIST is a hack it should use a persistent object
if (URL_LIST === undefined)
URL_LIST = {};
caches.open(CURRENT_CACHES['cribjs']).then(function(cache) {
switch (event.data.command) {
// This command returns a list of the URLs corresponding to the Request objects
// that serve as keys for the current cache.
case 'keys':
cache.keys().then(function(requests) {
var urls = requests.map(function(request) {
return request.url;
});
// event.ports[0] corresponds to the MessagePort that was transferred as part of the controlled page's
// call to controller.postMessage(). Therefore, event.ports[0].postMessage() will trigger the onmessage
// handler from the controlled page.
// It's up to you how to structure the messages that you send back; this is just one example.
event.ports[0].postMessage({
error: null,
urls: urls.sort()
});
});
break;
case 'allDocs':
cache.keys().then(function(requests) {
var urls = requests.map(function(request) {
return request.url;
}), i, i_len, url;
for (i = 0, i_len = urls.length; i < i_len; i += 1) {
url = urls[i];
if (URL_LIST[url] === undefined) {
URL_LIST[url] = {
cached: true,
request_number: 0,
url: url,
};
}
}
event.ports[0].postMessage({
error: null,
urls: URL_LIST
});
});
break;
// This command adds a new request/response pair to the cache.
case 'add':
// If event.data.url isn't a valid URL, new Request() will throw a TypeError which will be handled
// by the outer .catch().
// Hardcode {mode: 'no-cors} since the default for new Requests constructed from strings is to require
// CORS, and we don't have any way of knowing whether an arbitrary URL that a user entered supports CORS.
request = new Request(event.data.url, {mode: 'no-cors'}),
response = new Response(event.data.information);
if (URL_LIST[request.url] === undefined) {
URL_LIST[request.url] = {
cached: true,
request_number: 0,
url: request.url,
};
}
cache.put(request, response).then(function() {
event.ports[0].postMessage({
error: null
});
});
break;
// This command removes a request/response pair from the cache (assuming it exists).
case 'delete':
request = new Request(event.data.url, {mode: 'no-cors'});
cache.delete(request).then(function(success) {
event.ports[0].postMessage({
error: success ? null : 'Item was not found in the cache.'
});
});
if (URL_LIST[request.url] !== undefined) {
URL_LIST[request.url].cached = false;
}
break;
default:
// This will be handled by the outer .catch().
throw 'Unknown command: ' + event.data.command;
}
}).catch(function(error) {
// If the promise rejects, handle it by returning a standardized error message to the controlled page.
console.error('Message handling failed:', error);
event.ports[0].postMessage({
error: error.toString()
});
});
});
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