';
+
';
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 + '](' + document.images[i].src + ')
');
-}
-
-// 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].firstChild.nodeValue + ' ' + icons[i].getAttribute('name') + '](' + icons[i].getAttribute('url') + ')
';
-
- 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;
}