whisper.cat stuff
This commit is contained in:
386
weboasis/text/base64/base64.js
Normal file
386
weboasis/text/base64/base64.js
Normal file
@ -0,0 +1,386 @@
|
||||
'use strict';
|
||||
// Async base64 encoding and decoding
|
||||
// Bundles TextEncoderLite and b64.js for utf8 and typed array support.
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||
|
||||
function asyncToBase64(value, encoding, onSuccess, onError) {
|
||||
try {
|
||||
switch (encoding) {
|
||||
case 'ascii':
|
||||
var result = window.btoa(value);
|
||||
onSuccess(result);
|
||||
break;
|
||||
case 'utf8':
|
||||
value = Base64Utils.encodeToUtf8Array(value);
|
||||
// Fall through into byte array case.
|
||||
case undefined:
|
||||
var result = Base64Utils.uint8ToBase64(value);
|
||||
onSuccess(result);
|
||||
break;
|
||||
default:
|
||||
onError('Unknown encoding \'' + encoding + '\'.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.name === 'InvalidCharacterError') {
|
||||
onError('Couldn\'t convert non-latin1 characters to base64.');
|
||||
} else if (error.message) {
|
||||
onError('Couldn\'t convert to base64 because ' + error.message + '.');
|
||||
} else {
|
||||
onError('Couldn\'t convert to base64 because ' + error + '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function asyncFromBase64(value, encoding, onSuccess, onError) {
|
||||
try {
|
||||
switch (encoding) {
|
||||
case 'ascii':
|
||||
var result = window.atob(Base64Utils.cleanupBase64(value));
|
||||
onSuccess(result);
|
||||
break;
|
||||
case 'utf8':
|
||||
var utf8ByteArray = Base64Utils.b64ToByteArray(Base64Utils.cleanupBase64(value));
|
||||
var result = Base64Utils.decodeFromUtf8Array(utf8ByteArray);
|
||||
onSuccess(result);
|
||||
break;
|
||||
default:
|
||||
onError('Unknown encoding \'' + encoding + '\'.');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.name == 'InvalidCharacterError') {
|
||||
onError('Couldn\'t convert because the base64 decodes to non-latin1 characters.');
|
||||
} else if (error.message) {
|
||||
onError('Couldn\'t convert from base64 because ' + error.message + '.');
|
||||
} else {
|
||||
onError('Couldn\'t convert from base64 because ' + error + '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Utility helper functions:
|
||||
// cleanupBase64, uint8ToBase64, b64ToByteArray, encodeToUtf8Array, decodeFromUtf8Array
|
||||
function Base64Utils() {
|
||||
}
|
||||
|
||||
// Remove invalid characters and correct padding to accept more liberal input.
|
||||
// This is modified from https://github.com/feross/buffer/blob/master/index.js by feross et. al.
|
||||
Base64Utils.cleanupBase64 = function(dirty) {
|
||||
// Trim whitespace.
|
||||
dirty = dirty.trim ? dirty.trim() : dirty.replace(/^\s+|\s+$/g, '');
|
||||
|
||||
// Remove anything outside our range.
|
||||
var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g;
|
||||
dirty = dirty.replace(INVALID_BASE64_RE, '');
|
||||
|
||||
if (dirty.length < 2)
|
||||
return '';
|
||||
|
||||
// Ensure padding is correct.
|
||||
while (dirty.length % 4 !== 0)
|
||||
dirty = dirty + '=';
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
// b64.js from https://github.com/beatgammit/base64-js by beatgammit, feross, and others.
|
||||
// Modified to only export uint8ToBase64 and b64ToByteArray.
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2014
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
;(function (exports) {
|
||||
var i, code, lookup, revLookup, Arr;
|
||||
|
||||
function initIfNeeded() {
|
||||
if (code !== undefined)
|
||||
return;
|
||||
|
||||
code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
lookup = [];
|
||||
for (i = 0; i < code.length; i++)
|
||||
lookup[i] = code[i];
|
||||
revLookup = [];
|
||||
for (i = 0; i < code.length; ++i)
|
||||
revLookup[code.charCodeAt(i)] = i;
|
||||
revLookup['-'.charCodeAt(0)] = 62;
|
||||
revLookup['_'.charCodeAt(0)] = 63;
|
||||
Arr = (typeof Uint8Array !== 'undefined') ? Uint8Array : Array;
|
||||
}
|
||||
|
||||
function decode(elt) {
|
||||
var v = revLookup[elt.charCodeAt(0)];
|
||||
return v !== undefined ? v : -1;
|
||||
}
|
||||
|
||||
function b64ToByteArray(b64) {
|
||||
initIfNeeded();
|
||||
|
||||
var i, j, l, tmp, placeHolders, arr;
|
||||
|
||||
if (b64.length % 4 > 0)
|
||||
throw new Error('the length is not a multiple of 4');
|
||||
|
||||
// the number of equal signs (place holders)
|
||||
// if there are two placeholders, than the two characters before it
|
||||
// represent one byte
|
||||
// if there is only one, then the three characters before it represent 2 bytes
|
||||
// this is just a cheap hack to not do indexOf twice
|
||||
var len = b64.length;
|
||||
placeHolders = b64.charAt(len - 2) === '=' ? 2 : b64.charAt(len - 1) === '=' ? 1 : 0;
|
||||
|
||||
// base64 is 4/3 + up to two characters of the original data
|
||||
arr = new Arr(b64.length * 3 / 4 - placeHolders);
|
||||
|
||||
// if there are placeholders, only get up to the last complete 4 chars
|
||||
l = placeHolders > 0 ? b64.length - 4 : b64.length;
|
||||
|
||||
var L = 0;
|
||||
|
||||
function push(v) {
|
||||
arr[L++] = v;
|
||||
}
|
||||
|
||||
for (i = 0, j = 0; i < l; i += 4, j += 3) {
|
||||
tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3));
|
||||
push((tmp & 0xFF0000) >> 16);
|
||||
push((tmp & 0xFF00) >> 8);
|
||||
push(tmp & 0xFF);
|
||||
}
|
||||
|
||||
if (placeHolders === 2) {
|
||||
tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4);
|
||||
push(tmp & 0xFF);
|
||||
} else if (placeHolders === 1) {
|
||||
tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2);
|
||||
push((tmp >> 8) & 0xFF);
|
||||
push(tmp & 0xFF);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function encode(num) {
|
||||
return lookup[num];
|
||||
}
|
||||
|
||||
function tripletToBase64(num) {
|
||||
return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F);
|
||||
}
|
||||
|
||||
function encodeChunk(uint8, start, end) {
|
||||
var temp;
|
||||
var output = [];
|
||||
for (var i = start; i < end; i += 3) {
|
||||
temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]);
|
||||
output.push(tripletToBase64(temp));
|
||||
}
|
||||
return output.join('');
|
||||
}
|
||||
|
||||
function uint8ToBase64(uint8) {
|
||||
initIfNeeded();
|
||||
|
||||
var i;
|
||||
var extraBytes = uint8.length % 3; // if we have 1 byte left, pad 2 bytes
|
||||
var output = '';
|
||||
var parts = [];
|
||||
var temp, length;
|
||||
var maxChunkLength = 16383; // must be multiple of 3
|
||||
|
||||
// go through the array every three bytes, we'll deal with trailing stuff later
|
||||
for (i = 0, length = uint8.length - extraBytes; i < length; i += maxChunkLength)
|
||||
parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > length ? length : (i + maxChunkLength)));
|
||||
|
||||
// pad the end with zeros, but make sure to not forget the extra bytes
|
||||
switch (extraBytes) {
|
||||
case 1:
|
||||
temp = uint8[uint8.length - 1];
|
||||
output += encode(temp >> 2);
|
||||
output += encode((temp << 4) & 0x3F);
|
||||
output += '==';
|
||||
break;
|
||||
case 2:
|
||||
temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]);
|
||||
output += encode(temp >> 10);
|
||||
output += encode((temp >> 4) & 0x3F);
|
||||
output += encode((temp << 2) & 0x3F);
|
||||
output += '=';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
parts.push(output);
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
exports.uint8ToBase64 = uint8ToBase64;
|
||||
exports.b64ToByteArray = b64ToByteArray;
|
||||
}(Base64Utils));
|
||||
|
||||
// TextEncoderLite from https://github.com/coolaj86/TextEncoderLite/blob/master/index.js by
|
||||
// coolaj86, feross, and others.
|
||||
// Modified to only export encodeToUtf8Array and decodeFromUtf8Array.
|
||||
//
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Feross Aboukhadijeh, and other contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
;(function (exports) {
|
||||
function utf8ToBytes(string, units) {
|
||||
units = units || Infinity;
|
||||
var codePoint;
|
||||
var length = string.length;
|
||||
var leadSurrogate = null;
|
||||
var bytes = [];
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
|
||||
// is surrogate component
|
||||
if (codePoint > 0xD7FF && codePoint < 0xE000) {
|
||||
// last char was a lead
|
||||
if (leadSurrogate) {
|
||||
// 2 leads in a row
|
||||
if (codePoint < 0xDC00) {
|
||||
if ((units -= 3) > -1)
|
||||
bytes.push(0xEF, 0xBF, 0xBD);
|
||||
leadSurrogate = codePoint;
|
||||
continue;
|
||||
} else {
|
||||
// valid surrogate pair
|
||||
codePoint = leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00 | 0x10000;
|
||||
leadSurrogate = null;
|
||||
}
|
||||
} else {
|
||||
// no lead yet
|
||||
if (codePoint > 0xDBFF) {
|
||||
// unexpected trail
|
||||
if ((units -= 3) > -1)
|
||||
bytes.push(0xEF, 0xBF, 0xBD);
|
||||
continue;
|
||||
} else if (i + 1 === length) {
|
||||
// unpaired lead
|
||||
if ((units -= 3) > -1)
|
||||
bytes.push(0xEF, 0xBF, 0xBD);
|
||||
continue;
|
||||
} else {
|
||||
// valid lead
|
||||
leadSurrogate = codePoint;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if (leadSurrogate) {
|
||||
// valid bmp char, but last char was a lead
|
||||
if ((units -= 3) > -1)
|
||||
bytes.push(0xEF, 0xBF, 0xBD);
|
||||
leadSurrogate = null;
|
||||
}
|
||||
|
||||
// encode utf8
|
||||
if (codePoint < 0x80) {
|
||||
if ((units -= 1) < 0)
|
||||
break;
|
||||
bytes.push(codePoint);
|
||||
} else if (codePoint < 0x800) {
|
||||
if ((units -= 2) < 0)
|
||||
break;
|
||||
bytes.push(
|
||||
codePoint >> 0x6 | 0xC0,
|
||||
codePoint & 0x3F | 0x80
|
||||
);
|
||||
} else if (codePoint < 0x10000) {
|
||||
if ((units -= 3) < 0)
|
||||
break;
|
||||
bytes.push(
|
||||
codePoint >> 0xC | 0xE0,
|
||||
codePoint >> 0x6 & 0x3F | 0x80,
|
||||
codePoint & 0x3F | 0x80
|
||||
);
|
||||
} else if (codePoint < 0x200000) {
|
||||
if ((units -= 4) < 0)
|
||||
break;
|
||||
bytes.push(
|
||||
codePoint >> 0x12 | 0xF0,
|
||||
codePoint >> 0xC & 0x3F | 0x80,
|
||||
codePoint >> 0x6 & 0x3F | 0x80,
|
||||
codePoint & 0x3F | 0x80
|
||||
);
|
||||
} else {
|
||||
throw new Error('Invalid code point');
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function utf8Slice(buf, start, end) {
|
||||
var res = '';
|
||||
var tmp = '';
|
||||
end = Math.min(buf.length, end || Infinity);
|
||||
start = start || 0;
|
||||
for (var i = start; i < end; i++) {
|
||||
if (buf[i] <= 0x7F) {
|
||||
res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i]);
|
||||
tmp = '';
|
||||
} else {
|
||||
tmp += '%' + buf[i].toString(16);
|
||||
}
|
||||
}
|
||||
return res + decodeUtf8Char(tmp);
|
||||
}
|
||||
|
||||
function decodeUtf8Char(str) {
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch (err) {
|
||||
return String.fromCharCode(0xFFFD); // UTF 8 invalid char
|
||||
}
|
||||
}
|
||||
|
||||
exports.encodeToUtf8Array = function(str) {
|
||||
var result;
|
||||
if ('undefined' === typeof Uint8Array)
|
||||
result = utf8ToBytes(str);
|
||||
else
|
||||
result = new Uint8Array(utf8ToBytes(str));
|
||||
return result;
|
||||
}
|
||||
|
||||
exports.decodeFromUtf8Array = function(bytes) {
|
||||
return utf8Slice(bytes, 0, bytes.length);
|
||||
}
|
||||
}(Base64Utils));
|
BIN
weboasis/text/base64/favicon.ico
Normal file
BIN
weboasis/text/base64/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
74
weboasis/text/base64/gulpfile.js
Normal file
74
weboasis/text/base64/gulpfile.js
Normal file
@ -0,0 +1,74 @@
|
||||
// Build step, just type:
|
||||
// gulp
|
||||
//
|
||||
// Prereqs:
|
||||
// 1) Install Node and NPM from https://nodejs.org
|
||||
// Known to work with Node 4.2.6 and NPM 2.14.12
|
||||
// 2) Install dependencies from package.json
|
||||
// npm install
|
||||
//
|
||||
// The purpose of this build step is just to crush the output filesize. Everything should work
|
||||
// when loaded directly out of the root directory without running this build step.
|
||||
|
||||
var gulp = require('gulp');
|
||||
|
||||
var del = require('del');
|
||||
var imagemin = require('gulp-imagemin');
|
||||
var pngquant = require('imagemin-pngquant');
|
||||
var inlineSource = require('gulp-inline-source');
|
||||
var htmlmin = require('gulp-htmlmin');
|
||||
var uglify = require('gulp-uglify');
|
||||
|
||||
var outDir = 'out/';
|
||||
|
||||
var paths = {
|
||||
html: ['index.html'],
|
||||
images: ['images/*.png'],
|
||||
extras: ['manifest.json', 'favicon.ico'],
|
||||
scripts: ['simple-offline-service-worker.js']
|
||||
};
|
||||
|
||||
gulp.task('clean', function() {
|
||||
return del(outDir);
|
||||
});
|
||||
|
||||
// Optimize and copy images
|
||||
gulp.task('images', ['clean'], function() {
|
||||
var imageminOptions = {
|
||||
use: [pngquant({quality: '65-80', speed: 1})]
|
||||
};
|
||||
return gulp.src(paths.images)
|
||||
.pipe(imagemin(imageminOptions))
|
||||
.pipe(gulp.dest(outDir + 'images/'));
|
||||
});
|
||||
|
||||
gulp.task('minifyHtmlCssJs', ['clean'], function() {
|
||||
var inlineSourceOptions = {
|
||||
compress: false
|
||||
};
|
||||
var htmlminOptions = {
|
||||
collapseWhitespace: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
preserveLineBreaks: true,
|
||||
removeComments: true
|
||||
};
|
||||
return gulp.src(paths.html)
|
||||
.pipe(inlineSource(inlineSourceOptions))
|
||||
.pipe(htmlmin(htmlminOptions))
|
||||
.pipe(gulp.dest(outDir));
|
||||
});
|
||||
|
||||
gulp.task('minifyExternalScripts', ['clean'], function() {
|
||||
return gulp.src(paths.scripts)
|
||||
.pipe(uglify())
|
||||
.pipe(gulp.dest(outDir));
|
||||
});
|
||||
|
||||
gulp.task('copy', ['clean'], function() {
|
||||
// Copy extras
|
||||
gulp.src(paths.extras)
|
||||
.pipe(gulp.dest(outDir));
|
||||
});
|
||||
|
||||
gulp.task('default', ['clean', 'copy', 'images', 'minifyHtmlCssJs', 'minifyExternalScripts']);
|
322
weboasis/text/base64/index.html
Normal file
322
weboasis/text/base64/index.html
Normal file
@ -0,0 +1,322 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, user-scalable=no">
|
||||
<meta name="description" content="A fast and simple tool to convert to and from base64.">
|
||||
<meta name="theme-color" content="#2196f3">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="icon" href="favicon.ico">
|
||||
<meta http-equiv="cleartype" content="on">
|
||||
<title>WebOasis - Base64 Converter</title>
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
background: #fafafa;
|
||||
font-family: 'Roboto', 'Helvetica', 'Trebuchet MS1', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Disable rubber-band effect on scroll. */
|
||||
-ms-touch-action: none;
|
||||
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
.header {
|
||||
background: #0078d4;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.26);
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
padding: 0.65em;
|
||||
|
||||
-webkit-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.topRight {
|
||||
color: rgba(250,250,250,0.8);
|
||||
float: right;
|
||||
font-size: 14px;
|
||||
/* This aligns the link when directly next to the header. */
|
||||
padding-top: 4px;
|
||||
}
|
||||
.topRight:hover, .topRight:focus {
|
||||
color: rgb(250,250,250);
|
||||
}
|
||||
.io {
|
||||
-webkit-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
.tile {
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2);
|
||||
margin: 2em;
|
||||
/* When squished (mobile landscape), have a minimum standard of readability. */
|
||||
min-height: 7em;
|
||||
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex: 1 0 auto;
|
||||
flex: 1 0 auto;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
width: 0;
|
||||
}
|
||||
/* Ensure there's not twice the padding when side-by-side. */
|
||||
#leftTile { margin-right: 1em; }
|
||||
#rightTile { margin-left: 1em; }
|
||||
.type {
|
||||
border-bottom: 1px solid #dbdbdb;
|
||||
color: rgba(0,0,0,0.87);
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
padding: 0.65em;
|
||||
|
||||
-webkit-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.content {
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
/* Margins must equal the total padding on children to ensure scrollbars stay flush. */
|
||||
margin-bottom: 2em;
|
||||
margin-right: 2em;
|
||||
-webkit-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.textarea {
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
/* Workaround to improve experience on browsers without new flexbox. */
|
||||
min-height: 3em;
|
||||
outline: none;
|
||||
overflow: auto;
|
||||
/* This padding must match the imagearea's padding. */
|
||||
padding: 1em;
|
||||
position: absolute;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
}
|
||||
.textarea[readonly], .textarea[readonly='readonly'] {
|
||||
background: #fff;
|
||||
color: rgba(0,0,0,0.54);
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.errorSource {
|
||||
background-color: #ffebee;
|
||||
color: rgba(0,0,0,0.38);
|
||||
}
|
||||
.errorDestination {
|
||||
color: rgba(0,0,0,0.38);
|
||||
}
|
||||
.imagearea {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
height: 100%;
|
||||
/* Workaround to improve experience on browsers without new flexbox. */
|
||||
min-height: 3em;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
/* This padding must match the textarea's padding. */
|
||||
padding: 1em;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
/* Custom upload button to workaround multiple cross-browser issues. */
|
||||
/* The basic approach is to hide an upload control on top of a button image. */
|
||||
.customUpload {
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
width: 60px;
|
||||
}
|
||||
/* When an image is present, move button to the top-left. */
|
||||
.imagearea.containsImage .customUpload {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
}
|
||||
.customUpload svg {
|
||||
/* drop-shadow does not support the exact blur syntax so this has been approximated. */
|
||||
-webkit-filter: drop-shadow(0 2px 1px rgba(0,0,0,0.14)) drop-shadow(0 1px 5px rgba(0,0,0,0.12)) drop-shadow(0 1px 1px rgba(0,0,0,0.1));
|
||||
filter: drop-shadow(0 2px 1px rgba(0,0,0,0.14)) drop-shadow(0 1px 5px rgba(0,0,0,0.12)) drop-shadow(0 1px 1px rgba(0,0,0,0.1));
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.customUpload svg path {
|
||||
fill: #2196f3;
|
||||
}
|
||||
.customUpload:hover svg path {
|
||||
fill: #1976d2;
|
||||
}
|
||||
.customUpload input {
|
||||
cursor: pointer;
|
||||
height: 200%;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
/* Disable blue glow on Chrome for Android due to cursor: pointer. */
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
top: -100%;
|
||||
width: 100%;
|
||||
/* A filter on the svg will give it a stacking context, so ensure this stays on top. */
|
||||
z-index: 99;
|
||||
}
|
||||
/* Drag and drop styles. */
|
||||
#leftTile .dragover {
|
||||
background-color: #e1f5fe;
|
||||
}
|
||||
/* On small screens, stack the areas without margin. */
|
||||
@media (max-width: 1000px) {
|
||||
.io {
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tile {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
|
||||
-webkit-flex: 1 0 auto;
|
||||
flex: 1 0 auto;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
/* Remove side-by-side padding special-case. */
|
||||
#leftTile { margin-right: 0; }
|
||||
#rightTile { margin-left: 0; }
|
||||
/* Workaround for crbug.com/586872 and Android 4.3 where the background is visible. */
|
||||
body {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
/* Custom select box to workaround multiple cross-browser issues. */
|
||||
/* The basic idea is to stack an invisible select above custom text and a dropdown icon. */
|
||||
.customSelect {
|
||||
border-bottom: 1px solid #dbdbdb;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.customSelect svg {
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
vertical-align: text-bottom;
|
||||
width: 1em;
|
||||
/* Hairline 2px adjustment just for aesthetics. */
|
||||
margin-left: -2px;
|
||||
}
|
||||
.customSelect svg path {
|
||||
fill: rgba(0,0,0,0.87);
|
||||
}
|
||||
.customSelect select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
font: inherit;
|
||||
/* Position at +/- 10% to increase hit-testable area. */
|
||||
height: 120%;
|
||||
left: -10%;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: -10%;
|
||||
width: 120%;
|
||||
}
|
||||
.customSelect select:focus {
|
||||
outline: none;
|
||||
}
|
||||
/* Custom focus support because we cannot use parent CSS selectors. */
|
||||
.customSelect#focused {
|
||||
border-bottom: 1px solid rgba(0,0,0,0.87);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">WebOasis - Base64 Converter<a class="topRight" href="../">Go Back</a></div>
|
||||
<div class="io">
|
||||
<div id="leftTile" class="tile">
|
||||
<div class="type">
|
||||
<div class="customSelect">
|
||||
<select id="leftTypeSelect">
|
||||
<option value="text" selected="true">Text</option>
|
||||
<option value="image">Image</option>
|
||||
</select>
|
||||
<span id="leftTypeText">Text</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-100 -100 300 300">
|
||||
<path d="M0 0L100 0L50 80Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div id="leftContent" class="content">
|
||||
<textarea id="leftTextarea" class="textarea" spellcheck="false">Hello world</textarea>
|
||||
<div id="leftImageArea" class="imagearea hidden">
|
||||
<div class="customUpload">
|
||||
<input id="leftImageInput" type="file" accept="image/*" title="">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="rightTile" class="tile">
|
||||
<div class="type">
|
||||
<div class="customSelect">
|
||||
<select id="rightTypeSelect">
|
||||
<option value="base64utf8" selected="true">Base64 (utf8)</option>
|
||||
<option value="base64ascii">Base64 (ascii)</option>
|
||||
<option value="base64image" disabled="true">Base64 (image)</option>
|
||||
</select>
|
||||
<span id="rightTypeText">Base64</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-100 -100 300 300">
|
||||
<path d="M0 0L100 0L50 80Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<textarea id="rightTextarea" class="textarea" spellcheck="false">SGVsbG8gd29ybGQ=</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="base64.js" inline></script>
|
||||
<script src="index.js" inline></script>
|
||||
</html>
|
289
weboasis/text/base64/index.js
Normal file
289
weboasis/text/base64/index.js
Normal file
@ -0,0 +1,289 @@
|
||||
'use strict';
|
||||
// Business logic for base64 encoder (index.html).
|
||||
// Requires asyncToBase64 and asyncFromBase64 from base64.js
|
||||
|
||||
// Initialization of these conversion type constants and locals is done in DOMContentLoaded.
|
||||
var LeftConversionTypes, RightConversionTypes;
|
||||
|
||||
// These state variables are for the selected conversion types, initialized in DOMContentLoaded.
|
||||
var leftConversionType, rightConversionType;
|
||||
|
||||
// This state variable lets us change the conversion type without overwriting the user's last input.
|
||||
var userLastChangedRightSide = false;
|
||||
|
||||
// FIXME: Check if this can be cleared more often to reduce peak memory usage.
|
||||
var currentFile;
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
// Must keep in sync with #leftTypeSelect's DOM.
|
||||
LeftConversionTypes = {
|
||||
'text': {displayName: 'Text', isImage: false},
|
||||
'image': {displayName: 'Image', isImage: true}
|
||||
}
|
||||
|
||||
// Must keep in sync with the #rightTypeSelect's DOM.
|
||||
RightConversionTypes = {
|
||||
'base64utf8': {displayName: 'Base64', encoding: 'utf8', forImage: false},
|
||||
'base64ascii': {displayName: 'Base64 (ascii)', encoding: 'ascii', forImage: false},
|
||||
'base64image': {displayName: 'Base64 (image)', encoding: 'utf8', forImage: true}
|
||||
}
|
||||
|
||||
leftConversionType = LeftConversionTypes[Object.keys(LeftConversionTypes)[0]];
|
||||
rightConversionType = RightConversionTypes[Object.keys(RightConversionTypes)[0]];
|
||||
|
||||
leftTextarea.addEventListener('input', function() {
|
||||
userLastChangedRightSide = false;
|
||||
updateConversion();
|
||||
});
|
||||
|
||||
leftImageInput.addEventListener('change', function(event) {
|
||||
if (event.target.files[0]) {
|
||||
currentFile = event.target.files[0];
|
||||
userLastChangedRightSide = false;
|
||||
updateConversion();
|
||||
}
|
||||
});
|
||||
|
||||
rightTextarea.addEventListener('input', function() {
|
||||
userLastChangedRightSide = true;
|
||||
updateConversion();
|
||||
});
|
||||
|
||||
leftTypeSelect.addEventListener('change', function() {
|
||||
onLeftTypeChanged();
|
||||
});
|
||||
|
||||
rightTypeSelect.addEventListener('change', function() {
|
||||
onRightTypeChanged();
|
||||
});
|
||||
|
||||
// Ensure there is a focus effect on the custom type select boxes.
|
||||
leftTypeSelect.addEventListener('focus', function(event) {
|
||||
leftTypeSelect.parentElement.setAttribute('id', 'focused');
|
||||
});
|
||||
leftTypeSelect.addEventListener('blur', function(event) {
|
||||
leftTypeSelect.parentElement.removeAttribute('id');
|
||||
});
|
||||
rightTypeSelect.addEventListener('focus', function(event) {
|
||||
rightTypeSelect.parentElement.setAttribute('id', 'focused');
|
||||
});
|
||||
rightTypeSelect.addEventListener('blur', function(event) {
|
||||
rightTypeSelect.parentElement.removeAttribute('id');
|
||||
});
|
||||
|
||||
// Drag and drop support.
|
||||
var insideCount = 0;
|
||||
leftContent.addEventListener('dragenter', function(event) {
|
||||
insideCount++;
|
||||
leftTextarea.classList.add('dragover');
|
||||
leftImageArea.classList.add('dragover');
|
||||
});
|
||||
leftContent.addEventListener('dragleave', function(event) {
|
||||
if (--insideCount === 0) {
|
||||
leftTextarea.classList.remove('dragover');
|
||||
leftImageArea.classList.remove('dragover');
|
||||
}
|
||||
});
|
||||
document.body.addEventListener('dragover', function(event) {
|
||||
// Required for the drop event to work properly.
|
||||
event.preventDefault();
|
||||
});
|
||||
document.body.addEventListener('drop', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
insideCount = 0;
|
||||
leftTextarea.classList.remove('dragover');
|
||||
leftImageArea.classList.remove('dragover');
|
||||
if (event.dataTransfer.files.length > 0)
|
||||
onDropFiles(event.dataTransfer.files);
|
||||
});
|
||||
|
||||
// Update state if a select value is already present (e.g., if the user navigated back.)
|
||||
var leftConversionTypeFromDOM = LeftConversionTypes[leftTypeSelect.value];
|
||||
if (leftConversionTypeFromDOM !== leftConversionType)
|
||||
onLeftTypeChanged();
|
||||
var rightConversionTypeFromDOM = RightConversionTypes[rightTypeSelect.value];
|
||||
if (rightConversionTypeFromDOM !== rightConversionType)
|
||||
onRightTypeChanged();
|
||||
|
||||
// Bootup our service worker for offline support.
|
||||
if ('serviceWorker' in navigator)
|
||||
navigator.serviceWorker.register('simple-offline-service-worker.js');
|
||||
});
|
||||
|
||||
function updateConversion() {
|
||||
// Cleanup any leftover errors.
|
||||
leftImageArea.classList.remove('errorSource');
|
||||
leftTextarea.classList.remove('errorSource');
|
||||
rightTextarea.classList.remove('errorSource');
|
||||
leftTextarea.classList.remove('errorDestination');
|
||||
rightTextarea.classList.remove('errorDestination');
|
||||
|
||||
if (leftConversionType.isImage)
|
||||
convertImage();
|
||||
else
|
||||
bidirectionalTextConversion(!userLastChangedRightSide);
|
||||
}
|
||||
|
||||
// TODO: Gracefully disable this if the FileReader/etc APIs are not available.
|
||||
function convertImage() {
|
||||
var setBackgroundImage = function(optionalDataUri) {
|
||||
if (optionalDataUri) {
|
||||
leftImageArea.classList.add('containsImage');
|
||||
leftImageArea.style.backgroundImage = 'url(' + optionalDataUri + ')';
|
||||
} else {
|
||||
leftImageArea.classList.remove('containsImage');
|
||||
leftImageArea.style.backgroundImage = '';
|
||||
}
|
||||
}
|
||||
var success = function(value) {
|
||||
var dataUri = 'data:' + imageType(currentFile) + ';base64,' + value;
|
||||
rightTextarea.value = '<img src="' + dataUri + '">';
|
||||
setBackgroundImage(dataUri);
|
||||
}
|
||||
var error = function(message) {
|
||||
leftImageArea.classList.add('errorSource');
|
||||
rightTextarea.classList.add('errorDestination');
|
||||
rightTextarea.value = message;
|
||||
setBackgroundImage(undefined);
|
||||
}
|
||||
|
||||
if (!currentFile) {
|
||||
rightTextarea.value = 'No image';
|
||||
setBackgroundImage(undefined);
|
||||
return;
|
||||
}
|
||||
if (imageType(currentFile).indexOf('image') === -1) {
|
||||
error('Image type \'' + imageType(currentFile) + '\' was not recognized.');
|
||||
return;
|
||||
}
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(result) {
|
||||
var imageFileAsArray = new Uint8Array(result.target.result);
|
||||
asyncToBase64(imageFileAsArray, undefined, success, error);
|
||||
};
|
||||
reader.onerror = function(result) {
|
||||
error(result.message);
|
||||
};
|
||||
|
||||
rightTextarea.value = 'Converting...';
|
||||
reader.readAsArrayBuffer(currentFile);
|
||||
}
|
||||
|
||||
function bidirectionalTextConversion(convertLeftToRight) {
|
||||
var inputElement = convertLeftToRight ? leftTextarea : rightTextarea;
|
||||
var outputElement = convertLeftToRight ? rightTextarea : leftTextarea;
|
||||
var success = function(value) {
|
||||
outputElement.value = value;
|
||||
}
|
||||
var error = function(message) {
|
||||
inputElement.classList.add('errorSource');
|
||||
outputElement.classList.add('errorDestination');
|
||||
outputElement.value = message;
|
||||
}
|
||||
var converter = convertLeftToRight ? asyncToBase64 : asyncFromBase64;
|
||||
converter(inputElement.value, rightConversionType.encoding, success, error);
|
||||
}
|
||||
|
||||
function onLeftTypeChanged(optionalSkipConversionUpdate) {
|
||||
var selectedValue = leftTypeSelect.options[leftTypeSelect.selectedIndex].value;
|
||||
var newType = LeftConversionTypes[selectedValue];
|
||||
if (newType === leftConversionType)
|
||||
return;
|
||||
if (newType === undefined)
|
||||
throw 'Unknown conversion type: ' + selectedValue;
|
||||
leftConversionType = newType;
|
||||
leftTypeText.textContent = leftConversionType.displayName;
|
||||
|
||||
// Ensure the correct (image / text) left view is visible.
|
||||
if (leftConversionType.isImage == leftImageArea.classList.contains('hidden')) {
|
||||
leftImageArea.classList.toggle('hidden');
|
||||
leftTextarea.classList.toggle('hidden');
|
||||
}
|
||||
|
||||
updateRightTypeAfterLeftTypeChange();
|
||||
if (!optionalSkipConversionUpdate)
|
||||
updateConversion();
|
||||
}
|
||||
|
||||
function onRightTypeChanged(optionalSkipConversionUpdate) {
|
||||
var selectedValue = rightTypeSelect.options[rightTypeSelect.selectedIndex].value;
|
||||
var newType = RightConversionTypes[selectedValue];
|
||||
if (newType === rightConversionType)
|
||||
return;
|
||||
if (newType === undefined)
|
||||
throw 'Unknown conversion type: ' + selectedValue;
|
||||
rightConversionType = newType;
|
||||
rightTypeText.textContent = rightConversionType.displayName;
|
||||
if (rightConversionType.forImage)
|
||||
rightTextarea.setAttribute('readonly', 'readonly');
|
||||
else
|
||||
rightTextarea.removeAttribute('readonly');
|
||||
if (rightConversionType.forImage)
|
||||
userLastChangedRightSide = false;
|
||||
if (!optionalSkipConversionUpdate)
|
||||
updateConversion();
|
||||
}
|
||||
|
||||
function updateRightTypeAfterLeftTypeChange() {
|
||||
var firstEnabledIndex, firstEnabledType;
|
||||
for (var optionIndex = 0; optionIndex < rightTypeSelect.options.length; optionIndex++) {
|
||||
var option = rightTypeSelect.options[optionIndex];
|
||||
var rightType = RightConversionTypes[option.value];
|
||||
if (rightType === undefined)
|
||||
throw 'Unknown conversion type: ' + selectedValue;
|
||||
var enabled = leftConversionType.isImage === rightType.forImage;
|
||||
option.disabled = !enabled;
|
||||
if (!firstEnabledIndex && !firstEnabledType && enabled) {
|
||||
firstEnabledIndex = optionIndex;
|
||||
firstEnabledType = rightType;
|
||||
}
|
||||
}
|
||||
if (leftConversionType.isImage != rightConversionType.forImage) {
|
||||
rightTypeSelect.selectedIndex = firstEnabledIndex;
|
||||
onRightTypeChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
function onDropFiles(files) {
|
||||
if (files.length === 0)
|
||||
return;
|
||||
currentFile = files[0];
|
||||
if (!leftConversionType.isImage) {
|
||||
// Switch to the first image type.
|
||||
var leftConversions = Object.keys(LeftConversionTypes);
|
||||
for (var index = 0; index < leftConversions.length; index++) {
|
||||
if (LeftConversionTypes[leftConversions[index]].isImage) {
|
||||
leftTypeSelect.selectedIndex = index;
|
||||
onLeftTypeChanged(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
updateConversion();
|
||||
}
|
||||
|
||||
function imageType(file) {
|
||||
if (file.type !== '')
|
||||
return file.type;
|
||||
|
||||
// The stock browser on Android 4.3 returns an empty string for type. If the type is empty but a
|
||||
// file was uploaded, synthesize a mimetype from the filename.
|
||||
if (!file.name || file.name.length <= 0)
|
||||
return '';
|
||||
var extension = file.name.substr(file.name.lastIndexOf('.') + 1);
|
||||
switch (extension) {
|
||||
case('bmp'): return 'image/bmp';
|
||||
case('gif'): return 'image/gif';
|
||||
case('jpeg'): return 'image/jpeg';
|
||||
case('jpg'): return 'image/jpeg';
|
||||
case('jpe'): return 'image/jpeg';
|
||||
case('png'): return 'image/png';
|
||||
case('svg'): return 'image/svg+xml';
|
||||
case('svgz'): return 'image/svg+xml';
|
||||
case('webp'): return 'image/webp';
|
||||
case('ico'): return 'image/x-icon';
|
||||
}
|
||||
return '';
|
||||
}
|
21
weboasis/text/base64/manifest.json
Normal file
21
weboasis/text/base64/manifest.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"short_name": "Base64",
|
||||
"name": "Base64 Converter",
|
||||
"icons": [
|
||||
{
|
||||
"src": "images/appicon192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/appicon144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "./",
|
||||
"display": "standalone",
|
||||
"orientation": "any",
|
||||
"background_color": "#fafafa",
|
||||
"theme_color": "#2196f3"
|
||||
}
|
19
weboasis/text/base64/package.json
Normal file
19
weboasis/text/base64/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "Base64",
|
||||
"version": "1.0.0",
|
||||
"description": "Base64 Converter",
|
||||
"main": "index.html",
|
||||
"devDependencies": {
|
||||
"del": "^2.2.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-htmlmin": "^1.3.0",
|
||||
"gulp-imagemin": "^2.4.0",
|
||||
"gulp-inline-source": "^2.1.0",
|
||||
"gulp-uglify": "^1.5.3",
|
||||
"imagemin-pngquant": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
}
|
65
weboasis/text/base64/simple-offline-service-worker.js
Normal file
65
weboasis/text/base64/simple-offline-service-worker.js
Normal file
@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
// Simple offline service worker.
|
||||
//
|
||||
// On the main page include:
|
||||
// if ('serviceWorker' in navigator)
|
||||
// navigator.serviceWorker.register('simple-offline-service-worker.js');
|
||||
//
|
||||
// This cache always requests from the network to keep the cache fresh. There is a tradeoff here
|
||||
// because it'll wait ages for a slow network despite having a cached response ready to go. If stale
|
||||
// content is acceptable, use an 'eventually fresh' approach as described by Jake Archibald or
|
||||
// Nicolás Bevacqua in https://ponyfoo.com/articles/progressive-networking-serviceworker
|
||||
|
||||
var version = 'v4.2.0';
|
||||
|
||||
// To cache https://yoururl/subdirectory/ without listing index.html, use './'. If using a manifest,
|
||||
// 'start_url' should also be './'. You can also add additional files to this list.
|
||||
var offlineFiles = [ './' ];
|
||||
|
||||
self.addEventListener('install', function(event) {
|
||||
// Cache our list of files.
|
||||
event.waitUntil(
|
||||
caches.open(version).then(function(cache) {
|
||||
return cache.addAll(offlineFiles).then(function() {
|
||||
return self.skipWaiting();
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Remove any stale cache entries.
|
||||
self.addEventListener('activate', function(event) {
|
||||
event.waitUntil(
|
||||
caches.keys().then(function(keys) {
|
||||
return Promise.all(keys.map(function(key) {
|
||||
if (key !== version)
|
||||
return caches.delete(key);
|
||||
}));
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function(event) {
|
||||
event.respondWith(
|
||||
// FIXME: We should send off the fetch request and check the cache in parallel.
|
||||
fetch(event.request).then(function(networkReponse) {
|
||||
// Check if this request is already in our cache. We only want to cache previously
|
||||
// cached items to prevent the cache from getting polluted.
|
||||
return caches.open(version).then(function(cache) {
|
||||
return cache.match(event.request).then(function(cachedResponse) {
|
||||
if (!cachedResponse)
|
||||
return networkReponse;
|
||||
// Clone the response since we're also returning it below.
|
||||
cache.put(event.request, networkReponse.clone());
|
||||
return networkReponse;
|
||||
});
|
||||
});
|
||||
}).catch(function(networkError) {
|
||||
return caches.open(version).then(function(cache) {
|
||||
return cache.match(event.request).then(function(cachedResponse) {
|
||||
return cachedResponse || networkError;
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
Reference in New Issue
Block a user