Attaches plugin (#181)

* Attaches plugin

* Ignore server dir

* Progress bar added

* Styles upd

* Fix

* refactoring

* core.ajax fix

* Upd

* Upd

* update styles

* remove tmp return

* styles updated

* file title now 'title'

* Current upload check

* update attaches and image plugin
This commit is contained in:
George Berezhnoy 2017-03-21 21:45:52 +03:00 committed by Peter Savchenko
parent aff9ea13ad
commit 510764a3cd
15 changed files with 667 additions and 41 deletions

4
.gitignore vendored
View file

@ -5,4 +5,6 @@ Thumbs.db
/*.sublime-project
/*.sublime-workspace
node_modules/*
node_modules/*
/server/
/uploads/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -54,6 +54,9 @@
<script src="plugins/raw/raw.js"></script>
<link rel="stylesheet" href="plugins/raw/raw.css">
<script src="plugins/attaches/attaches.js"></script>
<link rel="stylesheet" href="plugins/attaches/attaches.css">
<script>
codex.editor.start({
holderId : "codex-editor",
@ -206,6 +209,21 @@
destroy: rawPlugin.destroy,
enableLineBreaks: true,
allowPasteHTML: true
},
attaches: {
type: 'attaches',
displayInToolbox: true,
iconClassname: 'cdx-attaches__icon',
prepare: cdxAttaches.prepare,
render: cdxAttaches.render,
save: cdxAttaches.save,
validate: cdxAttaches.validate,
destroy: cdxAttaches.destroy,
appendCallback: cdxAttaches.appendCallback,
config: {
fetchUrl: '/test',
maxSize: 50000,
}
}
},
data : {

View file

@ -154,17 +154,16 @@ module.exports = (function (core) {
}
var XMLHTTP = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),
successFunction = function () {},
var XMLHTTP = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'),
encodedString,
isFormData,
prop;
settings.async = true;
settings.type = settings.type || 'GET';
settings.data = settings.data || '';
settings['content-type'] = settings['content-type'] || 'application/json; charset=utf-8';
successFunction = settings.success || successFunction ;
if (settings.type == 'GET' && settings.data) {
@ -189,7 +188,11 @@ module.exports = (function (core) {
if (settings.beforeSend && typeof settings.beforeSend == 'function') {
settings.beforeSend.call();
if (settings.beforeSend() === false) {
return;
}
}
@ -216,11 +219,33 @@ module.exports = (function (core) {
XMLHTTP.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
if (typeof settings.progress == 'function') {
XMLHTTP.upload.onprogress = settings.progress;
}
XMLHTTP.onreadystatechange = function () {
if (XMLHTTP.readyState == 4 && XMLHTTP.status == 200) {
if (XMLHTTP.readyState == 4) {
successFunction(XMLHTTP.responseText);
if (XMLHTTP.status == 200) {
if (typeof settings.success == 'function') {
settings.success(XMLHTTP.responseText);
}
} else {
if (typeof settings.error == 'function') {
settings.error(XMLHTTP.responseText);
}
}
}
@ -238,6 +263,7 @@ module.exports = (function (core) {
}
return XMLHTTP;
};

View file

@ -10,6 +10,13 @@ module.exports = (function (transport) {
let editor = codex.editor;
/**
* @private {Object} current XmlHttpRequest instance
*/
var currentRequest = null;
/**
* @type {null} | {DOMElement} input - keeps input element in memory
*/
@ -54,11 +61,7 @@ module.exports = (function (transport) {
files = input.files,
formData = new FormData();
if (editor.transport.arguments.multiple === false) {
formData.append('files', files[0], files[0].name);
} else {
if (editor.transport.arguments.multiple === true) {
for ( i = 0; i < files.length; i++) {
@ -66,15 +69,20 @@ module.exports = (function (transport) {
}
} else {
formData.append('files', files[0], files[0].name);
}
editor.core.ajax({
currentRequest = editor.core.ajax({
type : 'POST',
data : formData,
url : editor.transport.arguments.url,
beforeSend : editor.transport.arguments.beforeSend,
success : editor.transport.arguments.success,
error : editor.transport.arguments.error
error : editor.transport.arguments.error,
progress : editor.transport.arguments.progress
});
/** Clear input */
@ -91,6 +99,7 @@ module.exports = (function (transport) {
* @param {Function} args.beforeSend - function calls before sending ajax
* @param {Function} args.success - success callback
* @param {Function} args.error - on error handler
* @param {Function} args.progress - xhr onprogress handler
* @param {Boolean} args.multiple - allow select several files
* @param {String} args.accept - adds accept attribute
*/
@ -114,6 +123,14 @@ module.exports = (function (transport) {
};
transport.abort = function () {
currentRequest.abort();
currentRequest = null;
};
return transport;
})({});

View file

@ -1,6 +1,6 @@
{
"name": "codex.editor",
"version": "1.6.1",
"version": "1.6.2",
"description": "Codex Editor. Native JS, based on API and Open Source",
"main": "index.js",
"scripts": {

View file

@ -0,0 +1,128 @@
.cdx-attaches__default-wrapper {
margin: 15px 0;
padding: 15px;
background: #fff;
border: 1px solid #ebecec;
box-shadow: 0 1px 2px 0 rgba(34, 36, 44, 0.03);
border-radius: 3px;
text-align: center;
}
.cdx-attaches__default-button {
color: #8990aa;
cursor: pointer;
}
.cdx-attaches__default-button:hover {
color: #393f52;
}
.cdx-attaches__wrapper {
display: -ms-flexbox;
display: flex;
-ms-flex-flow: row nowrap;
flex-flow: row nowrap;
-ms-flex-pack: start;
justify-content: flex-start;
-ms-flex-align: center;
align-items: center;
margin: 10px 0;
padding: 15px 20px;
background: #fff;
border: 1px solid #ebecec;
box-shadow: 0 1px 2px 0 rgba(34, 36, 44, 0.03);
border-radius: 3px;
font-size: 15px;
}
.cdx-attaches__file-name {
-ms-flex-positive: 8;
flex-grow: 8;
width: 100%;
outline: none;
border: 0;
font-size: inherit;
}
.cdx-attaches__file-name--collapsed {
width: 30%;
overflow: hidden;
text-overflow: ellipsis;
}
.cdx-attaches__extension,
.cdx-attaches__size {
color: #8f9298;
}
.cdx-attaches__extension::after {
content: ',';
}
.cdx-attaches__size::after {
content: 'KB';
margin-left: 0.2em;
}
.cdx-attaches__icon {
display: inline-block;
width: 16px;
height: 32px;
background: url(file-icon-black.svg) no-repeat center center;
background-size: contain;
}
li:hover .cdx-attaches__icon,
.selected .cdx-attaches__icon {
background: url(file-icon-white.svg) no-repeat center center;
background-size: contain;
}
.cdx-attaches__icon--inline {
height: 16px;
vertical-align: text-bottom;
}
.cdx-attaches__loader {
background-color: transparent;
background-image: repeating-linear-gradient(-45deg, transparent, transparent 4px, #f5f9ff 4px, #eaedef 8px) !important;
background-size: 56px 56px;
animation: loading-bar 5s infinite linear;
}
@keyframes loading-bar {
100% { background-position: -56% 0; }
}
.cdx-attaches__progress-bar {
width: 100%;
height: 3px;
margin: 0 15px;
background: #e1e3eb;
border: 0;
border-radius: 5px;
}
.cdx-attaches__progress-bar::-webkit-progress-bar {
background: #e0e1e3;
border-radius: 5px;
}
.cdx-attaches__progress-bar::-webkit-progress-value {
background: #414957;
border-radius: 5px;
transition: all 100ms ease-in;
}
progress::-moz-progress-bar {
background: #414957;
border-radius: 5px;
}
.cdx-attaches__cross-button {
width: 18px;
height: 18px;
background: url(cross.svg) no-repeat center center;
background-size: contain;
cursor: pointer;
}

View file

@ -0,0 +1,428 @@
/**
* Attache-file Plugin for CodeX Editor
*
* @param {String} config.fetchUrl - Route for file uploding
* @param {Nubmer} config.maxSize - Maximum allowed file size in KB
* @param {String} config.accept - Accepted MIME-types. By default, accepts all
*
* Backend should return response with
* 'url' - Full path to the uploaded file
* 'title' - File title,
* 'name' - File name without extension,
* 'extension' - File extension,
* 'size' - File size
*
* @author @gohabereg
* @version 1.0.0
*/
var cdxAttaches = function () {
/**
* Private methods and props
*/
var KBYTE = 1024;
/**
* Default config
* Can be redefined with prepare method
*
* @var sting config.fetchUrl -- url to your fetch script
* @var int config.maxSize -- max size of file in kilobytes
* @var accept config.accept -- valid MIME-types. By default, accepts all
*
*/
var config = {
fetchUrl: '',
maxSize: 2,
accept: ''
};
var elementsClasses = {
defaultFormWrapper : 'cdx-attaches__default-wrapper',
defaultFormButton : 'cdx-attaches__default-button',
progressBar : 'cdx-attaches__progress-bar',
wrapper : 'cdx-attaches__wrapper',
loader : 'cdx-attaches__loader',
crossButton : 'cdx-attaches__cross-button',
file: {
title : 'cdx-attaches__file-name',
collapsedName : 'cdx-attaches__file-name--collapsed',
extension : 'cdx-attaches__extension',
size : 'cdx-attaches__size'
}
};
var ui = {
defaultForm: function () {
var wrapper = codex.editor.draw.node('div', elementsClasses.defaultFormWrapper),
button = codex.editor.draw.node('div', elementsClasses.defaultFormButton);
button.addEventListener('click', upload.fire);
button.innerHTML = '<i class="cdx-attaches__icon cdx-attaches__icon--inline"></i> Загрузить файл';
wrapper.appendChild(button);
return wrapper;
},
uploadedFile: function (data) {
var wrapper = codex.editor.draw.node('div', elementsClasses.wrapper),
name = codex.editor.draw.node('input', elementsClasses.file.title),
extension = codex.editor.draw.node('span', elementsClasses.file.extension),
size = codex.editor.draw.node('span', elementsClasses.file.size);
wrapper.dataset.url = data.url;
wrapper.dataset.name = data.name;
name.value = data.title || '';
extension.textContent = data.extension.toUpperCase();
size.textContent = data.size;
wrapper.appendChild(name);
wrapper.appendChild(extension);
wrapper.appendChild(size);
return wrapper;
},
progressBar: {
bar: null,
draw: function () {
var wrapper = codex.editor.draw.node('div', elementsClasses.wrapper),
progress = codex.editor.draw.node('progress', elementsClasses.progressBar),
name = codex.editor.draw.node('span', elementsClasses.file.title),
crossButton = codex.editor.draw.node('span', elementsClasses.crossButton);
progress.max = 100;
progress.value = 0;
name.textContent = codex.editor.transport.input.files[0].name;
name.classList.add(elementsClasses.file.collapsedName);
crossButton.addEventListener('click', upload.abort);
ui.progressBar.bar = progress;
wrapper.appendChild(name);
wrapper.appendChild(progress);
wrapper.appendChild(crossButton);
return wrapper;
},
change: function (value) {
console.assert( !isNaN(value), 'CodeX Editor Attaches: passed value is not a Number');
ui.progressBar.bar.value = value;
}
}
};
/**
* Notify about upload errors via codex.editor.notifications
*
* @param Object error can have `message` property with error message
*/
var notifyError = function (error) {
error = error || {};
codex.editor.notifications.notification({
type: 'error',
message: 'Ошибка во время загрузки файла' + ( error.message ? ': ' + error.message : '' )
});
};
/**
* Contains validation methods
*
* TODO: MIME-type validation
*
*/
var validation = {
size: function () {
var file = codex.editor.transport.input.files[0];
return Math.ceil(file.size / KBYTE) <= config.maxSize;
},
};
var upload = {
current: null,
aborted: false,
/**
* Fired codex.editor.transport selectAndUpload methods
*/
fire: function () {
codex.editor.transport.selectAndUpload({
url: config.fetchUrl,
success: upload.success,
beforeSend: upload.start,
progress: upload.progress,
error: upload.error,
accept: config.accept
});
},
/**
* Will be called before upload
* Draws load animation and progress bar
*/
start: function () {
if (!validation.size()) {
notifyError({message: 'Файл слишком большой'});
return false;
}
if (upload.current) {
notifyError({message: 'Дождитесь окончания предыдущей загрузки'});
return;
}
var progress = ui.progressBar.draw();
upload.current = progress;
codex.editor.content.switchBlock(codex.editor.content.currentNode, progress);
},
/**
* Handler for XmlHttpRequest.upload.onprogress event
* Changes progress bar status
*
* @param event
*/
progress: function (event) {
/** Prevents isNaN value assignment */
if (!event.total) {
return;
}
var value = parseInt(event.loaded / event.total * 100);
ui.progressBar.change(value);
},
/**
* Will be called after success upload
* Try to decode JSON response and draws ui or fires error handler
*
* @param response
*/
success: function (response) {
var data,
uploadedFile;
try {
response = JSON.parse(response);
if (response.success) {
data = response.data;
data.size = Math.ceil(data.size / KBYTE) || 1;
uploadedFile = ui.uploadedFile(data);
codex.editor.content.switchBlock(upload.current, uploadedFile);
uploadedFile.querySelector('input').focus();
} else {
upload.error(response);
}
} catch (e) {
upload.error();
}
upload.current = null;
},
/**
* Upload errors handler
*
* @param error
*/
error: function (error) {
var defaultFrom = ui.defaultForm();
codex.editor.content.switchBlock(upload.current, defaultFrom);
if (!upload.aborted) {
notifyError(error);
}
upload.aborted = false;
upload.current = null;
},
abort: function () {
codex.editor.transport.abort();
upload.aborted = true;
upload.current = null;
}
};
/*
* Public methods
* @param {String} _config.fetchUrl Required
*/
var prepare = function (_config) {
return new Promise(function(resolve, reject){
if ( !_config.fetchUrl ){
reject(Error('fetchUrl is missed'));
return;
}
config.fetchUrl = _config.fetchUrl;
config.accept = _config.accept || config.accept;
if ( !isNaN(_config.maxSize)){
config.maxSize = _config.maxSize;
}
resolve();
});
};
var render = function (data) {
if (!data) {
return ui.defaultForm();
}
return ui.uploadedFile(data);
};
var save = function (block) {
var data = {
url: block.dataset.url,
name: block.dataset.name,
title: block.querySelector('.' + elementsClasses.file.title).value,
extension: block.querySelector('.' + elementsClasses.file.extension).textContent,
size: block.querySelector('.' + elementsClasses.file.size).textContent,
};
return data;
};
var validate = function (data) {
if (!data.url || !data.url.trim()) {
return false;
}
if (!data.title || !data.title.trim()) {
return false;
}
if (!data.extension || !data.extension.trim()) {
return false;
}
if (!data.size || !data.size.trim()) {
return false;
}
return true;
};
var destroy = function () {
cdxAttaches = null;
};
var appendCallback = function () {
upload.fire();
};
return {
prepare: prepare,
render: render,
save: save,
validate: validate,
destroy: destroy,
appendCallback: appendCallback
};
}();

View file

@ -0,0 +1,7 @@
<svg width="13px" height="13px" viewBox="0 0 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="icon/cross" transform="translate(-5.000000, -5.000000)" fill="#575D67">
<path d="M10.4350288,10.7279221 L4.4359005,10.7279221 C3.8796597,10.7279221 3.43502884,11.1756373 3.43502884,11.7279221 C3.43502884,12.2840572 3.88313435,12.7279221 4.4359005,12.7279221 L10.4350288,12.7279221 L10.4350288,18.7270504 C10.4350288,19.2832912 10.8827441,19.7279221 11.4350288,19.7279221 C11.991164,19.7279221 12.4350288,19.2798166 12.4350288,18.7270504 L12.4350288,12.7279221 L18.4341572,12.7279221 C18.990398,12.7279221 19.4350288,12.2802068 19.4350288,11.7279221 C19.4350288,11.1717869 18.9869233,10.7279221 18.4341572,10.7279221 L12.4350288,10.7279221 L12.4350288,4.72879372 C12.4350288,4.17255292 11.9873136,3.72792206 11.4350288,3.72792206 C10.8788937,3.72792206 10.4350288,4.17602757 10.4350288,4.72879372 L10.4350288,10.7279221 Z" transform="translate(11.435029, 11.727922) rotate(45.000000) translate(-11.435029, -11.727922) "></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg enable-background="new 0 0 48 48" height="48px" id="Layer_1" version="1.1" viewBox="0 0 48 48" width="48px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path clip-rule="evenodd" d="M37,47H11c-2.209,0-4-1.791-4-4V5c0-2.209,1.791-4,4-4h18.973 c0.002,0,0.005,0,0.007,0h0.02H30c0.32,0,0.593,0.161,0.776,0.395l9.829,9.829C40.84,11.407,41,11.68,41,12l0,0v0.021 c0,0.002,0,0.003,0,0.005V43C41,45.209,39.209,47,37,47z M31,4.381V11h6.619L31,4.381z M39,13h-9c-0.553,0-1-0.448-1-1V3H11 C9.896,3,9,3.896,9,5v38c0,1.104,0.896,2,2,2h26c1.104,0,2-0.896,2-2V13z M33,39H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18 c0.553,0,1,0.448,1,1C34,38.553,33.553,39,33,39z M33,31H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18c0.553,0,1,0.448,1,1 C34,30.553,33.553,31,33,31z M33,23H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18c0.553,0,1,0.448,1,1C34,22.553,33.553,23,33,23 z" fill-rule="evenodd" fill="#000"/></svg>

After

Width:  |  Height:  |  Size: 958 B

View file

@ -0,0 +1 @@
<svg enable-background="new 0 0 48 48" height="48px" id="Layer_1" version="1.1" viewBox="0 0 48 48" width="48px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path clip-rule="evenodd" d="M37,47H11c-2.209,0-4-1.791-4-4V5c0-2.209,1.791-4,4-4h18.973 c0.002,0,0.005,0,0.007,0h0.02H30c0.32,0,0.593,0.161,0.776,0.395l9.829,9.829C40.84,11.407,41,11.68,41,12l0,0v0.021 c0,0.002,0,0.003,0,0.005V43C41,45.209,39.209,47,37,47z M31,4.381V11h6.619L31,4.381z M39,13h-9c-0.553,0-1-0.448-1-1V3H11 C9.896,3,9,3.896,9,5v38c0,1.104,0.896,2,2,2h26c1.104,0,2-0.896,2-2V13z M33,39H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18 c0.553,0,1,0.448,1,1C34,38.553,33.553,39,33,39z M33,31H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18c0.553,0,1,0.448,1,1 C34,30.553,33.553,31,33,31z M33,23H15c-0.553,0-1-0.447-1-1c0-0.552,0.447-1,1-1h18c0.553,0,1,0.448,1,1C34,22.553,33.553,23,33,23 z" fill-rule="evenodd" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 958 B

View file

@ -128,7 +128,7 @@ var image = (function(image_plugin) {
*/
makeImage : function(data, imageTypeClasses, stretched, bordered) {
var file = data.file,
var file = data,
text = data.caption,
type = data.type,
image = ui_.image(file, imageTypeClasses),
@ -348,13 +348,11 @@ var image = (function(image_plugin) {
background : false,
border : false,
isstretch : false,
file : {
url : e.target.result,
bigUrl : null,
width : null,
height : null,
additionalData : null
},
url : e.target.result,
bigUrl : null,
width : null,
height : null,
additionalData : null,
caption : '',
cover : null
};
@ -392,11 +390,11 @@ var image = (function(image_plugin) {
*/
var newImage = image.holder.getElementsByTagName('IMG')[0];
newImage.src = parsed.data.file.url;
newImage.dataset.bigUrl = parsed.data.file.bigUrl;
newImage.dataset.width = parsed.data.file.width;
newImage.dataset.height = parsed.data.file.height;
newImage.dataset.additionalData = parsed.data.file.additionalData;
newImage.src = parsed.data.url;
newImage.dataset.bigUrl = parsed.data.bigUrl;
newImage.dataset.width = parsed.data.width;
newImage.dataset.height = parsed.data.height;
newImage.dataset.additionalData = parsed.data.additionalData;
},
@ -433,11 +431,11 @@ var image = (function(image_plugin) {
var newImage = image.getElementsByTagName('IMG')[0];
newImage.dataset.stretched = false;
newImage.dataset.src = imageInfo.file.url;
newImage.dataset.bigUrl = imageInfo.file.bigUrl;
newImage.dataset.width = imageInfo.file.width;
newImage.dataset.height = imageInfo.file.height;
newImage.dataset.additionalData = imageInfo.file.additionalData;
newImage.dataset.src = imageInfo.url;
newImage.dataset.bigUrl = imageInfo.bigUrl;
newImage.dataset.width = imageInfo.width;
newImage.dataset.height = imageInfo.height;
newImage.dataset.additionalData = imageInfo.additionalData;
image.classList.remove(elementClasses_.imagePreview);
@ -603,13 +601,13 @@ var image = (function(image_plugin) {
background : false,
border : content.dataset.bordered === 'true' ? true : false,
isstretch : content.dataset.stretched === 'true' ? true : false,
file : {
// file : {
url : image.dataset.src || image.src,
bigUrl : image.dataset.bigUrl,
width : image.width,
height : image.height,
additionalData :null
},
additionalData :null,
// },
caption : caption.innerHTML || '',
cover : null
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long