removing the composer from core, out to its own plugin: nodebb-plugin-composer-default, closes #3288
parent
491d376fb4
commit
48af82659e
@ -1,595 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/* globals define, socket, app, config, ajaxify, utils, templates, bootbox */
|
|
||||||
|
|
||||||
define('composer', [
|
|
||||||
'taskbar',
|
|
||||||
'translator',
|
|
||||||
'composer/controls',
|
|
||||||
'composer/uploads',
|
|
||||||
'composer/formatting',
|
|
||||||
'composer/drafts',
|
|
||||||
'composer/tags',
|
|
||||||
'composer/categoryList',
|
|
||||||
'composer/preview',
|
|
||||||
'composer/resize'
|
|
||||||
], function(taskbar, translator, controls, uploads, formatting, drafts, tags, categoryList, preview, resize) {
|
|
||||||
var composer = {
|
|
||||||
active: undefined,
|
|
||||||
posts: {},
|
|
||||||
bsEnvironment: undefined,
|
|
||||||
formatting: []
|
|
||||||
};
|
|
||||||
|
|
||||||
$(window).off('resize', onWindowResize).on('resize', onWindowResize);
|
|
||||||
|
|
||||||
$(window).on('action:composer.topics.post', function(ev, data) {
|
|
||||||
localStorage.removeItem('category:' + data.data.cid + ':bookmark');
|
|
||||||
localStorage.removeItem('category:' + data.data.cid + ':bookmark:clicked');
|
|
||||||
});
|
|
||||||
|
|
||||||
$(window).on('popstate', function(ev, data) {
|
|
||||||
var env = utils.findBootstrapEnvironment();
|
|
||||||
|
|
||||||
if (composer.active && (env === 'xs' || env ==='sm')) {
|
|
||||||
if (!composer.posts[composer.active].modified) {
|
|
||||||
discard(composer.active);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
translator.translate('[[modules:composer.discard]]', function(translated) {
|
|
||||||
bootbox.confirm(translated, function(confirm) {
|
|
||||||
if (confirm) {
|
|
||||||
discard(composer.active);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function removeComposerHistory() {
|
|
||||||
var env = utils.findBootstrapEnvironment();
|
|
||||||
if (env === 'xs' || env ==='sm') {
|
|
||||||
history.back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query server for formatting options
|
|
||||||
socket.emit('modules.composer.getFormattingOptions', function(err, options) {
|
|
||||||
composer.formatting = options;
|
|
||||||
});
|
|
||||||
|
|
||||||
function onWindowResize() {
|
|
||||||
if (composer.active !== undefined) {
|
|
||||||
resize.reposition($('#cmp-uuid-' + composer.active));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function alreadyOpen(post) {
|
|
||||||
// If a composer for the same cid/tid/pid is already open, return the uuid, else return bool false
|
|
||||||
var type, id;
|
|
||||||
|
|
||||||
if (post.hasOwnProperty('cid')) {
|
|
||||||
type = 'cid';
|
|
||||||
} else if (post.hasOwnProperty('tid')) {
|
|
||||||
type = 'tid';
|
|
||||||
} else if (post.hasOwnProperty('pid')) {
|
|
||||||
type = 'pid';
|
|
||||||
}
|
|
||||||
|
|
||||||
id = post[type];
|
|
||||||
|
|
||||||
// Find a match
|
|
||||||
for(var uuid in composer.posts) {
|
|
||||||
if (composer.posts[uuid].hasOwnProperty(type) && id === composer.posts[uuid][type]) {
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No matches...
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function push(post) {
|
|
||||||
var uuid = utils.generateUUID(),
|
|
||||||
existingUUID = alreadyOpen(post);
|
|
||||||
|
|
||||||
if (existingUUID) {
|
|
||||||
taskbar.updateActive(existingUUID);
|
|
||||||
return composer.load(existingUUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
translator.translate('[[topic:composer.new_topic]]', function(newTopicStr) {
|
|
||||||
taskbar.push('composer', uuid, {
|
|
||||||
title: post.title ? post.title : newTopicStr
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Construct a save_id
|
|
||||||
if (0 !== parseInt(app.user.uid, 10)) {
|
|
||||||
if (post.hasOwnProperty('cid')) {
|
|
||||||
post.save_id = ['composer', app.user.uid, 'cid', post.cid].join(':');
|
|
||||||
} else if (post.hasOwnProperty('tid')) {
|
|
||||||
post.save_id = ['composer', app.user.uid, 'tid', post.tid].join(':');
|
|
||||||
} else if (post.hasOwnProperty('pid')) {
|
|
||||||
post.save_id = ['composer', app.user.uid, 'pid', post.pid].join(':');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
composer.posts[uuid] = post;
|
|
||||||
composer.load(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
function composerAlert(post_uuid, message) {
|
|
||||||
$('#cmp-uuid-' + post_uuid).find('.composer-submit').removeAttr('disabled');
|
|
||||||
app.alert({
|
|
||||||
type: 'danger',
|
|
||||||
timeout: 3000,
|
|
||||||
title: '',
|
|
||||||
message: message,
|
|
||||||
alert_id: 'post_error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
composer.findByTid = function(tid) {
|
|
||||||
// Iterates through the initialised composers and returns the uuid of the matching composer
|
|
||||||
for(var uuid in composer.posts) {
|
|
||||||
if (composer.posts.hasOwnProperty(uuid) && composer.posts[uuid].hasOwnProperty('tid') && parseInt(composer.posts[uuid].tid, 10) === parseInt(tid, 10)) {
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
composer.addButton = function(iconClass, onClick) {
|
|
||||||
formatting.addButton(iconClass, onClick);
|
|
||||||
};
|
|
||||||
|
|
||||||
composer.newTopic = function(cid) {
|
|
||||||
socket.emit('categories.isModerator', cid, function(err, isMod) {
|
|
||||||
if (err) {
|
|
||||||
return app.alertError(err.message);
|
|
||||||
}
|
|
||||||
push({
|
|
||||||
cid: cid,
|
|
||||||
title: '',
|
|
||||||
body: '',
|
|
||||||
modified: false,
|
|
||||||
isMain: true,
|
|
||||||
isMod: isMod
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
composer.addQuote = function(tid, topicSlug, postIndex, pid, title, username, text, uuid) {
|
|
||||||
uuid = uuid || composer.active;
|
|
||||||
|
|
||||||
if (uuid === undefined) {
|
|
||||||
composer.newReply(tid, pid, title, '[[modules:composer.user_said, ' + username + ']]\n' + text);
|
|
||||||
return;
|
|
||||||
} else if (uuid !== composer.active) {
|
|
||||||
// If the composer is not currently active, activate it
|
|
||||||
composer.load(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
var postContainer = $('#cmp-uuid-' + uuid);
|
|
||||||
var bodyEl = postContainer.find('textarea');
|
|
||||||
var prevText = bodyEl.val();
|
|
||||||
if (parseInt(tid, 10) !== parseInt(composer.posts[uuid].tid, 10)) {
|
|
||||||
var link = '[' + title + '](/topic/' + topicSlug + '/' + (parseInt(postIndex, 10) + 1) + ')';
|
|
||||||
translator.translate('[[modules:composer.user_said_in, ' + username + ', ' + link + ']]\n', config.defaultLang, onTranslated);
|
|
||||||
} else {
|
|
||||||
translator.translate('[[modules:composer.user_said, ' + username + ']]\n', config.defaultLang, onTranslated);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onTranslated(translated) {
|
|
||||||
composer.posts[uuid].body = (prevText.length ? prevText + '\n\n' : '') + translated + text;
|
|
||||||
bodyEl.val(composer.posts[uuid].body);
|
|
||||||
focusElements(postContainer);
|
|
||||||
preview.render(postContainer);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
composer.newReply = function(tid, pid, title, text) {
|
|
||||||
socket.emit('topics.isModerator', tid, function(err, isMod) {
|
|
||||||
if (err) {
|
|
||||||
return app.alertError(err.message);
|
|
||||||
}
|
|
||||||
translator.translate(text, config.defaultLang, function(translated) {
|
|
||||||
push({
|
|
||||||
tid: tid,
|
|
||||||
toPid: pid,
|
|
||||||
title: $('<div/>').text(title).html(),
|
|
||||||
body: translated,
|
|
||||||
modified: false,
|
|
||||||
isMain: false,
|
|
||||||
isMod: isMod
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
composer.editPost = function(pid) {
|
|
||||||
socket.emit('modules.composer.push', pid, function(err, threadData) {
|
|
||||||
if(err) {
|
|
||||||
return app.alertError(err.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
push({
|
|
||||||
pid: pid,
|
|
||||||
uid: threadData.uid,
|
|
||||||
handle: threadData.handle,
|
|
||||||
title: threadData.title,
|
|
||||||
body: threadData.body,
|
|
||||||
modified: false,
|
|
||||||
isMain: threadData.isMain,
|
|
||||||
topic_thumb: threadData.topic_thumb,
|
|
||||||
tags: threadData.tags
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
composer.load = function(post_uuid) {
|
|
||||||
var postContainer = $('#cmp-uuid-' + post_uuid);
|
|
||||||
if (postContainer.length) {
|
|
||||||
activate(post_uuid);
|
|
||||||
resize.reposition(postContainer);
|
|
||||||
focusElements(postContainer);
|
|
||||||
} else {
|
|
||||||
createNewComposer(post_uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
startNotifyTyping(composer.posts[post_uuid]);
|
|
||||||
};
|
|
||||||
|
|
||||||
function startNotifyTyping(postData) {
|
|
||||||
function emit() {
|
|
||||||
socket.emit('modules.composer.notifyTyping', {
|
|
||||||
tid: postData.tid,
|
|
||||||
uid: app.user.uid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parseInt(postData.tid, 10)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stopNotifyInterval(postData);
|
|
||||||
|
|
||||||
emit();
|
|
||||||
postData.notifyTypingIntervalId = setInterval(emit, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopNotifyTyping(postData) {
|
|
||||||
if (!parseInt(postData.tid, 10)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
socket.emit('modules.composer.stopNotifyTyping', {
|
|
||||||
tid: postData.tid,
|
|
||||||
uid: app.user.uid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopNotifyInterval(postData) {
|
|
||||||
if (postData.notifyTypingIntervalId) {
|
|
||||||
clearInterval(postData.notifyTypingIntervalId);
|
|
||||||
postData.notifyTypingIntervalId = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNewComposer(post_uuid) {
|
|
||||||
var postData = composer.posts[post_uuid];
|
|
||||||
|
|
||||||
var allowTopicsThumbnail = config.allowTopicsThumbnail && postData.isMain && (config.hasImageUploadPlugin || config.allowFileUploads),
|
|
||||||
isTopic = postData ? !!postData.cid : false,
|
|
||||||
isMain = postData ? !!postData.isMain : false,
|
|
||||||
isEditing = postData ? !!postData.pid : false,
|
|
||||||
isGuestPost = postData ? parseInt(postData.uid, 10) === 0 : false;
|
|
||||||
|
|
||||||
composer.bsEnvironment = utils.findBootstrapEnvironment();
|
|
||||||
|
|
||||||
// see
|
|
||||||
// https://github.com/NodeBB/NodeBB/issues/2994 and
|
|
||||||
// https://github.com/NodeBB/NodeBB/issues/1951
|
|
||||||
// remove when 1951 is resolved
|
|
||||||
|
|
||||||
var title = postData.title.replace(/%/g, '%').replace(/,/g, ',');
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
title: title,
|
|
||||||
mobile: composer.bsEnvironment === 'xs' || composer.bsEnvironment === 'sm',
|
|
||||||
allowTopicsThumbnail: allowTopicsThumbnail,
|
|
||||||
isTopicOrMain: isTopic || isMain,
|
|
||||||
minimumTagLength: config.minimumTagLength,
|
|
||||||
maximumTagLength: config.maximumTagLength,
|
|
||||||
isTopic: isTopic,
|
|
||||||
isEditing: isEditing,
|
|
||||||
showHandleInput: config.allowGuestHandles && (app.user.uid === 0 || (isEditing && isGuestPost && app.user.isAdmin)),
|
|
||||||
handle: postData ? postData.handle || '' : undefined,
|
|
||||||
formatting: composer.formatting,
|
|
||||||
isAdminOrMod: app.user.isAdmin || postData.isMod
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data.mobile) {
|
|
||||||
var qs = '?p=' + window.location.pathname;
|
|
||||||
ajaxify.go('compose', function() {
|
|
||||||
renderComposer();
|
|
||||||
}, false, qs);
|
|
||||||
} else {
|
|
||||||
renderComposer();
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderComposer() {
|
|
||||||
parseAndTranslate('composer', data, function(composerTemplate) {
|
|
||||||
if ($('#cmp-uuid-' + post_uuid).length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
composerTemplate = $(composerTemplate);
|
|
||||||
|
|
||||||
composerTemplate.attr('id', 'cmp-uuid-' + post_uuid);
|
|
||||||
|
|
||||||
$(document.body).append(composerTemplate);
|
|
||||||
|
|
||||||
var postContainer = $(composerTemplate[0]),
|
|
||||||
bodyEl = postContainer.find('textarea'),
|
|
||||||
draft = drafts.getDraft(postData.save_id),
|
|
||||||
submitBtn = postContainer.find('.composer-submit');
|
|
||||||
|
|
||||||
preview.handleToggler(postContainer);
|
|
||||||
tags.init(postContainer, composer.posts[post_uuid]);
|
|
||||||
categoryList.init(postContainer, composer.posts[post_uuid]);
|
|
||||||
|
|
||||||
activate(post_uuid);
|
|
||||||
resize.reposition(postContainer);
|
|
||||||
|
|
||||||
if (config.allowFileUploads || config.hasImageUploadPlugin) {
|
|
||||||
uploads.initialize(post_uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
formatting.addHandler(postContainer);
|
|
||||||
|
|
||||||
if (allowTopicsThumbnail) {
|
|
||||||
uploads.toggleThumbEls(postContainer, composer.posts[post_uuid].topic_thumb || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
postContainer.on('change', 'input, textarea', function() {
|
|
||||||
composer.posts[post_uuid].modified = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
submitBtn.on('click', function() {
|
|
||||||
var action = $(this).attr('data-action');
|
|
||||||
|
|
||||||
switch(action) {
|
|
||||||
case 'post-lock':
|
|
||||||
$(this).attr('disabled', true);
|
|
||||||
post(post_uuid, {lock: true});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'post': // intentional fall-through
|
|
||||||
default:
|
|
||||||
$(this).attr('disabled', true);
|
|
||||||
post(post_uuid);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
postContainer.on('click', 'a[data-switch-action]', function() {
|
|
||||||
var action = $(this).attr('data-switch-action'),
|
|
||||||
label = $(this).html();
|
|
||||||
|
|
||||||
submitBtn.attr('data-action', action).html(label);
|
|
||||||
});
|
|
||||||
|
|
||||||
postContainer.find('.composer-discard').on('click', function() {
|
|
||||||
if (!composer.posts[post_uuid].modified) {
|
|
||||||
removeComposerHistory();
|
|
||||||
discard(post_uuid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var btn = $(this).prop('disabled', true);
|
|
||||||
translator.translate('[[modules:composer.discard]]', function(translated) {
|
|
||||||
bootbox.confirm(translated, function(confirm) {
|
|
||||||
if (confirm) {
|
|
||||||
removeComposerHistory();
|
|
||||||
discard(post_uuid);
|
|
||||||
}
|
|
||||||
btn.prop('disabled', false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
postContainer.on('click', function() {
|
|
||||||
if (!taskbar.isActive(post_uuid)) {
|
|
||||||
taskbar.updateActive(post_uuid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bodyEl.on('input propertychange', function() {
|
|
||||||
preview.render(postContainer);
|
|
||||||
});
|
|
||||||
|
|
||||||
bodyEl.on('scroll', function() {
|
|
||||||
preview.matchScroll(postContainer);
|
|
||||||
});
|
|
||||||
|
|
||||||
bodyEl.val(draft ? draft : postData.body);
|
|
||||||
|
|
||||||
preview.render(postContainer, function() {
|
|
||||||
preview.matchScroll(postContainer);
|
|
||||||
});
|
|
||||||
|
|
||||||
drafts.init(postContainer, postData);
|
|
||||||
|
|
||||||
resize.handleResize(postContainer);
|
|
||||||
|
|
||||||
handleHelp(postContainer);
|
|
||||||
|
|
||||||
$(window).trigger('action:composer.loaded', {
|
|
||||||
post_uuid: post_uuid,
|
|
||||||
composerData: composer.posts[post_uuid]
|
|
||||||
});
|
|
||||||
|
|
||||||
formatting.addComposerButtons();
|
|
||||||
focusElements(postContainer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseAndTranslate(template, data, callback) {
|
|
||||||
templates.parse(template, data, function(composerTemplate) {
|
|
||||||
translator.translate(composerTemplate, callback);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleHelp(postContainer) {
|
|
||||||
var helpBtn = postContainer.find('.help');
|
|
||||||
socket.emit('modules.composer.renderHelp', function(err, html) {
|
|
||||||
if (!err && html && html.length > 0) {
|
|
||||||
helpBtn.removeClass('hidden');
|
|
||||||
helpBtn.on('click', function() {
|
|
||||||
bootbox.alert(html);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function activate(post_uuid) {
|
|
||||||
if(composer.active && composer.active !== post_uuid) {
|
|
||||||
composer.minimize(composer.active);
|
|
||||||
}
|
|
||||||
|
|
||||||
composer.active = post_uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusElements(postContainer) {
|
|
||||||
var title = postContainer.find('input.title');
|
|
||||||
if (title.length) {
|
|
||||||
title.focus();
|
|
||||||
} else {
|
|
||||||
postContainer.find('textarea').focus().putCursorAtEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function post(post_uuid, options) {
|
|
||||||
var postData = composer.posts[post_uuid],
|
|
||||||
postContainer = $('#cmp-uuid-' + post_uuid),
|
|
||||||
handleEl = postContainer.find('.handle'),
|
|
||||||
titleEl = postContainer.find('.title'),
|
|
||||||
bodyEl = postContainer.find('textarea'),
|
|
||||||
thumbEl = postContainer.find('input#topic-thumb-url');
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
titleEl.val(titleEl.val().trim());
|
|
||||||
bodyEl.val(bodyEl.val().trim());
|
|
||||||
if (thumbEl.length) {
|
|
||||||
thumbEl.val(thumbEl.val().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
var checkTitle = (parseInt(postData.cid, 10) || parseInt(postData.pid, 10)) && postContainer.find('input.title').length;
|
|
||||||
|
|
||||||
if (uploads.inProgress[post_uuid] && uploads.inProgress[post_uuid].length) {
|
|
||||||
return composerAlert(post_uuid, '[[error:still-uploading]]');
|
|
||||||
} else if (checkTitle && titleEl.val().length < parseInt(config.minimumTitleLength, 10)) {
|
|
||||||
return composerAlert(post_uuid, '[[error:title-too-short, ' + config.minimumTitleLength + ']]');
|
|
||||||
} else if (checkTitle && titleEl.val().length > parseInt(config.maximumTitleLength, 10)) {
|
|
||||||
return composerAlert(post_uuid, '[[error:title-too-long, ' + config.maximumTitleLength + ']]');
|
|
||||||
} else if (checkTitle && !utils.slugify(titleEl.val()).length) {
|
|
||||||
return composerAlert(post_uuid, '[[error:invalid-title]]');
|
|
||||||
} else if (bodyEl.val().length < parseInt(config.minimumPostLength, 10)) {
|
|
||||||
return composerAlert(post_uuid, '[[error:content-too-short, ' + config.minimumPostLength + ']]');
|
|
||||||
} else if (bodyEl.val().length > parseInt(config.maximumPostLength, 10)) {
|
|
||||||
return composerAlert(post_uuid, '[[error:content-too-long, ' + config.maximumPostLength + ']]');
|
|
||||||
}
|
|
||||||
|
|
||||||
var composerData = {}, action;
|
|
||||||
|
|
||||||
if (parseInt(postData.cid, 10) > 0) {
|
|
||||||
action = 'topics.post';
|
|
||||||
composerData = {
|
|
||||||
handle: handleEl ? handleEl.val() : undefined,
|
|
||||||
title: titleEl.val(),
|
|
||||||
content: bodyEl.val(),
|
|
||||||
topic_thumb: thumbEl.val() || '',
|
|
||||||
category_id: postData.cid,
|
|
||||||
tags: tags.getTags(post_uuid),
|
|
||||||
lock: options.lock || false
|
|
||||||
};
|
|
||||||
} else if (parseInt(postData.tid, 10) > 0) {
|
|
||||||
action = 'posts.reply';
|
|
||||||
composerData = {
|
|
||||||
tid: postData.tid,
|
|
||||||
handle: handleEl ? handleEl.val() : undefined,
|
|
||||||
content: bodyEl.val(),
|
|
||||||
toPid: postData.toPid,
|
|
||||||
lock: options.lock || false
|
|
||||||
};
|
|
||||||
} else if (parseInt(postData.pid, 10) > 0) {
|
|
||||||
action = 'posts.edit';
|
|
||||||
composerData = {
|
|
||||||
pid: postData.pid,
|
|
||||||
handle: handleEl ? handleEl.val() : undefined,
|
|
||||||
content: bodyEl.val(),
|
|
||||||
title: titleEl.val(),
|
|
||||||
topic_thumb: thumbEl.val() || '',
|
|
||||||
tags: tags.getTags(post_uuid)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.emit(action, composerData, function (err, data) {
|
|
||||||
postContainer.find('.composer-submit').removeAttr('disabled');
|
|
||||||
if (err) {
|
|
||||||
if (err.message === '[[error:email-not-confirmed]]') {
|
|
||||||
return app.showEmailConfirmWarning(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return app.alertError(err.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
discard(post_uuid);
|
|
||||||
drafts.removeDraft(postData.save_id);
|
|
||||||
|
|
||||||
if (action === 'topics.post') {
|
|
||||||
ajaxify.go('topic/' + data.slug);
|
|
||||||
} else {
|
|
||||||
removeComposerHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
$(window).trigger('action:composer.' + action, {composerData: composerData, data: data});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function discard(post_uuid) {
|
|
||||||
if (composer.posts[post_uuid]) {
|
|
||||||
$('#cmp-uuid-' + post_uuid).remove();
|
|
||||||
drafts.removeDraft(composer.posts[post_uuid].save_id);
|
|
||||||
stopNotifyInterval(composer.posts[post_uuid]);
|
|
||||||
stopNotifyTyping(composer.posts[post_uuid]);
|
|
||||||
|
|
||||||
delete composer.posts[post_uuid];
|
|
||||||
composer.active = undefined;
|
|
||||||
taskbar.discard('composer', post_uuid);
|
|
||||||
$('body').css({'margin-bottom': 0});
|
|
||||||
$('[data-action="post"]').removeAttr('disabled');
|
|
||||||
|
|
||||||
|
|
||||||
$('html').removeClass('composing mobile');
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
composer.minimize = function(post_uuid) {
|
|
||||||
var postContainer = $('#cmp-uuid-' + post_uuid);
|
|
||||||
postContainer.css('visibility', 'hidden');
|
|
||||||
composer.active = undefined;
|
|
||||||
taskbar.minimize('composer', post_uuid);
|
|
||||||
|
|
||||||
stopNotifyInterval(composer.posts[post_uuid]);
|
|
||||||
stopNotifyTyping(composer.posts[post_uuid]);
|
|
||||||
|
|
||||||
$('body').css({'margin-bottom': '0px'});
|
|
||||||
};
|
|
||||||
|
|
||||||
return composer;
|
|
||||||
});
|
|
@ -1,44 +0,0 @@
|
|||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/*globals define, config, socket, app*/
|
|
||||||
|
|
||||||
define('composer/categoryList', function() {
|
|
||||||
var categoryList = {};
|
|
||||||
|
|
||||||
categoryList.init = function(postContainer, postData) {
|
|
||||||
var listEl = postContainer.find('.category-list');
|
|
||||||
if (!listEl.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.emit('categories.getCategoriesByPrivilege', 'topics:create', function(err, categories) {
|
|
||||||
if (err) {
|
|
||||||
return app.alertError(err.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove categories that are just external links
|
|
||||||
categories = categories.filter(function(category) {
|
|
||||||
return !category.link;
|
|
||||||
});
|
|
||||||
|
|
||||||
categories.forEach(function(category) {
|
|
||||||
$('<option value="' + category.cid + '">' + category.name + '</option>').appendTo(listEl);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (postData.cid) {
|
|
||||||
listEl.find('option[value="' + postData.cid + '"]').prop('selected', true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
listEl.on('change', function() {
|
|
||||||
if (postData.cid) {
|
|
||||||
postData.cid = this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$('[tabindex=' + (parseInt($(this).attr('tabindex'), 10) + 1) + ']').trigger('focus');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return categoryList;
|
|
||||||
});
|
|
@ -1,46 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
/*global define*/
|
|
||||||
|
|
||||||
define('composer/controls', function() {
|
|
||||||
var controls = {};
|
|
||||||
|
|
||||||
/*************************************************/
|
|
||||||
/* Rich Textarea Controls */
|
|
||||||
/*************************************************/
|
|
||||||
controls.insertIntoTextarea = function(textarea, value) {
|
|
||||||
var $textarea = $(textarea);
|
|
||||||
var currentVal = $textarea.val();
|
|
||||||
|
|
||||||
$textarea.val(
|
|
||||||
currentVal.slice(0, textarea.selectionStart) +
|
|
||||||
value +
|
|
||||||
currentVal.slice(textarea.selectionStart)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
controls.wrapSelectionInTextareaWith = function(textarea, leading, trailing){
|
|
||||||
if(trailing === undefined){
|
|
||||||
trailing = leading;
|
|
||||||
}
|
|
||||||
|
|
||||||
var $textarea = $(textarea);
|
|
||||||
var currentVal = $textarea.val();
|
|
||||||
|
|
||||||
$textarea.val(
|
|
||||||
currentVal.slice(0, textarea.selectionStart) +
|
|
||||||
leading +
|
|
||||||
currentVal.slice(textarea.selectionStart, textarea.selectionEnd) +
|
|
||||||
trailing +
|
|
||||||
currentVal.slice(textarea.selectionEnd)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
controls.updateTextareaSelection = function(textarea, start, end){
|
|
||||||
textarea.setSelectionRange(start, end);
|
|
||||||
$(textarea).focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
return controls;
|
|
||||||
});
|
|
@ -1,69 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/* globals define */
|
|
||||||
|
|
||||||
define('composer/drafts', function() {
|
|
||||||
|
|
||||||
var drafts = {};
|
|
||||||
var saveThrottleId;
|
|
||||||
var saving = false;
|
|
||||||
|
|
||||||
drafts.init = function(postContainer, postData) {
|
|
||||||
|
|
||||||
var bodyEl = postContainer.find('textarea');
|
|
||||||
bodyEl.on('keyup', function() {
|
|
||||||
resetTimeout();
|
|
||||||
|
|
||||||
saveThrottleId = setTimeout(function() {
|
|
||||||
saveDraft(postContainer, postData);
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function resetTimeout() {
|
|
||||||
if (saveThrottleId) {
|
|
||||||
clearTimeout(saveThrottleId);
|
|
||||||
saveThrottleId = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drafts.getDraft = function(save_id) {
|
|
||||||
return localStorage.getItem(save_id);
|
|
||||||
};
|
|
||||||
|
|
||||||
function saveDraft(postContainer, postData) {
|
|
||||||
var raw;
|
|
||||||
|
|
||||||
if (canSave() && postData && postData.save_id && postContainer.length) {
|
|
||||||
raw = postContainer.find('textarea').val();
|
|
||||||
if (raw.length) {
|
|
||||||
localStorage.setItem(postData.save_id, raw);
|
|
||||||
} else {
|
|
||||||
drafts.removeDraft(postData.save_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drafts.removeDraft = function(save_id) {
|
|
||||||
resetTimeout();
|
|
||||||
return localStorage.removeItem(save_id);
|
|
||||||
};
|
|
||||||
|
|
||||||
function canSave() {
|
|
||||||
if (saving) {
|
|
||||||
return saving;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
localStorage.setItem('test', 'test');
|
|
||||||
localStorage.removeItem('test');
|
|
||||||
saving = true;
|
|
||||||
return true;
|
|
||||||
} catch(e) {
|
|
||||||
saving = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return drafts;
|
|
||||||
});
|
|
@ -1,58 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/* globals define */
|
|
||||||
|
|
||||||
define('composer/formatting', ['composer/controls', 'composer/preview'], function(controls, preview) {
|
|
||||||
|
|
||||||
var formatting = {};
|
|
||||||
|
|
||||||
var formattingDispatchTable = {
|
|
||||||
'picture': function(){
|
|
||||||
$('#files').click();
|
|
||||||
},
|
|
||||||
|
|
||||||
upload: function(){
|
|
||||||
$('#files').click();
|
|
||||||
},
|
|
||||||
|
|
||||||
tags: function() {
|
|
||||||
$('.tags-container').toggleClass('hidden');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var buttons = [];
|
|
||||||
|
|
||||||
formatting.addComposerButtons = function() {
|
|
||||||
for(var x=0,numButtons=buttons.length;x<numButtons;x++) {
|
|
||||||
$('.formatting-bar .btn-group form').before('<span class="btn btn-link" tabindex="-1" data-format="' + buttons[x].name + '"><i class="' + buttons[x].iconClass + '"></i></span>');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
formatting.addButton = function(iconClass, onClick) {
|
|
||||||
var name = iconClass.replace('fa fa-', '');
|
|
||||||
|
|
||||||
formattingDispatchTable[name] = onClick;
|
|
||||||
buttons.push({
|
|
||||||
name: name,
|
|
||||||
iconClass: iconClass
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
formatting.addButtonDispatch = function(name, onClick) {
|
|
||||||
formattingDispatchTable[name] = onClick;
|
|
||||||
};
|
|
||||||
|
|
||||||
formatting.addHandler = function(postContainer) {
|
|
||||||
postContainer.on('click', '.formatting-bar span', function () {
|
|
||||||
var format = $(this).attr('data-format'),
|
|
||||||
textarea = $(this).parents('.composer').find('textarea')[0];
|
|
||||||
|
|
||||||
if(formattingDispatchTable.hasOwnProperty(format)){
|
|
||||||
formattingDispatchTable[format](textarea, textarea.selectionStart, textarea.selectionEnd);
|
|
||||||
preview.render(postContainer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return formatting;
|
|
||||||
});
|
|
@ -1,83 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/* globals define, socket*/
|
|
||||||
|
|
||||||
define('composer/preview', function() {
|
|
||||||
var preview = {};
|
|
||||||
|
|
||||||
var timeoutId = 0;
|
|
||||||
|
|
||||||
preview.render = function(postContainer, callback) {
|
|
||||||
callback = callback || function() {};
|
|
||||||
if (timeoutId) {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
timeoutId = 0;
|
|
||||||
}
|
|
||||||
var textarea = postContainer.find('textarea');
|
|
||||||
|
|
||||||
timeoutId = setTimeout(function() {
|
|
||||||
socket.emit('modules.composer.renderPreview', textarea.val(), function(err, preview) {
|
|
||||||
timeoutId = 0;
|
|
||||||
if (err) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
preview = $(preview);
|
|
||||||
preview.find('img').addClass('img-responsive');
|
|
||||||
postContainer.find('.preview').html(preview);
|
|
||||||
$(window).trigger('action:composer.preview');
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}, 250);
|
|
||||||
};
|
|
||||||
|
|
||||||
preview.matchScroll = function(postContainer) {
|
|
||||||
var textarea = postContainer.find('textarea');
|
|
||||||
var preview = postContainer.find('.preview');
|
|
||||||
var diff = textarea[0].scrollHeight - textarea.height();
|
|
||||||
|
|
||||||
if (diff === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scrollPercent = textarea.scrollTop() / diff;
|
|
||||||
|
|
||||||
preview.scrollTop(Math.max(preview[0].scrollHeight - preview.height(), 0) * scrollPercent);
|
|
||||||
};
|
|
||||||
|
|
||||||
preview.handleToggler = function(postContainer) {
|
|
||||||
function hidePreview() {
|
|
||||||
togglePreview(false);
|
|
||||||
localStorage.setItem('composer:previewToggled', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showPreview() {
|
|
||||||
togglePreview(true);
|
|
||||||
localStorage.removeItem('composer:previewToggled');
|
|
||||||
}
|
|
||||||
|
|
||||||
function togglePreview(show) {
|
|
||||||
previewContainer.toggleClass('hide', !show);
|
|
||||||
writeContainer.toggleClass('maximized', !show);
|
|
||||||
showBtn.toggleClass('hide', show);
|
|
||||||
|
|
||||||
$('.write').focus();
|
|
||||||
preview.matchScroll(postContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
var showBtn = postContainer.find('.write-container .toggle-preview'),
|
|
||||||
hideBtn = postContainer.find('.preview-container .toggle-preview'),
|
|
||||||
previewContainer = $('.preview-container'),
|
|
||||||
writeContainer = $('.write-container');
|
|
||||||
|
|
||||||
hideBtn.on('click', hidePreview);
|
|
||||||
showBtn.on('click', showPreview);
|
|
||||||
|
|
||||||
if (localStorage.getItem('composer:previewToggled')) {
|
|
||||||
hidePreview();
|
|
||||||
} else {
|
|
||||||
showPreview();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return preview;
|
|
||||||
});
|
|
@ -1,195 +0,0 @@
|
|||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/* globals app, define, config, utils*/
|
|
||||||
|
|
||||||
define('composer/resize', ['autosize'], function(autosize) {
|
|
||||||
var resize = {},
|
|
||||||
oldPercentage = 0;
|
|
||||||
|
|
||||||
resize.reposition = function(postContainer) {
|
|
||||||
var percentage = localStorage.getItem('composer:resizePercentage') || 0.5;
|
|
||||||
|
|
||||||
doResize(postContainer, percentage);
|
|
||||||
};
|
|
||||||
|
|
||||||
function doResize(postContainer, percentage) {
|
|
||||||
var env = utils.findBootstrapEnvironment();
|
|
||||||
|
|
||||||
|
|
||||||
// todo, lump in browsers that don't support transform (ie8) here
|
|
||||||
// at this point we should use modernizr
|
|
||||||
if (env === 'sm' || env === 'xs' || window.innerHeight < 480) {
|
|
||||||
$('html').addClass('composing mobile');
|
|
||||||
autosize(postContainer.find('textarea')[0]);
|
|
||||||
percentage = 1;
|
|
||||||
} else {
|
|
||||||
$('html').removeClass('composing mobile');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (percentage) {
|
|
||||||
var max = getMaximumPercentage();
|
|
||||||
|
|
||||||
if (percentage < 0.25) {
|
|
||||||
percentage = 0.25;
|
|
||||||
} else if (percentage > max) {
|
|
||||||
percentage = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (env === 'md' || env === 'lg') {
|
|
||||||
var transform = 'translate(0, ' + (Math.abs(1-percentage) * 100) + '%)';
|
|
||||||
postContainer.css({
|
|
||||||
'-webkit-transform': transform,
|
|
||||||
'-moz-transform': transform,
|
|
||||||
'-ms-transform': transform,
|
|
||||||
'-o-transform': transform,
|
|
||||||
'transform': transform
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
postContainer.removeAttr('style');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
postContainer.percentage = percentage;
|
|
||||||
|
|
||||||
if (config.hasImageUploadPlugin) {
|
|
||||||
postContainer.find('.img-upload-btn').removeClass('hide');
|
|
||||||
postContainer.find('#files.lt-ie9').removeClass('hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.allowFileUploads) {
|
|
||||||
postContainer.find('.file-upload-btn').removeClass('hide');
|
|
||||||
postContainer.find('#files.lt-ie9').removeClass('hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
postContainer.css('visibility', 'visible');
|
|
||||||
|
|
||||||
// Add some extra space at the bottom of the body so that the user can still scroll to the last post w/ composer open
|
|
||||||
$('body').css({'margin-bottom': postContainer.css('height')});
|
|
||||||
|
|
||||||
resizeWritePreview(postContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
resize.handleResize = function(postContainer) {
|
|
||||||
function resizeStart(e) {
|
|
||||||
var resizeRect = resizeEl[0].getBoundingClientRect(),
|
|
||||||
resizeCenterY = resizeRect.top + (resizeRect.height / 2);
|
|
||||||
|
|
||||||
resizeOffset = (resizeCenterY - e.clientY) / 2;
|
|
||||||
resizeActive = true;
|
|
||||||
resizeDown = e.clientY;
|
|
||||||
|
|
||||||
$(window).on('mousemove', resizeAction);
|
|
||||||
$(window).on('mouseup', resizeStop);
|
|
||||||
$('body').on('touchmove', resizeTouchAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resizeStop(e) {
|
|
||||||
resizeActive = false;
|
|
||||||
|
|
||||||
postContainer.find('textarea').focus();
|
|
||||||
$(window).off('mousemove', resizeAction);
|
|
||||||
$(window).off('mouseup', resizeStop);
|
|
||||||
$('body').off('touchmove', resizeTouchAction);
|
|
||||||
|
|
||||||
var position = (e.clientY - resizeOffset),
|
|
||||||
newHeight = $(window).height() - position,
|
|
||||||
windowHeight = $(window).height();
|
|
||||||
|
|
||||||
if (newHeight > windowHeight - $('#header-menu').height() - (windowHeight / 15)) {
|
|
||||||
snapToTop = true;
|
|
||||||
} else {
|
|
||||||
snapToTop = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleMaximize(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleMaximize(e) {
|
|
||||||
if (e.clientY - resizeDown === 0 || snapToTop) {
|
|
||||||
var newPercentage = getMaximumPercentage();
|
|
||||||
|
|
||||||
if (!postContainer.hasClass('maximized') || !snapToTop) {
|
|
||||||
oldPercentage = postContainer.percentage;
|
|
||||||
doResize(postContainer, newPercentage);
|
|
||||||
postContainer.addClass('maximized');
|
|
||||||
} else {
|
|
||||||
doResize(postContainer, oldPercentage);
|
|
||||||
postContainer.removeClass('maximized');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resizeTouchAction(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
resizeAction(e.touches[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resizeAction(e) {
|
|
||||||
if (resizeActive) {
|
|
||||||
var position = (e.clientY - resizeOffset),
|
|
||||||
newHeight = $(window).height() - position;
|
|
||||||
|
|
||||||
doResize(postContainer, newHeight / $(window).height());
|
|
||||||
|
|
||||||
resizeWritePreview(postContainer);
|
|
||||||
resizeSavePosition(newHeight);
|
|
||||||
|
|
||||||
if (Math.abs(e.clientY - resizeDown) > 0) {
|
|
||||||
postContainer.removeClass('maximized');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resizeSavePosition(px) {
|
|
||||||
var percentage = px / $(window).height(),
|
|
||||||
max = getMaximumPercentage();
|
|
||||||
localStorage.setItem('composer:resizePercentage', percentage < max ? percentage : max);
|
|
||||||
}
|
|
||||||
|
|
||||||
var resizeActive = false,
|
|
||||||
resizeOffset = 0,
|
|
||||||
resizeDown = 0,
|
|
||||||
snapToTop = false,
|
|
||||||
resizeEl = postContainer.find('.resizer');
|
|
||||||
|
|
||||||
resizeEl
|
|
||||||
.on('mousedown', resizeStart)
|
|
||||||
.on('touchstart', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
resizeStart(e.touches[0]);
|
|
||||||
})
|
|
||||||
.on('touchend', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
resizeStop();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function getMaximumPercentage() {
|
|
||||||
return ($(window).height() - $('#header-menu').height() - 1) / $(window).height();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resizeWritePreview(postContainer) {
|
|
||||||
var total = getFormattingHeight(postContainer);
|
|
||||||
postContainer
|
|
||||||
.find('.write-preview-container')
|
|
||||||
.css('height', postContainer.percentage * $(window).height() - $('#header-menu').height() - total);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFormattingHeight(postContainer) {
|
|
||||||
return [
|
|
||||||
postContainer.find('.title-container').outerHeight(true),
|
|
||||||
postContainer.find('.formatting-bar').outerHeight(true),
|
|
||||||
postContainer.find('.topic-thumb-container').outerHeight(true),
|
|
||||||
$('.taskbar').height()
|
|
||||||
].reduce(function(a, b) {
|
|
||||||
return a + b;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return resize;
|
|
||||||
});
|
|
@ -1,92 +0,0 @@
|
|||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/*globals define, config, socket, app*/
|
|
||||||
|
|
||||||
define('composer/tags', function() {
|
|
||||||
var tags = {};
|
|
||||||
|
|
||||||
tags.init = function(postContainer, postData) {
|
|
||||||
var tagEl = postContainer.find('.tags');
|
|
||||||
if (!tagEl.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tagEl.tagsinput({
|
|
||||||
maxTags: config.tagsPerTopic,
|
|
||||||
maxChars: config.maximumTagLength,
|
|
||||||
confirmKeys: [13, 44],
|
|
||||||
trimValue: true
|
|
||||||
});
|
|
||||||
|
|
||||||
tagEl.on('beforeItemAdd', function(event) {
|
|
||||||
event.cancel = event.item.length < config.minimumTagLength || event.item.length > config.maximumTagLength;
|
|
||||||
if (event.item.length < config.minimumTagLength) {
|
|
||||||
app.alertError('[[error:tag-too-short, ' + config.minimumTagLength + ']]');
|
|
||||||
} else if (event.item.length > config.maximumTagLength) {
|
|
||||||
app.alertError('[[error:tag-too-long, ' + config.maximumTagLength + ']]');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tagEl.on('itemAdded', function(event) {
|
|
||||||
$(window).trigger('action:tag.added', {cid: postData.cid, tagEl: tagEl, tag: event.item});
|
|
||||||
});
|
|
||||||
|
|
||||||
addTags(postData.tags, tagEl);
|
|
||||||
|
|
||||||
var input = postContainer.find('.bootstrap-tagsinput input');
|
|
||||||
|
|
||||||
app.loadJQueryUI(function() {
|
|
||||||
input.autocomplete({
|
|
||||||
delay: 100,
|
|
||||||
open: function() {
|
|
||||||
$(this).autocomplete('widget').css('z-index', 20000);
|
|
||||||
},
|
|
||||||
source: function(request, response) {
|
|
||||||
socket.emit('topics.searchTags', {query: request.term, cid: postData.cid}, function(err, tags) {
|
|
||||||
if (err) {
|
|
||||||
return app.alertError(err.message);
|
|
||||||
}
|
|
||||||
if (tags) {
|
|
||||||
response(tags);
|
|
||||||
}
|
|
||||||
$('.ui-autocomplete a').attr('data-ajaxify', 'false');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
select: function(event, ui) {
|
|
||||||
// when autocomplete is selected from the dropdown simulate a enter key down to turn it into a tag
|
|
||||||
triggerEnter(input);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
input.attr('tabIndex', tagEl.attr('tabIndex'));
|
|
||||||
input.on('blur', function() {
|
|
||||||
triggerEnter(input);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function triggerEnter(input) {
|
|
||||||
// http://stackoverflow.com/a/3276819/583363
|
|
||||||
var e = jQuery.Event('keypress');
|
|
||||||
e.which = 13;
|
|
||||||
e.keyCode = 13;
|
|
||||||
setTimeout(function() {
|
|
||||||
input.trigger(e);
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addTags(tags, tagEl) {
|
|
||||||
if (tags && tags.length) {
|
|
||||||
for(var i=0; i<tags.length; ++i) {
|
|
||||||
tagEl.tagsinput('add', tags[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tags.getTags = function(post_uuid) {
|
|
||||||
return $('#cmp-uuid-' + post_uuid + ' .tags').tagsinput('items');
|
|
||||||
};
|
|
||||||
|
|
||||||
return tags;
|
|
||||||
});
|
|
@ -1,324 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/* globals define, utils, config, app */
|
|
||||||
|
|
||||||
define('composer/uploads', ['composer/preview', 'csrf'], function(preview, csrf) {
|
|
||||||
var uploads = {
|
|
||||||
inProgress: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
uploads.initialize = function(post_uuid) {
|
|
||||||
|
|
||||||
initializeDragAndDrop(post_uuid);
|
|
||||||
initializePaste(post_uuid);
|
|
||||||
|
|
||||||
addChangeHandlers(post_uuid);
|
|
||||||
addTopicThumbHandlers(post_uuid);
|
|
||||||
};
|
|
||||||
|
|
||||||
function addChangeHandlers(post_uuid) {
|
|
||||||
var postContainer = $('#cmp-uuid-' + post_uuid);
|
|
||||||
|
|
||||||
postContainer.find('#files').on('change', function(e) {
|
|
||||||
var files = (e.target || {}).files || ($(this).val() ? [{name: $(this).val(), type: utils.fileMimeType($(this).val())}] : null);
|
|
||||||
if(files) {
|
|
||||||
uploadContentFiles({files: files, post_uuid: post_uuid, route: '/api/post/upload'});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
postContainer.find('#topic-thumb-file').on('change', function(e) {
|
|
||||||
var files = (e.target || {}).files || ($(this).val() ? [{name: $(this).val(), type: utils.fileMimeType($(this).val())}] : null),
|
|
||||||
fd;
|
|
||||||
|
|
||||||
if(files) {
|
|
||||||
if (window.FormData) {
|
|
||||||
fd = new FormData();
|
|
||||||
for (var i = 0; i < files.length; ++i) {
|
|
||||||
fd.append('files[]', files[i], files[i].name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uploadTopicThumb({files: files, post_uuid: post_uuid, route: '/api/topic/thumb/upload', formData: fd});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addTopicThumbHandlers(post_uuid) {
|
|
||||||
var postContainer = $('#cmp-uuid-' + post_uuid);
|
|
||||||
|
|
||||||
postContainer.on('click', '.topic-thumb-clear-btn', function(e) {
|
|
||||||
postContainer.find('input#topic-thumb-url').val('').trigger('change');
|
|
||||||
resetInputFile(postContainer.find('input#topic-thumb-file'));
|
|
||||||
$(this).addClass('hide');
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
postContainer.on('paste change keypress', 'input#topic-thumb-url', function() {
|
|
||||||
var urlEl = $(this);
|
|
||||||
setTimeout(function(){
|
|
||||||
var url = urlEl.val();
|
|
||||||
if (url) {
|
|
||||||
postContainer.find('.topic-thumb-clear-btn').removeClass('hide');
|
|
||||||
} else {
|
|
||||||
resetInputFile(postContainer.find('input#topic-thumb-file'));
|
|
||||||
postContainer.find('.topic-thumb-clear-btn').addClass('hide');
|
|
||||||
}
|
|
||||||
postContainer.find('img.topic-thumb-preview').attr('src', url);
|
|
||||||
}, 100);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
uploads.toggleThumbEls = function(postContainer, url) {
|
|
||||||
var thumbToggleBtnEl = postContainer.find('.topic-thumb-toggle-btn');
|
|
||||||
|
|
||||||
postContainer.find('input#topic-thumb-url').val(url);
|
|
||||||
postContainer.find('img.topic-thumb-preview').attr('src', url);
|
|
||||||
if (url) {
|
|
||||||
postContainer.find('.topic-thumb-clear-btn').removeClass('hide');
|
|
||||||
}
|
|
||||||
thumbToggleBtnEl.removeClass('hide');
|
|
||||||
thumbToggleBtnEl.off('click').on('click', function() {
|
|
||||||
var container = postContainer.find('.topic-thumb-container');
|
|
||||||
container.toggleClass('hide', !container.hasClass('hide'));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function resetInputFile($el) {
|
|
||||||
$el.wrap('<form />').closest('form').get(0).reset();
|
|
||||||
$el.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeDragAndDrop(post_uuid) {
|
|
||||||
|
|
||||||
function onDragEnter() {
|
|
||||||
if(draggingDocument) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
drop.css('top', postContainer.find('.write-preview-container').position().top + 'px');
|
|
||||||
drop.css('height', textarea.height());
|
|
||||||
drop.css('line-height', textarea.height() + 'px');
|
|
||||||
drop.show();
|
|
||||||
|
|
||||||
drop.on('dragleave', function() {
|
|
||||||
drop.hide();
|
|
||||||
drop.off('dragleave');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDragDrop(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var files = e.files || (e.dataTransfer || {}).files || (e.target.value ? [e.target.value] : []),
|
|
||||||
fd;
|
|
||||||
|
|
||||||
if(files.length) {
|
|
||||||
if (window.FormData) {
|
|
||||||
fd = new FormData();
|
|
||||||
for (var i = 0; i < files.length; ++i) {
|
|
||||||
fd.append('files[]', files[i], files[i].name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadContentFiles({
|
|
||||||
files: files,
|
|
||||||
post_uuid: post_uuid,
|
|
||||||
route: '/api/post/upload',
|
|
||||||
formData: fd
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
drop.hide();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancel(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($.event.props.indexOf('dataTransfer') === -1) {
|
|
||||||
$.event.props.push('dataTransfer');
|
|
||||||
}
|
|
||||||
|
|
||||||
var draggingDocument = false;
|
|
||||||
|
|
||||||
var postContainer = $('#cmp-uuid-' + post_uuid),
|
|
||||||
drop = postContainer.find('.imagedrop'),
|
|
||||||
textarea = postContainer.find('textarea');
|
|
||||||
|
|
||||||
$(document).off('dragstart').on('dragstart', function() {
|
|
||||||
draggingDocument = true;
|
|
||||||
}).off('dragend').on('dragend', function() {
|
|
||||||
draggingDocument = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
textarea.on('dragenter', onDragEnter);
|
|
||||||
|
|
||||||
drop.on('dragover', cancel);
|
|
||||||
drop.on('dragenter', cancel);
|
|
||||||
drop.on('drop', onDragDrop);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializePaste(post_uuid) {
|
|
||||||
$(window).off('paste').on('paste', function(event) {
|
|
||||||
|
|
||||||
var items = (event.clipboardData || event.originalEvent.clipboardData || {}).items,
|
|
||||||
fd;
|
|
||||||
|
|
||||||
if(items && items.length) {
|
|
||||||
|
|
||||||
var blob = items[0].getAsFile();
|
|
||||||
if(blob) {
|
|
||||||
blob.name = 'upload-' + utils.generateUUID();
|
|
||||||
|
|
||||||
if (window.FormData) {
|
|
||||||
fd = new FormData();
|
|
||||||
fd.append('files[]', blob, blob.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadContentFiles({
|
|
||||||
files: [blob],
|
|
||||||
post_uuid: post_uuid,
|
|
||||||
route: '/api/post/upload',
|
|
||||||
formData: fd
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeRegExp(text) {
|
|
||||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
||||||
}
|
|
||||||
|
|
||||||
function maybeParse(response) {
|
|
||||||
if (typeof response === 'string') {
|
|
||||||
try {
|
|
||||||
return $.parseJSON(response);
|
|
||||||
} catch (e) {
|
|
||||||
return {status: 500, message: 'Something went wrong while parsing server response'};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertText(str, index, insert) {
|
|
||||||
return str.slice(0, index) + insert + str.slice(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
function uploadContentFiles(params) {
|
|
||||||
var files = params.files,
|
|
||||||
post_uuid = params.post_uuid,
|
|
||||||
formData = params.formData,
|
|
||||||
postContainer = $('#cmp-uuid-' + post_uuid),
|
|
||||||
textarea = postContainer.find('textarea'),
|
|
||||||
text = textarea.val(),
|
|
||||||
uploadForm = postContainer.find('#fileForm');
|
|
||||||
|
|
||||||
uploadForm.attr('action', config.relative_path + params.route);
|
|
||||||
|
|
||||||
for(var i = 0; i < files.length; ++i) {
|
|
||||||
var isImage = files[i].type.match(/image./);
|
|
||||||
|
|
||||||
text = insertText(text, textarea.getCursorPosition(), (isImage ? '!' : '') + '[' + files[i].name + '](uploading...) ');
|
|
||||||
|
|
||||||
if(files[i].size > parseInt(config.maximumFileSize, 10) * 1024) {
|
|
||||||
uploadForm[0].reset();
|
|
||||||
return app.alertError('[[error:file-too-big, ' + config.maximumFileSize + ']]');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea.val(text);
|
|
||||||
|
|
||||||
uploadForm.off('submit').submit(function() {
|
|
||||||
function updateTextArea(filename, text) {
|
|
||||||
var current = textarea.val();
|
|
||||||
var re = new RegExp(escapeRegExp(filename) + "]\\([^)]+\\)", 'g');
|
|
||||||
textarea.val(current.replace(re, filename + '](' + text + ')'));
|
|
||||||
}
|
|
||||||
|
|
||||||
uploads.inProgress[post_uuid] = uploads.inProgress[post_uuid] || [];
|
|
||||||
uploads.inProgress[post_uuid].push(1);
|
|
||||||
|
|
||||||
$(this).ajaxSubmit({
|
|
||||||
headers: {
|
|
||||||
'x-csrf-token': csrf.get()
|
|
||||||
},
|
|
||||||
resetForm: true,
|
|
||||||
clearForm: true,
|
|
||||||
formData: formData,
|
|
||||||
|
|
||||||
error: onUploadError,
|
|
||||||
|
|
||||||
uploadProgress: function(event, position, total, percent) {
|
|
||||||
for(var i=0; i < files.length; ++i) {
|
|
||||||
updateTextArea(files[i].name, 'uploading ' + percent + '%');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
success: function(uploads) {
|
|
||||||
uploads = maybeParse(uploads);
|
|
||||||
|
|
||||||
if(uploads && uploads.length) {
|
|
||||||
for(var i=0; i<uploads.length; ++i) {
|
|
||||||
updateTextArea(uploads[i].name, uploads[i].url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preview.render(postContainer);
|
|
||||||
textarea.focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
complete: function() {
|
|
||||||
uploadForm[0].reset();
|
|
||||||
uploads.inProgress[post_uuid].pop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
uploadForm.submit();
|
|
||||||
}
|
|
||||||
|
|
||||||
function uploadTopicThumb(params) {
|
|
||||||
var post_uuid = params.post_uuid,
|
|
||||||
formData = params.formData,
|
|
||||||
postContainer = $('#cmp-uuid-' + post_uuid),
|
|
||||||
spinner = postContainer.find('.topic-thumb-spinner'),
|
|
||||||
thumbForm = postContainer.find('#thumbForm');
|
|
||||||
|
|
||||||
thumbForm.attr('action', config.relative_path + params.route);
|
|
||||||
|
|
||||||
thumbForm.off('submit').submit(function() {
|
|
||||||
spinner.removeClass('hide');
|
|
||||||
|
|
||||||
uploads.inProgress[post_uuid] = uploads.inProgress[post_uuid] || [];
|
|
||||||
uploads.inProgress[post_uuid].push(1);
|
|
||||||
|
|
||||||
$(this).ajaxSubmit({
|
|
||||||
headers: {
|
|
||||||
'x-csrf-token': csrf.get()
|
|
||||||
},
|
|
||||||
formData: formData,
|
|
||||||
error: onUploadError,
|
|
||||||
success: function(uploads) {
|
|
||||||
uploads = maybeParse(uploads);
|
|
||||||
|
|
||||||
postContainer.find('#topic-thumb-url').val((uploads[0] || {}).url || '').trigger('change');
|
|
||||||
},
|
|
||||||
complete: function() {
|
|
||||||
uploads.inProgress[post_uuid].pop();
|
|
||||||
spinner.addClass('hide');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
thumbForm.submit();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUploadError(xhr) {
|
|
||||||
xhr = maybeParse(xhr);
|
|
||||||
app.alertError(xhr.responseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
return uploads;
|
|
||||||
});
|
|
||||||
|
|
Loading…
Reference in New Issue