diff --git a/Sources/Actions/Display.php b/Sources/Actions/Display.php index 012bce3e701..066877fd028 100644 --- a/Sources/Actions/Display.php +++ b/Sources/Actions/Display.php @@ -1087,7 +1087,7 @@ protected function setupTemplate(): void } // topic.js - Theme::loadJavaScriptFile('topic.js', ['defer' => false, 'minimize' => true], 'smf_topic'); + Theme::loadJavaScriptFile('topic.js', ['defer' => true, 'minimize' => true], 'smf_topic'); // quotedText.js Theme::loadJavaScriptFile('quotedText.js', ['defer' => true, 'minimize' => true], 'smf_quotedText'); diff --git a/Sources/Actions/MessageIndex.php b/Sources/Actions/MessageIndex.php index 100cce1623d..4374d37959a 100644 --- a/Sources/Actions/MessageIndex.php +++ b/Sources/Actions/MessageIndex.php @@ -961,7 +961,7 @@ protected function setupTemplate(): void Theme::loadTemplate('MessageIndex'); // Javascript for inline editing. - Theme::loadJavaScriptFile('topic.js', ['defer' => false, 'minimize' => true], 'smf_topic'); + Theme::loadJavaScriptFile('topic.js', ['defer' => true, 'minimize' => true], 'smf_topic'); // 'Print' the header and board info. Utils::$context['page_title'] = strip_tags(Board::$info->name); diff --git a/Sources/Actions/Search2.php b/Sources/Actions/Search2.php index da0e0b574bf..c3dd66c5a62 100644 --- a/Sources/Actions/Search2.php +++ b/Sources/Actions/Search2.php @@ -432,6 +432,8 @@ protected function setupTemplate(): void } } + Theme::loadJavaScriptFile('topic.js', ['defer' => true, 'minimize' => true], 'smf_topic'); + SearchApi::$loadedApi->resultsContext(); } diff --git a/Themes/default/Display.template.php b/Themes/default/Display.template.php index dd08b88e7ae..c1f38ee0250 100644 --- a/Themes/default/Display.template.php +++ b/Themes/default/Display.template.php @@ -288,63 +288,45 @@ function template_main() '; echo ' - '; } @@ -1039,21 +1010,22 @@ function insertQuoteFast(messageid) echo ' '; } diff --git a/Themes/default/MessageIndex.template.php b/Themes/default/MessageIndex.template.php index de79bf06335..de29af0fc34 100644 --- a/Themes/default/MessageIndex.template.php +++ b/Themes/default/MessageIndex.template.php @@ -204,11 +204,11 @@ function template_main() ', $topic['is_posted_in'] ? '' : '', '
-
'; +
'; // Now we handle the icons echo ' -
'; +
'; if ($topic['is_watched']) echo ' @@ -237,7 +237,7 @@ function template_main()
', $topic['new'] && User::$me->is_logged ? '' . Lang::getTxt('new', file: 'General') . '' : '', ' - ', $topic['first_post']['link'], (!$topic['approved'] ? ' (' . Lang::getTxt('awaiting_approval', file: 'General') . ')' : ''), ' + ', $topic['first_post']['link'], (!$topic['approved'] ? ' (' . Lang::getTxt('awaiting_approval', file: 'General') . ')' : ''), '

@@ -346,11 +346,13 @@ function template_main() // Show breadcrumbs at the bottom too. theme_linktree(); - if (!empty(Utils::$context['can_quick_mod']) && Theme::$current->options['display_quick_mod'] == 1 && !empty(Utils::$context['topics']) && Utils::$context['can_move']) echo ' '; + });'; // Javascript for inline editing. echo ' - '; @@ -548,8 +549,8 @@ function template_topic_legend() if (!empty(Utils::$context['jump_to'])) echo ' '; echo ' diff --git a/Themes/default/Search.template.php b/Themes/default/Search.template.php index e6457891d0a..bf3e87c862e 100644 --- a/Themes/default/Search.template.php +++ b/Themes/default/Search.template.php @@ -460,18 +460,19 @@ function template_results() echo '

'; diff --git a/Themes/default/css/index.css b/Themes/default/css/index.css index 11608132f73..c61c5c83f1d 100644 --- a/Themes/default/css/index.css +++ b/Themes/default/css/index.css @@ -503,7 +503,7 @@ blockquote cite::before { margin: 5px; } .bbc_details[open] > .bbc_summary { - padding-bottom: 7px; + padding-bottom: 7px; border-bottom: 1px dotted #aaa; } .bbc_inline_spoiler { @@ -4018,6 +4018,39 @@ p.information img { .topic_pages::after { content: " \00bb" } + +/* Icon container (normal) */ +.icon_list_box { + background: transparent; + padding: 3px; + text-align: center; + cursor: pointer; +} +.icon_list_box:hover { + background: #ffffff; + border: 1px solid #adadad; + padding: 2px; +} +.icon_list_container { + position: absolute; + cursor: pointer; + background: #ffffff; + border: 1px solid #adadad; + padding: 6px 0; + z-index: 1000; +} +.icon_list_item { + padding: 2px 3px; + line-height: 20px; + border: 1px solid #ffffff; + background: transparent; + display: inline-block; +} +.icon_list_item:hover { + background: #e0e0f0; + border: 1px dotted gray; +} + /* Mentions */ .atwho-view { position: absolute; @@ -4255,7 +4288,7 @@ h3.profile_hd::before, margin-top: 12px; } #attachment_previews { - display: none; + display: none; } #attachment_previews div.descbox > div, #attachment_previews div.errorbox > div, diff --git a/Themes/default/scripts/script.js b/Themes/default/scripts/script.js index bc5278d8517..1a373b2862d 100644 --- a/Themes/default/scripts/script.js +++ b/Themes/default/scripts/script.js @@ -22,85 +22,138 @@ var is_android = ua.indexOf('android') != -1; var ajax_indicator_ele = null; // Get a response from the server. -function getServerResponse(sUrl, funcCallback, sType, sDataType) -{ +function getServerResponse(sUrl, funcCallback, sType = 'GET', sDataType = 'json') { var oCaller = this; - return oMyDoc = $.ajax({ - type: sType, - url: sUrl, - headers: { - "X-SMF-AJAX": 1 - }, - xhrFields: { - withCredentials: typeof allow_xhjr_credentials !== "undefined" ? allow_xhjr_credentials : false - }, - cache: false, - dataType: sDataType, - success: function(response) { - if (typeof(funcCallback) != 'undefined') - { - funcCallback.call(oCaller, response); - } - }, + return smc_Request.fetch(sUrl, { + method: sType, + cache: 'no-cache' + }) + .then(response => { + if (sDataType === 'json') return response.json(); + + if (sDataType === 'text') return response.text(); + + if (sDataType === 'blob') return response.blob(); + + if (sDataType === 'arrayBuffer') return response.arrayBuffer(); + + return response; + }) + .then(data => { + if (typeof funcCallback !== 'undefined') { + funcCallback.call(oCaller, data); + } + + return data; + }) + .catch(error => { + if (typeof funcCallback !== 'undefined') { + funcCallback.call(oCaller, false); + } + + return Promise.reject(error); }); } +class smc_Request { + static fetch(sUrl, oOptions, iMilliseconds) { + let timeout; + let options = oOptions || {}; + + if (iMilliseconds) { + const controller = new AbortController(); + options.signal = controller.signal; + timeout = setTimeout(() => controller.abort(), iMilliseconds); + } + + if (typeof allow_xhjr_credentials !== "undefined" && allow_xhjr_credentials) { + options.credentials = 'include'; + } + + if (options.headers) { + if (options.headers instanceof Headers) { + options.headers.set("X-SMF-AJAX", 1); + } else { + options.headers["X-SMF-AJAX"] = 1; + } + } else { + options.headers = { + "X-SMF-AJAX": 1 + }; + } + + const promise = fetch(sUrl, options) + .then(res => res.ok ? res : Promise.reject(res)) + .catch(err => Promise.reject(new Error(`Network request failed: ${err.message}`))); + + if (iMilliseconds) { + return promise.finally(() => timeout && clearTimeout(timeout)); + } + + return promise; + } + + static fetchXML(sUrl, oOptions, iMilliseconds) { + return this.fetch(sUrl, oOptions, iMilliseconds) + .then(res => res.text()) + .then(str => new DOMParser().parseFromString(str, "text/xml")); + } +} + // Load an XML document. -function getXMLDocument(sUrl, funcCallback) -{ +function getXMLDocument(sUrl, funcCallback, iMilliseconds) { var oCaller = this; + const promise = smc_Request.fetchXML(sUrl, null, iMilliseconds); + + if (funcCallback) { + return promise + .then(data => { + funcCallback.call(oCaller, data); + return data; + }) + .catch(err => { + funcCallback.call(oCaller, false); + return Promise.reject(err); + }); + } - return $.ajax({ - type: 'GET', - url: sUrl, - headers: { - "X-SMF-AJAX": 1 - }, - xhrFields: { - withCredentials: typeof allow_xhjr_credentials !== "undefined" ? allow_xhjr_credentials : false - }, - cache: false, - dataType: 'xml', - success: function(responseXML) { - if (typeof(funcCallback) != 'undefined') - { - funcCallback.call(oCaller, responseXML); - } - }, - }); + return promise; } // Send a post form to the server. -function sendXMLDocument(sUrl, sContent, funcCallback) -{ +function sendXMLDocument(sUrl, sContent, funcCallback) { var oCaller = this; - var oSendDoc = $.ajax({ - type: 'POST', - url: sUrl, - headers: { - "X-SMF-AJAX": 1 - }, - xhrFields: { - withCredentials: typeof allow_xhjr_credentials !== "undefined" ? allow_xhjr_credentials : false - }, - data: sContent, - beforeSend: function(xhr) { - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - }, - dataType: 'xml', - success: function(responseXML) { - if (typeof(funcCallback) != 'undefined') - { - funcCallback.call(oCaller, responseXML); - } - }, - error: function(jqXHR, textStatus, errorThrown) { - console.error(jqXHR.responseText); - } + + const headers = {}; + if (typeof sContent === 'string' || sContent instanceof URLSearchParams) { + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (sContent instanceof Blob) { + headers['Content-Type'] = sContent.type || 'application/octet-stream'; + } else if (!(sContent instanceof FormData)) { + headers['Content-Type'] = 'application/json'; // Default to JSON + sContent = JSON.stringify(sContent); // Convert object to JSON string + } + + const promise = smc_Request.fetchXML(sUrl, { + method: 'POST', + headers, + body: sContent }); - return true; + if (funcCallback) { + return promise + .then(data => { + funcCallback.call(oCaller, data); + return data; + }) + .catch(err => { + funcCallback.call(oCaller, false); + return Promise.reject(err); + }); + } + + return promise; } // Convert a string to an 8 bit representation (like in PHP). @@ -196,76 +249,65 @@ function reqWin(desktopURL, alternateWidth, alternateHeight, noScrollbars) function reqOverlayDiv(desktopURL, sHeader, sIcon) { // Set up our div details - var sAjax_indicator = '
'; - var sHeader = typeof(sHeader) == 'string' ? sHeader : help_popup_heading_text; - - var containerOptions; - if (typeof(sIcon) == 'string' && sIcon.match(/\.(gif|png|jpe?g|svg|bmp|tiff)$/) != null) - containerOptions = {heading: sHeader, content: sAjax_indicator, icon: smf_images_url + '/' + sIcon}; - else - containerOptions = {heading: sHeader, content: sAjax_indicator, icon_class: 'main_icons ' + (typeof(sIcon) != 'string' ? 'help' : sIcon)}; + const sAjax_indicator = '
'; + sHeader = sHeader || help_popup_heading_text; + + let containerOptions; + if (sIcon && sIcon.match(/\.(gif|png|jpe?g|svg|bmp|tiff)$/) != null) { + containerOptions = { heading: sHeader, content: sAjax_indicator, icon: smf_images_url + '/' + sIcon }; + } else { + containerOptions = { heading: sHeader, content: sAjax_indicator, icon_class: 'main_icons ' + (sIcon || 'help') }; + } // Create the div that we are going to load - var oContainer = new smc_Popup(containerOptions); - var oPopup_body = $('#' + oContainer.popup_id).find('.popup_content'); + const oContainer = new smc_Popup(containerOptions); + const oPopup_body = oContainer.cover.querySelector('.popup_content'); // Load the help page content (we just want the text to show) - $.ajax({ - url: desktopURL + (desktopURL.includes('?') ? ';' : '?') + 'ajax', + fetch(desktopURL + (desktopURL.includes('?') ? ';' : '?') + 'ajax', { + method: 'GET', headers: { - 'X-SMF-AJAX': 1 - }, - xhrFields: { - withCredentials: typeof allow_xhjr_credentials !== "undefined" ? allow_xhjr_credentials : false - }, - type: "GET", - dataType: "html", - beforeSend: function () { - }, - success: function (data, textStatus, xhr) { - var help_content = $('
').html(data).find('a[href$="self.close();"]').hide().prev('br').hide().parent().html(); - oPopup_body.html(help_content); + 'X-SMF-AJAX': '1', - if (oPopup_body.find('*:not(:has(*)):visible').text().length > 1200) { - $('#' + oContainer.popup_id).find('.popup_window').addClass('large'); - } + // @fixme This is checked for in SMF\Actions\Login2::checkAjax(). + "X-Requested-With": "XMLHttpRequest" }, - error: function (xhr, textStatus, errorThrown) { - oPopup_body.html(textStatus); - }, - statusCode: { - 403: function(res, status, xhr) { - let errorMsg = res.getResponseHeader('x-smf-errormsg'); - oPopup_body.html(errorMsg ?? banned_text); - }, - 500: function() { - oPopup_body.html('500 Internal Server Error'); - } - } - }); + credentials: typeof allow_xhjr_credentials !== 'undefined' ? 'include' : 'omit' + }) + .then((res, rej) => res.ok ? res.text() : rej(res)) + .then(data => { + oPopup_body.innerHTML = data; + }) + .catch(error => { + const errorMsg = error.headers.get('x-smf-errormsg'); + oPopup_body.innerHTML = errorMsg || error.message || banned_text; + }); + return false; } // Create the popup menus for the top level/user menu area. function smc_PopupMenu(oOptions) { - this.opt = (typeof oOptions == 'object') ? oOptions : {}; + this.opt = oOptions || {}; this.opt.menus = {}; } smc_PopupMenu.prototype.add = function (sItem, sUrl) { - var $menu = $('#' + sItem + '_menu'), $item = $('#' + sItem + '_menu_top'); - if ($item.length == 0) + const menu = document.getElementById(sItem + '_menu'); + const item = document.getElementById(sItem + '_menu_top'); + + if (!item) { return; + } - this.opt.menus[sItem] = {open: false, loaded: false, sUrl: sUrl, itemObj: $item, menuObj: $menu }; + this.opt.menus[sItem] = { open: false, loaded: false, sUrl: sUrl, itemObj: item, menuObj: menu }; - $item.click({obj: this}, function (e) { + item.addEventListener('click', function(e) { e.preventDefault(); - - e.data.obj.toggle(sItem); - }); + this.toggle(sItem); + }.bind(this)); } smc_PopupMenu.prototype.toggle = function (sItem) @@ -280,57 +322,50 @@ smc_PopupMenu.prototype.open = function (sItem) { this.closeAll(); - if (!this.opt.menus[sItem].loaded) - { - this.opt.menus[sItem].menuObj.html('
' + (typeof(ajax_notification_text) != null ? ajax_notification_text : '') + '
'); + if (!this.opt.menus[sItem].loaded) { + this.opt.menus[sItem].menuObj.innerHTML = '
' + (ajax_notification_text || '') + '
'; - $.ajax({ - url: this.opt.menus[sItem].sUrl + (this.opt.menus[sItem].sUrl.includes('?') ? ';' : '?') + 'ajax', + fetch(this.opt.menus[sItem].sUrl + (this.opt.menus[sItem].sUrl.includes('?') ? ';' : '?') + 'ajax', { + method: "GET", headers: { - 'X-SMF-AJAX': 1 - }, - xhrFields: { - withCredentials: typeof allow_xhjr_credentials !== "undefined" ? allow_xhjr_credentials : false - }, - type: "GET", - dataType: "html", - beforeSend: function () { + 'X-SMF-AJAX': 1, }, - context: this.opt.menus[sItem].menuObj, - success: function (data, textStatus, xhr) { - this.html(data); - - if ($(this).hasClass('scrollable')) - $(this).customScrollbar({ - skin: "default-skin", - hScroll: false, - updateOnWindowResize: true - }); + credentials: typeof allow_xhjr_credentials !== "undefined" ? 'include' : 'same-origin', + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); } + return response.text(); + }) + .then(data => { + this.opt.menus[sItem].menuObj.innerHTML = data; + this.opt.menus[sItem].loaded = true; }); - - this.opt.menus[sItem].loaded = true; } - this.opt.menus[sItem].menuObj.addClass('visible'); - this.opt.menus[sItem].itemObj.addClass('open'); + this.opt.menus[sItem].menuObj.classList.add('visible'); + this.opt.menus[sItem].itemObj.classList.add('open'); this.opt.menus[sItem].open = true; // Now set up closing the menu if we click off. - $(document).on('click.menu', {obj: this}, function(e) { - if ($(e.target).closest(e.data.obj.opt.menus[sItem].menuObj.parent()).length) + this.opt.menus[sItem].handleClickOutside = function(e) { + if (e.target.closest('#' + this.opt.menus[sItem].itemObj.id) || e.target.closest('#' + this.opt.menus[sItem].menuObj.id)) { return; - e.data.obj.closeAll(); - $(document).off('click.menu'); - }); + } + + this.closeAll(); + }.bind(this); + + document.addEventListener('click', this.opt.menus[sItem].handleClickOutside); } smc_PopupMenu.prototype.close = function (sItem) { - this.opt.menus[sItem].menuObj.removeClass('visible'); - this.opt.menus[sItem].itemObj.removeClass('open'); + this.opt.menus[sItem].menuObj.classList.remove('visible'); + this.opt.menus[sItem].itemObj.classList.remove('open'); this.opt.menus[sItem].open = false; - $(document).off('click.menu'); + document.removeEventListener('click', this.opt.menus[sItem].handleClickOutside); } smc_PopupMenu.prototype.closeAll = function () @@ -930,285 +965,84 @@ function create_ajax_indicator_ele() document.body.appendChild(ajax_indicator_ele); } -// This function will retrieve the contents needed for the jump to boxes. -function grabJumpToContent(elem) -{ - var oXMLDoc = getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + 'action=xmlhttp;sa=jumpto;xml'); - var aBoardsAndCategories = []; - - ajax_indicator(true); - - oXMLDoc.done(function(data, textStatus, jqXHR){ - - var items = $(data).find('item'); - items.each(function(i) { - aBoardsAndCategories[i] = { - id: parseInt($(this).attr('id')), - isCategory: $(this).attr('type') == 'category', - name: this.firstChild.nodeValue.removeEntities(), - is_current: false, - isRedirect: parseInt($(this).attr('is_redirect')), - childLevel: parseInt($(this).attr('childlevel')) - } - }); - - ajax_indicator(false); +/** + * Parse an HTML template string into a DocumentFragment. + * + * @param {string} template - The HTML string to parse. + * @returns {DocumentFragment} - A fragment containing parsed nodes. + */ +function parseTemplateToFragment(template) { + const parser = new DOMParser(); + const doc = parser.parseFromString(template, 'text/html'); + const frag = document.createDocumentFragment(); - for (var i = 0, n = aJumpTo.length; i < n; i++) - { - aJumpTo[i].fillSelect(aBoardsAndCategories); - } - }); -} - -// This'll contain all JumpTo objects on the page. -var aJumpTo = new Array(); - -// *** JumpTo class. -function JumpTo(oJumpToOptions) -{ - this.opt = oJumpToOptions; - this.dropdownList = null; - this.showSelect(); - - // Register a change event after the select has been created. - $('#' + this.opt.sContainerId).one('mouseenter', function() { - grabJumpToContent(this); - }); -} - -// Show the initial select box (onload). Method of the JumpTo class. -JumpTo.prototype.showSelect = function () -{ - var sChildLevelPrefix = ''; - for (var i = this.opt.iCurBoardChildLevel; i > 0; i--) - sChildLevelPrefix += this.opt.sBoardChildLevelIndicator; - setInnerHTML(document.getElementById(this.opt.sContainerId), this.opt.sJumpToTemplate.replace(/%select_id%/, this.opt.sContainerId + '_select').replace(/%dropdown_list%/, ' ' + (this.opt.sGoButtonLabel != undefined ? '' : ''))); - this.dropdownList = document.getElementById(this.opt.sContainerId + '_select'); -} - -// Fill the jump to box with entries. Method of the JumpTo class. -JumpTo.prototype.fillSelect = function (aBoardsAndCategories) -{ - // Don't do this twice. - $('#' + this.opt.sContainerId).off('mouseenter'); - - // Create an option that'll be above and below the category. - var oDashOption = document.createElement('option'); - oDashOption.appendChild(document.createTextNode(this.opt.sCatSeparator)); - oDashOption.disabled = 'disabled'; - oDashOption.value = ''; - - if ('onbeforeactivate' in document) - this.dropdownList.onbeforeactivate = null; - else - this.dropdownList.onfocus = null; - - if (this.opt.bNoRedirect) - this.dropdownList.options[0].disabled = 'disabled'; - - // Create a document fragment that'll allowing inserting big parts at once. - var oListFragment = document.createDocumentFragment(); - - // Loop through all items to be added. - for (var i = 0, n = aBoardsAndCategories.length; i < n; i++) - { - var j, sChildLevelPrefix, oOption; - - // If we've reached the currently selected board add all items so far. - if (!aBoardsAndCategories[i].isCategory && aBoardsAndCategories[i].id == this.opt.iCurBoardId) - { - this.dropdownList.insertBefore(oListFragment, this.dropdownList.options[0]); - oListFragment = document.createDocumentFragment(); - continue; - } - - if (aBoardsAndCategories[i].isCategory) - oListFragment.appendChild(oDashOption.cloneNode(true)); - else - for (j = aBoardsAndCategories[i].childLevel, sChildLevelPrefix = ''; j > 0; j--) - sChildLevelPrefix += this.opt.sBoardChildLevelIndicator; - - oOption = document.createElement('option'); - oOption.appendChild(document.createTextNode((aBoardsAndCategories[i].isCategory ? this.opt.sCatPrefix : sChildLevelPrefix + this.opt.sBoardPrefix) + aBoardsAndCategories[i].name)); - if (!this.opt.bNoRedirect) - oOption.value = aBoardsAndCategories[i].isCategory ? '#c' + aBoardsAndCategories[i].id : '?board=' + aBoardsAndCategories[i].id + '.0'; - else - { - if (aBoardsAndCategories[i].isCategory || aBoardsAndCategories[i].isRedirect) - oOption.disabled = 'disabled'; - else - oOption.value = aBoardsAndCategories[i].id; - } - oListFragment.appendChild(oOption); - - if (aBoardsAndCategories[i].isCategory) - oListFragment.appendChild(oDashOption.cloneNode(true)); + while (doc.body.firstChild) { + frag.appendChild(doc.body.firstChild); } - // Add the remaining items after the currently selected item. - this.dropdownList.appendChild(oListFragment); + return frag; +} + +/** + * Replace a single placeholder in all text nodes and attributes within a fragment or element. + * + * This function traverses all text nodes under the specified root node using a TreeWalker. + * When it finds a text node containing the placeholder, it checks the type of the replacement value. + * If the value is a string, it simply replaces all occurrences of the placeholder in the text node. + * If the value is a Node, the function splits the text node into "before" and "after" segments, + * inserts the replacement node between them, and removes the original text node. + * + * After processing all text nodes, the function iterates over all elements within the root node + * and examines their attributes. Only string replacements are supported in attributes, so if + * an attribute value contains the placeholder, it is replaced using standard string substitution. + * + * @param {Node} root The root node (fragment, element, etc.) + * @param {string} placeholder The placeholder to replace (e.g. "%select_id%"). + * @param {string|Node} value Replacement string or DOM node. + */ +function replacePlaceholder(root, placeholder, value) { + const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false); + + let node; + while ((node = walker.nextNode())) { + const idx = node.nodeValue.indexOf(placeholder); + + if (idx !== -1) { + if (value instanceof Node) { + // Split text into before/after parts + const before = node.nodeValue.slice(0, idx); + const after = node.nodeValue.slice(idx + placeholder.length); + + const parent = node.parentNode; + if (before) { + parent.insertBefore(document.createTextNode(before), node); + } + parent.insertBefore(value, node); + if (after) { + parent.insertBefore(document.createTextNode(after), node); + } - // Add an onchange action - if (!this.opt.bNoRedirect) - this.dropdownList.onchange = function() { - if (this.selectedIndex > 0 && this.options[this.selectedIndex].value) - window.location.href = smf_scripturl + this.options[this.selectedIndex].value.substr(smf_scripturl.indexOf('?') == -1 || this.options[this.selectedIndex].value.substr(0, 1) != '?' ? 0 : 1); + parent.removeChild(node); + } else { + node.nodeValue = node.nodeValue.replace(placeholder, value); + } } -} - -// A global array containing all IconList objects. -var aIconLists = new Array(); - -// *** IconList object. -function IconList(oOptions) -{ - this.opt = oOptions; - this.bListLoaded = false; - this.oContainerDiv = null; - this.funcMousedownHandler = null; - this.funcParent = this; - this.iCurMessageId = 0; - this.iCurTimeout = 0; - - // Add backwards compatibility with old themes. - if (!('sSessionVar' in this.opt)) - this.opt.sSessionVar = 'sesc'; - - this.initIcons(); -} - -// Replace all message icons by icons with hoverable and clickable div's. -IconList.prototype.initIcons = function () -{ - for (var i = document.images.length - 1, iPrefixLength = this.opt.sIconIdPrefix.length; i >= 0; i--) - if (document.images[i].id.substr(0, iPrefixLength) == this.opt.sIconIdPrefix) - setOuterHTML(document.images[i], '
' + document.images[i].alt + '
'); -} - -// Event for the mouse hovering over the original icon. -IconList.prototype.onBoxHover = function (oDiv, bMouseOver) -{ - oDiv.style.border = bMouseOver ? this.opt.iBoxBorderWidthHover + 'px solid ' + this.opt.sBoxBorderColorHover : ''; - oDiv.style.background = bMouseOver ? this.opt.sBoxBackgroundHover : this.opt.sBoxBackground; - oDiv.style.padding = bMouseOver ? (3 - this.opt.iBoxBorderWidthHover) + 'px' : '3px' -} - -// Show the list of icons after the user clicked the original icon. -IconList.prototype.openPopup = function (oDiv, iMessageId) -{ - this.iCurMessageId = iMessageId; - - if (!this.bListLoaded && this.oContainerDiv == null) - { - // Create a container div. - this.oContainerDiv = document.createElement('div'); - this.oContainerDiv.id = 'iconList'; - this.oContainerDiv.style.display = 'none'; - this.oContainerDiv.style.cursor = 'pointer'; - this.oContainerDiv.style.position = 'absolute'; - this.oContainerDiv.style.background = this.opt.sContainerBackground; - this.oContainerDiv.style.border = this.opt.sContainerBorder; - this.oContainerDiv.style.padding = '6px 0px'; - document.body.appendChild(this.oContainerDiv); - - // Start to fetch its contents. - ajax_indicator(true); - sendXMLDocument.call(this, smf_prepareScriptUrl(smf_scripturl) + 'action=xmlhttp;sa=messageicons;board=' + this.opt.iBoardId + ';xml', '', this.onIconsReceived); } - // Set the position of the container. - var aPos = smf_itemPos(oDiv); - - this.oContainerDiv.style.top = (aPos[1] + oDiv.offsetHeight) + 'px'; - this.oContainerDiv.style.left = (aPos[0] - 1) + 'px'; - this.oClickedIcon = oDiv; - - if (this.bListLoaded) - this.oContainerDiv.style.display = 'block'; - - document.body.addEventListener('mousedown', this.onWindowMouseDown, false); -} - -// Setup the list of icons once it is received through xmlHTTP. -IconList.prototype.onIconsReceived = function (oXMLDoc) -{ - var icons = oXMLDoc.getElementsByTagName('smf')[0].getElementsByTagName('icon'); - var sItems = ''; - - for (var i = 0, n = icons.length; i < n; i++) - sItems += '' + icons[i].getAttribute('name') + ''; - - setInnerHTML(this.oContainerDiv, sItems); - this.oContainerDiv.style.display = 'block'; - this.bListLoaded = true; - - if (is_ie) - this.oContainerDiv.style.width = this.oContainerDiv.clientWidth + 'px'; - - ajax_indicator(false); -} - -// Event handler for hovering over the icons. -IconList.prototype.onItemHover = function (oDiv, bMouseOver) -{ - oDiv.style.background = bMouseOver ? this.opt.sItemBackgroundHover : this.opt.sItemBackground; - oDiv.style.border = bMouseOver ? this.opt.sItemBorderHover : this.opt.sItemBorder; - if (this.iCurTimeout != 0) - window.clearTimeout(this.iCurTimeout); - if (bMouseOver) - this.onBoxHover(this.oClickedIcon, true); - else - this.iCurTimeout = window.setTimeout(this.opt.sBackReference + '.collapseList();', 500); -} - -// Event handler for clicking on one of the icons. -IconList.prototype.onItemMouseDown = function (oDiv, sNewIcon) -{ - if (this.iCurMessageId != 0) - { - ajax_indicator(true); - this.tmpMethod = getXMLDocument; - var oXMLDoc = this.tmpMethod(smf_prepareScriptUrl(smf_scripturl) + 'action=jsmodify;topic=' + this.opt.iTopicId + ';msg=' + this.iCurMessageId + ';' + smf_session_var + '=' + smf_session_id + ';icon=' + sNewIcon + ';xml'), - oThis = this; - delete this.tmpMethod; - ajax_indicator(false); - - oXMLDoc.done(function(data, textStatus, jqXHR){ - oMessage = $(data).find('message') - curMessageId = oMessage.attr('id').replace( /^\D+/g, ''); - - if (oMessage.find('error').length == 0) - { - if (oThis.opt.bShowModify && oMessage.find('modified').length != 0) - $('#modified_' + curMessageId).html(oMessage.find('modified').text()); - - oThis.oClickedIcon.getElementsByTagName('img')[0].src = oDiv.getElementsByTagName('img')[0].src; + if (typeof value === 'string') { + const elements = root.querySelectorAll('*'); + for (let i = 0; i < elements.length; i++) { + const el = elements[i]; + for (let j = 0; j < el.attributes.length; j++) { + const attr = el.attributes[j]; + if (attr.value.includes(placeholder)) { + attr.value = attr.value.replace(placeholder, value); + } } - }); - } -} - -// Event handler for clicking outside the list (will make the list disappear). -IconList.prototype.onWindowMouseDown = function () -{ - for (var i = aIconLists.length - 1; i >= 0; i--) - { - aIconLists[i].funcParent.tmpMethod = aIconLists[i].collapseList; - aIconLists[i].funcParent.tmpMethod(); - delete aIconLists[i].funcParent.tmpMethod; + } } } -// Collapse the list of icons. -IconList.prototype.collapseList = function() -{ - this.onBoxHover(this.oClickedIcon, false); - this.oContainerDiv.style.display = 'none'; - this.iCurMessageId = 0; - document.body.removeEventListener('mousedown', this.onWindowMouseDown, false); -} - // Handy shortcuts for getting the mouse position on the screen - only used for IE at the moment. function smf_mousePose(oEvent) { @@ -1696,20 +1530,20 @@ function expand_quote_parent(oElement) } function avatar_fallback(e) { - var e = window.e || e; + var e = window.e || e; var default_url = smf_avatars_url + '/default.png'; - if (e.target.tagName !== 'IMG' || !e.target.classList.contains('avatar') || e.target.src === default_url ) - return; + if (e.target.tagName !== 'IMG' || !e.target.classList.contains('avatar') || e.target.src === default_url ) + return; e.target.src = default_url; return true; } if (document.addEventListener) - document.addEventListener("error", avatar_fallback, true); + document.addEventListener("error", avatar_fallback, true); else - document.attachEvent("error", avatar_fallback); + document.attachEvent("error", avatar_fallback); // SMF Preview handler. function smc_preview_post(oOptions) diff --git a/Themes/default/scripts/topic.js b/Themes/default/scripts/topic.js index 05279bd6740..4fcc6ffa41b 100755 --- a/Themes/default/scripts/topic.js +++ b/Themes/default/scripts/topic.js @@ -1,3 +1,354 @@ +class JumpTo { + static instances = []; + + constructor(opt) { + this.opt = opt; + this.dropdownList = null; + this.oContainer = document.getElementById(opt.sContainerId); + this.sTemplate = opt.sJumpToTemplate || '%dropdown_list%'; + this.showSelect(); + + let timeout = null; + + // Register instance + JumpTo.instances.push(this); + + // Detect if a "coarse pointer" (usually a touch screen) is the primary input device. + if (window.matchMedia("(pointer: coarse)").matches) + { + const focusHandler = () => + { + this.grabJumpToContent(); + this.oContainer.removeEventListener('focus', focusHandler); + }; + this.oContainer.addEventListener('focus', focusHandler); + } + else + { + const mouseOverHandler = () => + { + timeout = setTimeout(() => + { + this.grabJumpToContent(); + this.oContainer.removeEventListener('mouseover', mouseOverHandler); + this.oContainer.removeEventListener('mouseout', mouseOutHandler); + }, 200); + }; + + const mouseOutHandler = () => + { + clearTimeout(timeout); + }; + + this.oContainer.addEventListener('mouseover', mouseOverHandler); + this.oContainer.addEventListener('mouseout', mouseOutHandler); + } + } + + // This function will retrieve the contents needed for the jump to boxes. + grabJumpToContent() + { + ajax_indicator(true); + + getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + 'action=xmlhttp;sa=jumpto;xml', (xml) => + { + const items = xml.getElementsByTagName('smf')[0].getElementsByTagName('item'); + const boards = []; + + for (let i = 0; i < items.length; i++) + { + const item = items[i]; + boards.push({ + id: parseInt(item.getAttribute('id')), + isCategory: item.getAttribute('type') === 'category', + name: item.firstChild.nodeValue.removeEntities(), + is_current: false, + isRedirect: parseInt(item.getAttribute('is_redirect')), + childLevel: parseInt(item.getAttribute('childlevel')) + }); + } + + ajax_indicator(false); + + for (let i = 0; i < JumpTo.instances.length; i++) + { + JumpTo.instances[i].fillSelect(boards); + } + }); + } + + // Show select using template + showSelect() + { + const el = this.oContainer; + const frag = parseTemplateToFragment(this.sTemplate); + + // Create select element + const select = document.createElement('select'); + select.id = this.opt.sContainerId + '_select'; + select.name = this.opt.sCustomName || select.id; + if (this.opt.sClassName) + { + select.className = this.opt.sClassName; + } + if (this.opt.bDisabled) + { + select.disabled = true; + } + + // Default option + const defaultOption = document.createElement('option'); + defaultOption.value = this.opt.bNoRedirect ? this.opt.iCurBoardId : '?board=' + this.opt.iCurBoardId + '.0'; + defaultOption.textContent = this.opt.sBoardChildLevelIndicator.repeat(this.opt.iCurBoardChildLevel) + this.opt.sBoardPrefix + this.opt.sCurBoardName.removeEntities(); + select.appendChild(defaultOption); + + // Replace placeholders using node walker + replacePlaceholder(frag, '%select_id%', this.opt.sContainerId + '_select'); + replacePlaceholder(frag, '%dropdown_list%', select); + + if (this.opt.sGoButtonLabel) + { + const btn = document.createElement('button'); + btn.className = 'button'; + btn.textContent = this.opt.sGoButtonLabel; + btn.addEventListener('click', () => + { + window.location.href = smf_prepareScriptUrl(smf_scripturl) + (this.opt.sUrlPrefix || '') + 'board=' + this.opt.iCurBoardId + '.0'; + }); + + frag.append(' ', btn); + } + + // Append processed template to container + el.innerHTML = ''; // clear existing + el.appendChild(frag); + + this.dropdownList = select; + + if (!this.opt.bNoRedirect) + { + select.addEventListener('change', function(self) + { + const val = this.options[this.selectedIndex].value; + if (this.selectedIndex > 0 && val) + { + window.location.href = smf_prepareScriptUrl(smf_scripturl) + (self.opt.sUrlPrefix || '') + (val.startsWith('?') ? val.substring(1) : val); + } + }.bind(select, this)); + } + } + + // Fill select with boards/categories + fillSelect(boards) + { + if (!this.dropdownList) + { + return; + } + + const fragment = document.createDocumentFragment(); + const dashOptionTemplate = document.createElement('option'); + dashOptionTemplate.textContent = this.opt.sCatSeparator; + dashOptionTemplate.disabled = true; + + if (this.opt.bNoRedirect) + { + if (this.dropdownList.options[0]) + { + this.dropdownList.options[0].disabled = true; + } + } + + let lastWasCategory = false; + + for (let i = 0; i < boards.length; i++) + { + const item = boards[i]; + + // If we've reached the currently selected board add all items so far. + if (!item.isCategory && item.id === this.opt.iCurBoardId) + { + this.dropdownList.insertBefore(fragment, this.dropdownList.options[0]); + continue; + } + + if (item.isCategory) + { + if (!lastWasCategory) + { + fragment.appendChild(dashOptionTemplate.cloneNode(true)); + lastWasCategory = true; + } + } + else + { + lastWasCategory = false; + } + + const option = document.createElement('option'); + option.textContent = (item.isCategory ? this.opt.sCatPrefix : this.opt.sBoardChildLevelIndicator.repeat(item.childLevel) + this.opt.sBoardPrefix) + item.name; + option.value = item.isCategory ? '#c' + item.id : '?board=' + item.id + '.0'; + + if (this.opt.bNoRedirect && (item.isCategory || item.isRedirect)) + { + option.disabled = true; + } + + fragment.appendChild(option); + + if (item.isCategory) + { + fragment.appendChild(dashOptionTemplate.cloneNode(true)); + } + } + + // Add the remaining items after the currently selected item. + this.dropdownList.appendChild(fragment); + } +} + +// *** IconList object +function IconList(options) { + this.opt = options || {}; + + // Default CSS classes + this.opt.sBoxClass = this.opt.sBoxClass || 'icon_list_box'; + this.opt.sContainerClass = this.opt.sContainerClass || 'icon_list_container'; + this.opt.sItemClass = this.opt.sItemClass || 'icon_list_item'; + + this.bListLoaded = false; + this.oContainerDiv = null; + this.iCurMessageId = 0; + this.oClickedIcon = null; + + if (!IconList.instances) IconList.instances = []; + IconList.instances.push(this); + + this.initIcons(); +} + +// Replace all message icons by icons with hoverable and clickable div's. +IconList.prototype.initIcons = function () { + const prefixLength = this.opt.sIconIdPrefix.length; + const imgs = document.images; + + for (let i = 0; i < imgs.length; i++) { + const img = imgs[i]; + if (img.id.substr(0, prefixLength) === this.opt.sIconIdPrefix) { + const div = document.createElement('div'); + div.className = this.opt.sBoxClass; + div.appendChild(img.cloneNode(true)); + + div.addEventListener('click', this.openPopup.bind(this, div, parseInt(img.id.substr(prefixLength), 10))); + + img.parentNode.replaceChild(div, img); + } + } +}; + +// Show the list of icons after the user clicked the original icon. +IconList.prototype.openPopup = function (div, messageId) { + this.iCurMessageId = messageId; + this.oClickedIcon = div; + + if (!this.bListLoaded && this.oContainerDiv == null) { + this.oContainerDiv = document.createElement('div'); + this.oContainerDiv.className = this.opt.sContainerClass; + document.body.appendChild(this.oContainerDiv); + + ajax_indicator(true); + getXMLDocument( + smf_prepareScriptUrl(smf_scripturl) + 'action=xmlhttp;sa=messageicons;board=' + this.opt.iBoardId + ';xml', + this.onIconsReceived.bind(this) + ); + } + + const pos = smf_itemPos(div); + this.oContainerDiv.style.top = (pos[1] + div.offsetHeight) + 'px'; + this.oContainerDiv.style.left = (pos[0] - 1) + 'px'; + + if (this.bListLoaded) this.oContainerDiv.style.display = 'block'; + + document.body.addEventListener('mousedown', IconList.onWindowMouseDown); +}; + +// Setup the list of icons once it is received through xmlHTTP. +IconList.prototype.onIconsReceived = function (oXMLDoc) +{ + if (!oXMLDoc) return; + + ajax_indicator(false); + const icons = oXMLDoc.getElementsByTagName('smf')[0].getElementsByTagName('icon'); + const frag = document.createDocumentFragment(); + + for (let i = 0; i < icons.length; i++) { + const icon = icons[i]; + const span = document.createElement('span'); + span.className = this.opt.sItemClass; + + const img = document.createElement('img'); + img.src = icon.getAttribute('url'); + img.alt = icon.getAttribute('name'); + img.title = icon.firstChild ? icon.firstChild.nodeValue : ''; + img.style.verticalAlign = 'middle'; + + span.appendChild(img); + + span.addEventListener('pointerdown', this.onItemMouseDown.bind(this, span, icon.getAttribute('value'))); + + frag.appendChild(span); + } + + this.oContainerDiv.appendChild(frag); + this.oContainerDiv.style.display = 'block'; + this.bListLoaded = true; +}; + +// Event handler for clicking on one of the icons. +IconList.prototype.onItemMouseDown = function(span, newIcon) { + if (this.iCurMessageId === 0) return; + + ajax_indicator(true); + getXMLDocument( + smf_prepareScriptUrl(smf_scripturl) + + 'action=jsmodify;topic=' + this.opt.iTopicId + ';msg=' + this.iCurMessageId + ';' + + smf_session_var + '=' + smf_session_id + ';icon=' + newIcon + ';xml', + oXMLDoc => { + ajax_indicator(false); + if (!oXMLDoc) return; + + const messageEl = oXMLDoc.getElementsByTagName('message')[0]; + if (!messageEl) return; + + const curMessageId = (messageEl.getAttribute('id') || '').replace(/^\D+/g, ''); + if (!messageEl.getElementsByTagName('error')[0]) { + const modifiedEl = messageEl.getElementsByTagName('modified')[0]; + if (this.opt.bShowModify && modifiedEl) { + const modContainer = document.getElementById('modified_' + curMessageId); + if (modContainer) modContainer.innerHTML = modifiedEl.textContent; + } + const img = this.oClickedIcon.getElementsByTagName('img')[0]; + if (img) img.src = span.getElementsByTagName('img')[0].src; + } + } + ); +}; + +// Event handler for clicking outside the list (will make the list disappear). +IconList.onWindowMouseDown = function() { + if (!IconList.instances) return; + for (const inst of IconList.instances) { + inst.collapseList(); + } +}; + +// Collapse the list of icons. +IconList.prototype.collapseList = function() { + if (!this.oClickedIcon || !this.oContainerDiv) return; + this.oContainerDiv.style.display = 'none'; + this.iCurMessageId = 0; +}; + // *** QuickModifyTopic object. function QuickModifyTopic(oOptions) { @@ -7,26 +358,35 @@ function QuickModifyTopic(oOptions) this.sCurMessageId = ''; this.sBuffSubject = ''; this.oCurSubjectDiv = null; - this.oTopicModHandle = document; + this.oTopicModHandle = this.opt.oTopicModHandle || document; this.bInEditMode = false; - this.bMouseOnDiv = false; - this.init(); -} + this.aTextFields = ['subject']; + this.oSourceElments = {}; -// Used to initialise the object event handlers -QuickModifyTopic.prototype.init = function () -{ - // Detect and act on keypress + const oElement = this.oTopicModHandle.getElementById(oOptions.sTopicContainer); + for (const el of oElement.children) + { + if (el.children[1].dataset.msgId) + el.children[1].addEventListener( + 'dblclick', + this.modify_topic.bind(this, el.children[1].dataset.msgId) + ); + } + + // detect and act on keypress this.oTopicModHandle.onkeydown = this.modify_topic_keypress.bind(this); // Used to detect when we've stopped editing. - this.oTopicModHandle.onclick = this.modify_topic_click.bind(this); -}; + this.oTopicModHandle.addEventListener('click', function (oEvent) + { + if (this.bInEditMode && oEvent.target.tagName != 'INPUT') + this.modify_topic_save(smf_session_id, smf_session_var); + }.bind(this)); +} // called from the double click in the div QuickModifyTopic.prototype.modify_topic = function (topic_id, first_msg_id) { - // already editing if (this.bInEditMode) { // Same message then just return, otherwise drop out of this edit. @@ -37,12 +397,27 @@ QuickModifyTopic.prototype.modify_topic = function (topic_id, first_msg_id) } this.bInEditMode = true; - this.bMouseOnDiv = true; this.iCurTopicId = topic_id; - // Get the topics current subject - ajax_indicator(true); - sendXMLDocument.call(this, smf_prepareScriptUrl(smf_scripturl) + "action=quotefast;quote=" + first_msg_id + ";modify;xml", '', this.onDocReceived_modify_topic); + this.sCurMessageId = 'msg_' + first_msg_id; + this.oCurSubjectDiv = document.getElementById('msg_' + first_msg_id); + var oInput = document.createElement('input'); + oInput.type = 'text'; + oInput.name = 'subject'; + oInput.value = this.oCurSubjectDiv.textContent; + oInput.size = '60'; + oInput.style.width = '99%'; + oInput.maxlength = '80'; + oInput.onkeydown = this.modify_topic_keypress.bind(this); + this.oCurSubjectDiv.after(oInput); + oInput.focus(); + + if (this.opt.funcOnAfterCreate) { + this.opt.funcOnAfterCreate.call(this); + } + + // Here we hide any other things they want hidden on edit. + this.set_hidden_topic_areas('none'); } // callback function from the modify_topic ajax call @@ -55,11 +430,6 @@ QuickModifyTopic.prototype.onDocReceived_modify_topic = function (XMLDoc) return true; } - this.sCurMessageId = XMLDoc.getElementsByTagName("message")[0].getAttribute("id"); - this.oCurSubjectDiv = document.getElementById('msg_' + this.sCurMessageId.substr(4)); - this.sBuffSubject = getInnerHTML(this.oCurSubjectDiv); - - // Here we hide any other things they want hidden on edit. this.set_hidden_topic_areas('none'); // Show we are in edit mode and allow the edit @@ -70,7 +440,10 @@ QuickModifyTopic.prototype.onDocReceived_modify_topic = function (XMLDoc) // Cancel out of an edit and return things to back to what they were QuickModifyTopic.prototype.modify_topic_cancel = function () { - setInnerHTML(this.oCurSubjectDiv, this.sBuffSubject); + for (var i of this.aTextFields) + if (i in document.forms.quickModForm) + document.forms.quickModForm[i].remove(); + this.set_hidden_topic_areas(''); this.bInEditMode = false; @@ -87,36 +460,20 @@ QuickModifyTopic.prototype.set_hidden_topic_areas = function (set_style) } } -// For templating, shown that an inline edit is being made. -QuickModifyTopic.prototype.modify_topic_show_edit = function (subject) -{ - // Just template the subject. - setInnerHTML(this.oCurSubjectDiv, ''); - - // attach mouse over and out events to this new div - this.oCurSubjectDiv.instanceRef = this; - this.oCurSubjectDiv.onmouseout = function (oEvent) {return this.instanceRef.modify_topic_mouseout(oEvent);}; - this.oCurSubjectDiv.onmouseover = function (oEvent) {return this.instanceRef.modify_topic_mouseover(oEvent);}; -} - -// Yup that's right, save it +// Yup thats right, save it QuickModifyTopic.prototype.modify_topic_save = function (cur_session_id, cur_session_var) { if (!this.bInEditMode) return true; - // Add backwards compatibility with old themes. - if (typeof(cur_session_var) == 'undefined') - cur_session_var = 'sesc'; - - var i, x = new Array(); - x[x.length] = 'subject=' + document.forms.quickModForm['subject'].value.php_to8bit().php_urlencode(); - x[x.length] = 'topic=' + parseInt(document.forms.quickModForm.elements['topic'].value); - x[x.length] = 'msg=' + parseInt(document.forms.quickModForm.elements['msg'].value); + let x = []; + for (var i of this.aTextFields) + if (i in document.forms.quickModForm) + x.push(i + '=' + document.forms.quickModForm[i].value.php_to8bit().php_urlencode()); // send in the call to save the updated topic subject ajax_indicator(true); - sendXMLDocument.call(this, smf_prepareScriptUrl(smf_scripturl) + "action=jsmodify;topic=" + parseInt(document.forms.quickModForm.elements['topic'].value) + ";" + cur_session_var + "=" + cur_session_id + ";xml", x.join("&"), this.modify_topic_done); + sendXMLDocument.call(this, smf_prepareScriptUrl(smf_scripturl) + "action=jsmodify;topic=" + this.iCurTopicId + ";" + cur_session_var + "=" + cur_session_id + ";xml", x.join("&"), this.modify_topic_done); return false; } @@ -134,17 +491,26 @@ QuickModifyTopic.prototype.modify_topic_done = function (XMLDoc) } var message = XMLDoc.getElementsByTagName("smf")[0].getElementsByTagName("message")[0]; - var subject = message.getElementsByTagName("subject")[0]; + var subject = message.getElementsByTagName("subject")[0].childNodes[0].nodeValue; var error = message.getElementsByTagName("error")[0]; // No subject or other error? if (!subject || error) return false; - this.modify_topic_hide_edit(subject.childNodes[0].nodeValue); + setInnerHTML(this.oCurSubjectDiv, '' + subject + '<' +'/a>') this.set_hidden_topic_areas(''); this.bInEditMode = false; + for (var i of this.aTextFields) + { + if (this.oSourceElments[i]) + setInnerHTML(this.oSourceElments[i], message.getElementsByTagName(i)[0].childNodes[0].nodeValue); + + if (i in document.forms.quickModForm) + document.forms.quickModForm[i].remove(); + } + // redo tips if they are on since we just pulled the rug out on this one if ($.isFunction($.fn.SMFtooltip)) $('.preview').SMFtooltip().smf_tooltip_off; @@ -152,13 +518,6 @@ QuickModifyTopic.prototype.modify_topic_done = function (XMLDoc) return false; } -// Done with the edit, put in new subject and link. -QuickModifyTopic.prototype.modify_topic_hide_edit = function (subject) -{ - // Re-template the subject! - setInnerHTML(this.oCurSubjectDiv, '' + subject + '<' +'/a>'); -} - // keypress event ... like enter or escape QuickModifyTopic.prototype.modify_topic_keypress = function (oEvent) { @@ -183,25 +542,6 @@ QuickModifyTopic.prototype.modify_topic_keypress = function (oEvent) } } -// A click event to signal the finish of the edit -QuickModifyTopic.prototype.modify_topic_click = function (oEvent) -{ - if (this.bInEditMode && !this.bMouseOnDiv) - this.modify_topic_save(smf_session_id, smf_session_var); -} - -// Moved out of the editing div -QuickModifyTopic.prototype.modify_topic_mouseout = function (oEvent) -{ - this.bMouseOnDiv = false; -} - -// Moved back over the editing div -QuickModifyTopic.prototype.modify_topic_mouseover = function (oEvent) -{ - this.bMouseOnDiv = true; -} - // *** QuickReply object. function QuickReply(oOptions) { @@ -436,7 +776,7 @@ QuickModify.prototype.modifySave = function (sSessionId, sSessionVar) // Send in the XMLhttp request and let's hope for the best. ajax_indicator(true); - sendXMLDocument.call(this, smf_prepareScriptUrl(this.opt.sScriptUrl) + "action=jsmodify;topic=" + this.opt.iTopicId + ";" + smf_session_var + "=" + smf_session_id + ";xml", formData, this.onModifyDone); + sendXMLDocument.call(this, smf_prepareScriptUrl(this.opt.sScriptUrl) + "action=jsmodify;topic=" + this.opt.iTopicId + ";" + smf_session_var + "=" + smf_session_id + ";xml", new URLSearchParams(formData).toString(), this.onModifyDone); return false; }