contented/static/sdk.js

641 lines
17 KiB
JavaScript

;(function() {
"use strict";
var getCurrentScriptPath = function () {
// Determine current script path
// @ref https://stackoverflow.com/a/26023176
var scripts = document.querySelectorAll('script[src]');
var currentScript = scripts[scripts.length - 1].src;
var currentScriptChunks = currentScript.split('/');
var currentScriptFile = currentScriptChunks[currentScriptChunks.length - 1];
return currentScript.replace(currentScriptFile, '');
};
var loadScript = function(url) {
return new Promise(function(resolve, reject) {
var script = document.createElement('script');
script.onload = resolve;
script.onerror = reject;
script.src = url;
document.head.appendChild(script);
});
};
var formatBytes = function(bytes) {
var k = 1024, m = (1024*1024), g = (1024*1024*1024);
if (bytes < k) {
return bytes + " B";
} else if (bytes < m) {
return (bytes / k).toFixed(1) + " KiB";
} else if (bytes < g) {
return (bytes / m).toFixed(1) + " MiB";
} else {
return (bytes / g).toFixed(1) + " GiB";
}
};
// @ref https://stackoverflow.com/a/2117523
var guid = function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
var widgetHtml = `
<style type="text/css">
.contented {
box-sizing:border-box;
text-align: center;
border: 8px dashed lightgrey;
padding: 12px;
background:white; /* not transparent */
text-overflow:hidden;
overflow:auto;
width:100%;
height:100%;
position:relative;
}
.contented .contented-close {
position: absolute;
top: 12px;
right: 12px;
width: 24px;
height: 24px;
cursor: pointer;
}
.contented .contented-upload-type-selector {
display:block;
margin-bottom: 1em;
-webkit-user-select: none;
user-select: none;
}
.contented .contented-upload-type {
display:inline-block;
opacity:0.2;
transition:opacity linear 0.1s;
cursor:pointer;
}
.contented .contented-upload-type:hover {
opacity:0.5;
transition:opacity linear 0s;
}
.contented .contented-upload-type svg {
width:36px;
height:36px;
}
.contented .contented-upload-type.contented-upload-type-active {
opacity:1;
}
.contented.is-dragging {
background: lightblue;
}
.contented-content-area {
position:absolute;
top: 60px;
bottom: 10px;
left: 10px;
width: calc(100% - 20px);
/* Prevent blur under translateY */
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
}
.contented-content-area > div {
position: relative;
top: 50%;
transform: translateY(-50%);
}
.contented-upload-if {
display:none;
}
.contented-if-paste, .contented-if-drawing {
height:100%;
}
.contented-upload-if.contented-active {
display:block;
}
.contented textarea {
resize: none;
width:100%;
height:calc(100% - 1em - 15px);
box-sizing:border-box;
}
.contented-progress-bar {
display: block;
width:90%;
margin:0.5em auto 0 auto;
height:16px;
border-radius:8px;
background:lightgrey;
position:relative;
overflow:hidden;
}
.contented-progress-element {
position:absolute;
background:darkgreen;
left:0;
width:0%;
height:100%;
}
</style>
<div class="contented">
<div class="contented-close">
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
</svg>
</div>
<div class="contented-upload-type-selector">
<div class="contented-upload-type contented-upload-type-active" data-upload-type="drag" title="Drag and drop">
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z"></path>
</svg>
</div>
<div class="contented-upload-type" data-upload-type="file" title="Multiple files">
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M15,7H20.5L15,1.5V7M8,0H16L22,6V18A2,2 0 0,1 20,20H8C6.89,20 6,19.1 6,18V2A2,2 0 0,1 8,0M4,4V22H20V24H4A2,2 0 0,1 2,22V4H4Z"></path>
</svg>
</div>
<div class="contented-upload-type" data-upload-type="paste" title="Paste">
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M19,20H5V4H7V7H17V4H19M12,2A1,1 0 0,1 13,3A1,1 0 0,1 12,4A1,1 0 0,1 11,3A1,1 0 0,1 12,2M19,2H14.82C14.4,0.84 13.3,0 12,0C10.7,0 9.6,0.84 9.18,2H5A2,2 0 0,0 3,4V20A2,2 0 0,0 5,22H19A2,2 0 0,0 21,20V4A2,2 0 0,0 19,2Z" />
</svg>
</div>
<div class="contented-upload-type" data-upload-type="drawing" title="Drawing">
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M16.84,2.73C16.45,2.73 16.07,2.88 15.77,3.17L13.65,5.29L18.95,10.6L21.07,8.5C21.67,7.89 21.67,6.94 21.07,6.36L17.9,3.17C17.6,2.88 17.22,2.73 16.84,2.73M12.94,6L4.84,14.11L7.4,14.39L7.58,16.68L9.86,16.85L10.15,19.41L18.25,11.3M4.25,15.04L2.5,21.73L9.2,19.94L8.96,17.78L6.65,17.61L6.47,15.29" />
</svg>
</div>
<div class="contented-upload-type" data-upload-type="album" title="Album">
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M3,3H21V7H3V3M4,8H20V21H4V8M9.5,11A0.5,0.5 0 0,0 9,11.5V13H15V11.5A0.5,0.5 0 0,0 14.5,11H9.5Z" />
</svg>
</div>
</div>
<div class="contented-content-area">
<div class="contented-upload-if contented-if-drag contented-active">
<label>Drop files or Ctrl-V to upload <span class="contented-extratext"></span></label>
</div>
<div class="contented-upload-if contented-if-file">
<label>Select files to upload <span class="contented-extratext"></span></label><br>
<input class="contented-file-selector" type="file" multiple>
<button class="contented-file-upload">Upload &raquo;</button>
</div>
<div class="contented-upload-if contented-if-paste">
<textarea placeholder="Paste content here"></textarea>
<button class="contented-paste-upload">Upload &raquo;</button>
</div>
<div class="contented-upload-if contented-if-drawing">
<div class="contented-drawing-area"></div>
</div>
<div class="contented-upload-if contented-if-progress">
<label>...</label>
<div class="contented-progress-bar"><div class="contented-progress-element"></div></div>
</div>
<div class="contented-upload-if contented-if-album">
<input type="text" class="contented-album-title" placeholder="Album name"><br>
<input type="text" class="contented-album-items" placeholder="Image IDs, separated with commas">
<br><br>
<button class="contented-album-upload">Make album &raquo;</button>
</div>
</div>
</div>
`;
var initArea = function (aboutInfo, elementSelector, onUploaded, onClose) {
onUploaded = onUploaded || function () { };
onClose = onClose || function () { };
if ($(elementSelector).length != 1) {
return; // should only find one element
}
var element = $(elementSelector)[0];
// <input type="hidden" name="MAX_FILE_SIZE" value="` + ret.MaxUploadBytes + `" />
// Create a new div for ourselves on top of the existing area
var extraText = "";
if (aboutInfo.MaxUploadBytes > 0) {
extraText = " (max " + formatBytes(aboutInfo.MaxUploadBytes) + ")";
}
var $f = $("<div>").html(widgetHtml);
$f.find(".contented-extratext").text(extraText);
// Tab buttons
var hasSetupDrawingBoardYet = false;
var setType = function (type) {
$f.find(".contented-upload-type").removeClass("contented-upload-type-active");
$f.find(".contented-upload-type[data-upload-type=" + type + "]").addClass("contented-upload-type-active");
$f.find(".contented-upload-if").removeClass("contented-active");
$f.find(".contented-if-" + type).addClass("contented-active");
if (type == "drag") {
enablePasteHandler();
} else {
disablePasteHandler();
}
if (type == "drawing" && !hasSetupDrawingBoardYet) {
setupDrawingBoard();
hasSetupDrawingBoardYet = true;
}
};
$f.find(".contented-upload-type").click(function () {
setType($(this).attr('data-upload-type'));
});
// Widget positioning
var $element = $(element);
var offset = $element.offset();
$f.css({
'position': 'absolute',
'left': offset.left + "px",
'top': offset.top + "px",
'width': $element.width() + "px",
'min-width': $element.width() + "px",
'max-width': $element.width() + "px",
'height': $element.height() + "px",
'min-height': $element.height() + "px",
'max-height': $element.height() + "px"
});
// Drag and drop support
$f.find('.contented').on('dragover dragenter', function (e) {
e.preventDefault();
e.stopPropagation();
$(this).addClass('is-dragging');
});
$f.find('.contented').on('dragleave dragend', function (e) {
e.preventDefault();
e.stopPropagation();
$(this).removeClass('is-dragging');
});
$f.find('.contented').on('drop', function (e) {
e.preventDefault();
e.stopPropagation();
handleUploadFrom(e.originalEvent.dataTransfer.files);
});
$f.find('.contented-file-upload').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
handleUploadFrom($(".contented-file-selector")[0].files);
});
// Pastebin
$f.find('.contented-paste-upload').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var blob = new Blob([$(".contented-if-paste textarea").val()], {type : 'text/plain'});
handleUploadFrom([blob]);
});
// Album
$f.find(".contented-album-upload").on("click", function(e) {
e.preventDefault();
e.stopPropagation();
var title = $(".contented-album-title").val();
if (title === "") {
title = "Untitled album";
}
var childIDs = $(".contented-album-items").val().split(",");
for (var i = 0; i < childIDs.length; ++i) {
childIDs[i] = childIDs[i].trim(); // Basic validation - can't really perform full validation here
if (childIDs[i].length == 0) {
alert("Entry " + (i+1) + " is too short, expected non-zero length");
return;
}
if (! childIDs[i].match(/^[a-zA-Z0-9]+$/)) {
alert("Entry " + (i+1) + " contains unexpected character");
return;
}
}
var blob = new Blob([JSON.stringify(childIDs)], {type : 'contented/album'});
handleUploadFrom([new File([blob], title, {type: 'contented/album'})]);
});
// Ctrl+V uploads
var pasteHandler = function(e) {
e.preventDefault();
e.stopPropagation();
var items = (e.clipboardData || e.originalEvent.clipboardData).items;
var items_length = items.length;
var blobs = [];
var handled = 0;
var haveHandled = function() {
handled += 1;
if (handled == items_length) {
if (blobs.length > 0) {
handleUploadFrom( blobs );
} else {
// alert("Pasted 0 files");
}
}
};
for (var i = 0; i < items.length; ++i) {
var item = items[i];
var mimeType = item.type;
if (item.kind === 'file') {
blobs.push(item.getAsFile());
haveHandled();
} else if (item.kind === 'string') {
item.getAsString(function(s) {
blobs.push( new Blob([s], {type : mimeType}) );
haveHandled();
});
} else {
// file|string are the only supported types in
// all browsers at the time of writing
// Ignore future possibilities
haveHandled();
}
}
};
var enablePasteHandler = function() {
document.addEventListener('paste', pasteHandler);
};
var disablePasteHandler = function() {
document.removeEventListener('paste', pasteHandler);
};
// Embed in DOM, load default area
$("body").append($f);
if (!contented.supportsDrop()) {
setType('file');
} else {
setType('drag');
}
// Drawing board
var setupDrawingBoard = function() {
$("head").append(
'<link rel="stylesheet" type="text/css" href="' + contented.baseURL + 'drawingboard-0.4.6.min.css">'
);
var db_id = "contented-drawing-area-" + guid();
var $db = $("<div>").attr('id', db_id);
DrawingBoard.Control.ContentedUpload = DrawingBoard.Control.extend({
name: 'upload',
initialize: function() {
var $el = this.$el;
$el.append('<button class="contented-drawingboard-upload">Upload</button>');
$el.on('click', '.contented-drawingboard-upload', $.proxy(function(e) {
e.preventDefault();
e.stopPropagation();
$el.prop('disabled', true);
$el.text('Saving...');
$db.find("canvas")[0].toBlob(function(theBlob) {
handleUploadFrom([ theBlob ]);
});
}, this));
}
});
$db.css({
//'width': $f.find(".contented-content-area").width(),
'height': $f.find(".contented-content-area").height(),
'overflow': 'hidden'
});
$f.find(".contented-drawing-area").append($db);
var db = new DrawingBoard.Board(db_id, {
'controls': [
'Color',
'Size',
'DrawingMode',
'Navigation',
'ContentedUpload'
],
'controlsPosition': 'center',
'enlargeYourContainer': false,
'webStorage': false,
'droppable': false // don't mess with existing drop support
});
};
// Close button
var ourClose = function () {
$f.remove(); // remove from dom
disablePasteHandler();
onClose(); // upstream close
};
$f.find(".contented-close").click(function () {
ourClose();
})
// Progress bar
var setProgressCaption = function(message) {
$f.find(".contented-if-progress label").text(message);
};
var setProgressPercentage = function(frc) {
$f.find(".contented-progress-element").css('width', (frc * 100) + "%");
};
// Common upload handler
/**
*
* @param {File[]|Blob[]} files
*/
var handleUploadFrom = function(files) {
setProgressCaption("Uploading, please wait...");
setProgressPercentage(0);
setType("progress");
$f.find(".contented-upload-type-selector").hide();
$f.find(".contented").removeClass('is-dragging');
// Ajax uploader
var ajaxData = new FormData();
for (var i = 0; i < files.length; ++i) {
ajaxData.append("f", files[i]);
}
// ajax request
$.ajax({
url: contented.baseURL + "upload",
type: "POST",
data: ajaxData,
dataType: 'json', // response type
cache: false,
contentType: false,
processData: false,
xhr: function() {
var xhr = $.ajaxSettings.xhr();
xhr.upload.addEventListener(
'progress',
function(ev) {
if (ev.lengthComputable) {
setProgressCaption("Uploading (" + formatBytes(ev.loaded) + " / " + formatBytes(ev.total) + ")...");
setProgressPercentage(ev.total == 0 ? 0 : ev.loaded / ev.total);
}
},
false
);
return xhr;
},
complete: function () {
setProgressPercentage(1);
},
success: function (data) {
setProgressCaption("Upload completed successfully.");
onUploaded(data);
ourClose();
},
error: function () {
setProgressCaption("Upload failed!");
}
});
}
};
var init = function() {
var currentScriptPath = getCurrentScriptPath();
var baseURL = currentScriptPath.replace('sdk.js', '');
// Kick off background promises
var loader = new Promise(function(resolve, reject) {
if (typeof jQuery === "undefined") {
loadScript(contented.baseURL + "jquery-3.7.0.min.js").then(resolve);
} else {
resolve();
}
}).then(function() { return new Promise(function(resolve, reject) {
if (typeof DrawingBoard === "undefined") {
loadScript(contented.baseURL + "drawingboard-0.4.6.min.js").then(resolve);
} else {
resolve();
}
})}).then(function() { return new Promise(function(resolve, reject) {
$.get(contented.baseURL + "about", function (aboutInfo) {
resolve(aboutInfo);
});
})}).then(function(aboutInfo) {
// Update fields in global variable
window.contented.loaded = true;
return aboutInfo;
});
window.contented = {
"loaded": false,
"baseURL": baseURL,
"__preInit": [],
/**
* initArea shows the contented upload widget over the top of a target DOM element.
*
* @param any element Drop target (string selector / DOMElement / jQuery)
* @param Function onUploaded Called with an array of upload IDs
* @param Function onClose Called when the widget is being destroyed
*/
"init": function(elementSelector, onUploaded, onClose) {
loader.then(function(aboutInfo) {
initArea(aboutInfo, elementSelector, onUploaded, onClose);
});
},
/**
* supportsDrop returns whether drag-and-drop is supported by this browser.
*
* @return bool
*/
"supportsDrop": function() {
return ('ondrop' in window && 'FormData' in window && 'FileReader' in window);
},
"getPreviewURL": function(id) {
return baseURL + "p/" + encodeURIComponent(id);
},
"getMultiPreviewURL": function(items) {
return baseURL + "p/" + encodeURIComponent(items.join("-"));
},
"getDownloadURL": function(id) {
return baseURL + "get/" + encodeURIComponent(id);
},
"getInfoJSONURL": function(id) {
return baseURL + "info/" + encodeURIComponent(id);
},
"getThumbnailURL": function(thumbnailType, id) {
return baseURL + "thumb/" + encodeURIComponent(thumbnailType) + "/" + encodeURIComponent(id);
},
"thumbnail": {
"small_square": "s",
"medium_square": "b",
"medium": "t",
"large": "m",
"xlarge": "l",
"xxlarge": "h"
}
};
};
init();
})()