this.opts.pasteBeforeCallback = $.proxy(this._wPasteBeforeCallback, this);
this.opts.pasteAfterCallback = $.proxy(this._wPasteAfterCallback, this);
+ this.opts.keydownCallback = $.proxy(this._wKeydownCallback, this);
+
var $mpSyncClean = this.syncClean;
var self = this;
this.syncClean = function(html) {
else {
this._convertToHtml();
this.toggleVisual();
+ this._observeQuotes();
this.buttonGet('html').children('i').removeClass('fa-square').addClass('fa-square-o');
}
html = html.replace(/<img [^>]*?src=(["'])([^"']+?)\1.*?>/gi, '[img]$2[/img]');
// [quote]
- // html = html.replace(/<blockquote>/gi, '[quote]');
- // html = html.replace(/\n*<\/blockquote>/gi, '[/quote]');
+ html = html.replace(/<blockquote class="quoteBox" cite="([^"]+)?" data-author="([^"]+)?">/gi, function(match, link, author) {
+ if (link) {
+ return "[quote='" + author + "','" + link + "']";
+ }
+ else if (author) {
+ return "[quote='" + author + "']";
+ }
+
+ return "[quote]";
+ });
+ html = html.replace(/<\/blockquote>/gi, '[/quote]');
+ html = html.replace(/<header data-ignore="true">.*?<\/header>/gi, '');
// handle [color], [size], [font] and [tt]
var $components = html.split(/(<\/?span[^>]*>)/);
}
html = $tmp.join("\n");
+ // trim whitespaces within quote tags
+ html = html.replace(/\[quote([^\]]+)?\](.*?)\[\/quote\]/, function(match, attributes, content) {
+ return '[quote' + attributes + ']' + $.trim(content) + '[/quote]';
+ });
+
// insert codes
if ($.getLength($cachedCodes)) {
for (var $key in $cachedCodes) {
data = data.replace(/\[img='?([^"]*?)'?,'?(left|right)'?\]\[\/img\]/gi,'<img src="$1" style="float: $2" />');
data = data.replace(/\[img='?([^"]*?)'?\]\[\/img\]/gi,'<img src="$1" />');
- // [quote]
- // data = data.replace(/\[quote\]/gi, '<blockquote>');
- // data = data.replace(/\[\/quote\]/gi, '</blockquote>');
-
// [size]
data = data.replace(/\[size=(\d+)\](.*?)\[\/size\]/gi,'<span style="font-size: $1pt">$2</span>');
// preserve leading whitespaces in [code] tags
data = data.replace(/\[code\][\S\s]*?\[\/code\]/, '<pre>$&</pre>');
+ // [quote]
+ var $unquoteString = function(quotedString) {
+ return quotedString.replace(/^['"]/, '').replace(/['"]$/, '');
+ };
+
+ data = data.replace(/\[quote([^\]]+)?\]/gi, function(match, attributes) {
+ var $quote = '<blockquote class="quoteBox" cite="" data-author="">';
+
+ if (attributes) {
+ attributes = attributes.substr(1);
+ attributes = attributes.split(',');
+ var $author = '';
+ var $link = '';
+
+ switch (attributes.length) {
+ case 1:
+ $author = attributes[0];
+ break;
+
+ case 2:
+ $author = attributes[0];
+ $link = attributes[1];
+ break;
+ }
+
+ $author = WCF.String.escapeHTML($unquoteString($.trim($author)));
+ $link = WCF.String.escapeHTML($unquoteString($.trim($link)));
+
+ $quote = '<blockquote class="quoteBox" cite="' + $link + '" data-author="' + $author + '">';
+ $quote += '<div class="container containerPadding">'
+ + '<header data-ignore="true">'
+ + '<h3>'
+ + ($link ? '<a href="' + $link + '">' : '') + WCF.Language.get('wcf.bbcode.quote.title.javascript', { quoteAuthor: WCF.String.unescapeHTML($author) }) + ($link ? '</a>' : '')
+ + '</h3>'
+ + '<a class="redactorQuoteEdit"></a>'
+ + '</header>';
+ }
+
+ $quote += '<div>';
+
+ return $quote;
+ });
+ data = data.replace(/\[\/quote\]/gi, '</div></div></blockquote>');
+
+ data = data.replace(/<p><blockquote/gi, '<blockquote');
+ data = data.replace(/<\/blockquote><\/p>/, '</blockquote>');//<p>' + this.getOption('invisibleSpace') + '</p>');
+
this.$source.val(data);
},
WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'getImageAttachments_' + this.$source.wcfIdentify(), $data);
return $data.imageAttachmentIDs;
+ },
+
+ _wKeydownCallback: function(event) {
+ if (event.which === this.keyCode.DOWN) {
+ var $parent = this.getParent();
+ var $quote = ($parent) ? $($parent).closest('blockquote.quoteBox', this.$editor.get()[0]) : { length: 0 };
+
+ if ($parent && $quote.length) {
+ this.insertAfterLastElement($(parent)[0], $quote[0]);
+ }
+
+ return false;
+ }
+
+ return true;
+ },
+
+ _observeQuotes: function() {
+ this.$editor.find('.redactorQuoteEdit:not(.jsRedactorQuoteEdit)').addClass('jsRedactorQuoteEdit').click($.proxy(this._observeQuotesClick, this));
+ },
+
+ _observeQuotesClick: function(event) {
+ var $header = $(event.currentTarget).closest('header');
+ var $tooltip = $('<span class="redactor-link-tooltip" />');
+
+ $('<a href="#">' + WCF.Language.get('wcf.bbcode.quote.edit') + '</a>').click($.proxy(function(e) {
+ e.preventDefault();
+
+ this._openQuoteEditOverlay($(event.currentTarget).closest('blockquote.quoteBox'));
+ $('.redactor-link-tooltip').remove();
+ }, this)).appendTo($tooltip);
+
+ var $offset = $header.offset();
+ $tooltip.css({
+ left: $offset.left + 'px',
+ top: ($offset.top + 20) + 'px'
+ });
+
+ $('.redactor-link-tooltip').remove();
+ $tooltip.appendTo(document.body);
+ },
+
+ _openQuoteEditOverlay: function(quote) {
+ this.modalInit(WCF.Language.get('wcf.bbcode.quote.edit'), this.opts.modal_quote, 300, $.proxy(function() {
+ $('#redactorQuoteAuthor').val(quote.data('author'));
+
+ // do not use prop() here, an empty cite attribute would yield the page URL instead
+ $('#redactorQuoteLink').val(quote.attr('cite'));
+
+ $('#redactorEditQuote').click($.proxy(function() {
+ var $author = $('#redactorQuoteAuthor').val();
+ quote.data('author', $author);
+ quote.attr('data-author', $author);
+ quote.prop('cite', WCF.String.escapeHTML($('#redactorQuoteLink').val()));
+
+ this._updateQuoteHeader(quote);
+
+ this.modalClose();
+ }, this));
+ }, this));
+ },
+
+ _updateQuoteHeader: function(quote) {
+ var $author = quote.data('author');
+ var $link = quote.attr('cite');
+ if ($link) $link = WCF.String.escapeHTML($link);
+
+ quote.find('> div > header > h3').empty().append(this._buildQuoteHeader($author, $link));
+ },
+
+ insertQuoteBBCode: function(author, link) {
+ if (this.inWysiwygMode()) {
+ var $html = '<blockquote class="quoteBox" cite="' + $link + '" data-author="' + $author + '">'
+ + '<div class="container containerPadding">'
+ + '<header data-ignore="true">'
+ + '<h3>'
+ + this._buildQuoteHeader(author, link)
+ + '</h3>'
+ + '<a class="redactorQuoteEdit"></a>'
+ + '</header>';
+ + '<div id="redactorInsertedQuote">' + this.opts.invisibleSpace + '</div>'
+ + '</div>'
+ + '</blockquote>';
+
+ this.insertHtml($html);
+ }
+ else {
+ var $bbcode = '[quote][/quote]';
+ if (author) {
+ if (link) {
+ $bbcode = "[quote='" + author + "','" + link + "']";
+ }
+ else {
+ $bbcode = "[quote='" + author + "'][/quote]";
+ }
+ }
+
+ this.insertAtCaret($bbcode);
+ }
+ },
+
+ _buildQuoteHeader: function(author, link) {
+ var $header = '';
+ // author is empty, check if link was provided and use it instead
+ if (!author && link) {
+ author = link;
+ link = '';
+ }
+
+ if (author) {
+ if (link) $header += '<a href="' + link + '">';
+
+ $header += WCF.Language.get('wcf.bbcode.quote.title.javascript', { quoteAuthor: WCF.String.unescapeHTML(author) });
+
+ if (link) $header += '</a>';
+ }
+ else {
+ $header = '<small>' + WCF.Language.get('wcf.bbcode.quote.title.clickToSet') + '</small>';
+ }
+
+ return $header;
}
};
+ '</div>'
);
- $.extend( this.opts, {
- modal_file: String()
- + '<section id="redactor-modal-file-insert">'
- + '<div id="redactor-progress" class="redactor-progress-inline" style="display: none;"><span></span></div>'
- + '<form id="redactorUploadFileForm" method="post" action="" enctype="multipart/form-data">'
- + '<label>' + this.opts.curLang.filename + '</label>'
- + '<input type="text" id="redactor_filename" class="redactor_input" />'
- + '<div style="margin-top: 7px;">'
- + '<input type="file" id="redactor_file" name="' + this.opts.fileUploadParam + '" />'
- + '</div>'
- + '</form>'
- + '</section>',
- // img edit
-
- // img
-
- // link
-
- // table
-
- modal_video: String()
- + '<section id="redactor-modal-video-insert">'
- + '<form id="redactorInsertVideoForm">'
- + '<label>' + this.opts.curLang.video_html_code + '</label>'
- + '<textarea id="redactor_insert_video_area" style="width: 99%; height: 160px;"></textarea>'
- + '</form>'
- + '</section>'
- + '<footer>'
- + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
- + '<button id="redactor_insert_video_btn" class="redactor_modal_btn redactor_modal_action_btn">' + this.opts.curLang.insert + '</button>'
- + '</footer>'
-
- });
+ this.setOption('modal_quote',
+ '<fieldset>'
+ + '<dl>'
+ + '<dt><label for="redactorQuoteAuthor">' + WCF.Language.get('wcf.bbcode.quote.edit.author') + '</label></dt>'
+ + '<dd><input type="text" id="redactorQuoteAuthor" class="long" /></dd>'
+ + '</dl>'
+ + '<dl>'
+ + '<dt><label for="redactorQuoteLink">' + WCF.Language.get('wcf.bbcode.quote.edit.link') + '</label></dt>'
+ + '<dd><input type="text" id="redactorQuoteLink" class="long" /></dd>'
+ + '</dl>'
+ + '</fieldset>'
+ + '<div class="formSubmit">'
+ + '<button id="redactorEditQuote">' + this.opts.curLang.save + '</button>'
+ + '</div>'
+ );
},
mpModalInit: function() {
else {
this.modalClose();
}
- }
+ },
+
+ observeLinks: function() {
+ this.$editor.find('a:not(.redactorQuoteEdit)').on('click', $.proxy(this.linkObserver, this));
+
+ this.$editor.on('click.redactor', $.proxy(function(e)
+ {
+ this.linkObserverTooltipClose(e);
+ }, this));
+
+ $(document).on('click.redactor', $.proxy(function(e)
+ {
+ this.linkObserverTooltipClose(e);
+ }, this));
+ },
};