Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//Factotum[https://en.wikipedia.org/wiki/User:Alexis_Jazz/Factotum] is a userscript to add comments to discussions and edit pages in general with more features and options than usual. Report issues on the talk page. If you see short garbage variable names you are reading the compact version of this script.
//Everything in Factotum is (unless noted otherwise) public domain, irrevocably released as WTFPL Version 2[www.wtfpl.net/about/] by its author, Alexis Jazz. If you don't like that or think it's invalid where you live you may use this under CC BY-SA 3.0, CC BY 2.5 or CC BY 4.0 instead. Your choice.
//Facototum includes lz-string by Pieroxy[https://pieroxy.net/blog/pages/lz-string/index.html][https://github.com/pieroxy/lz-string], originally licensed WTFPL (currently licensed MIT, see FTT.LZString below for details), Version 2, modified by Alexis Jazz. See w:en:User:Alexis_Jazz/lz-string for details.
/*globals $:false,mw:false,OO:false*/
/*jshint eqnull:true, eqeqeq:false */
if ( typeof window.FTT == 'undefined' || typeof window.FTT.nodeName != 'undefined' ) { //window.FTT could also exist if there's a section named "FTT"
window.FTT = {};
var FTT = window.FTT;
FTT.run = function(D1,D2,M1,M2,E1,PCL){
'use strict';
/*jshint eqnull:true, eqeqeq:false */
FTT.translations = ['nl','de','sq','ur']; //array of full translations available in FTT-extra.js
D1 = function(str){
	return encodeURIComponent(str);
};
D2 = function(str){
	return decodeURIComponent(str);
};
M1 = function(str){
	return mw.config.get(str);
};
M2 = function(str){
	return mw.util.getParamValue(str);
};
FTT.timestampInit = new Date().getTime();
FTT.semiRandom = FTT.timestampInit.toString().slice(-4); //last 4 digits of current timestamp. Won't change as long as you don't leave the page. Used in regular expressions for temporary placeholders (see FTT.safeText) so you can discuss the actual placeholders without them getting replaced
FTT.openingFormInProgressDelay = 2000; //how many ms it may take to open a form. It's usually only 1 or 2, but if oojs-ui-core isn't available yet and your device is very old, maybe longer. This value is used to throw an error if the form didn't open in time.
FTT.purpleBGJoker = $('.FTTPurpleBG').length; //if some joker decides to post an element with the "FTTPurpleBG" class (which otherwise indicates an ongoing or stuck process), FTT won't break as we subtract those
FTT.SN = 'Factotum'; //script name
FTT.VERSIONDATE = '08:49, 26 September 2024 (UTC)';
FTT.nowikiOpen = '<nowiki>';//not used anywhere but this way the minifier won't remove it. needed because MediaWiki parses JS pages as wikitext, sometimes resulting in maintenance categories
FTT.nowiki = 'nowiki';//to avoid entering nowiki tags in replacements
FTT.userName = M1('wgUserName');
FTT.userLang = M1('wgUserLanguage');
FTT.PN = M1('wgPageName').replace(/_/g,' ');
FTT.ownTalk = (FTT.userName && M1('wgTitle') == M1('wgRelevantUserName') && M1('wgTitle') == FTT.userName && M1('wgCanonicalNamespace') == 'User_talk');
FTT.isMF = (typeof mw.mobileFrontend == 'object');
FTT.isMobile = (FTT.isMF && M1('skin') == 'minerva');
FTT.mwEditSec = '#mw-content-text .mw-heading>.mw-editsection';
FTT.retriedNetworkError = 0;
if ( FTT.userName ) {
	FTT.userNameUnderscore = FTT.userName.replace(/ /g, '_');
	FTT.assert = 'user';
} else {
	FTT.assert = 'anon';
}
FTT.userNSLinkRegExpPart = 'User:|User_talk:';
FTT.userNSWikitextRegExpPart = '[Uu]ser:|[Uu]ser[ _]talk:';
for (FTT.userNSInt=0;FTT.userNSInt<Object.values(M1('wgNamespaceIds')).length;FTT.userNSInt++) { //some wikis have multiple names for the same namespace (like gendered namespaces), nobody knows what will be used in links or wikitext
	try {FTT.testEncodableNameSpace = D1(Object.keys(M1('wgNamespaceIds'))[FTT.userNSInt].slice(1));} catch (e) {delete FTT.testEncodableNameSpace;console.log('FTT: namespace fubar');} //gotwiki has a namespace "\udf3d𐌹𐌿𐍄𐌰𐌽𐌳𐍃", no idea what's up with that, don't really care either
	if ( FTT.testEncodableNameSpace && [2,3].includes(Object.values(M1('wgNamespaceIds'))[FTT.userNSInt]) && ! ['user','user_talk'].includes(Object.keys(M1('wgNamespaceIds'))[FTT.userNSInt]) ) {
		FTT.userNSLinkRegExpPart = FTT.userNSLinkRegExpPart + '|' + D1(Object.keys(M1('wgNamespaceIds'))[FTT.userNSInt].slice(0,1).toUpperCase()) + mw.util.escapeRegExp(D1(Object.keys(M1('wgNamespaceIds'))[FTT.userNSInt].slice(1))) + ':';
		FTT.userNSWikitextRegExpPart = FTT.userNSWikitextRegExpPart + '|[' + Object.keys(M1('wgNamespaceIds'))[FTT.userNSInt].slice(0,1).toUpperCase() + Object.keys(M1('wgNamespaceIds'))[FTT.userNSInt].slice(0,1) + ']' + mw.util.escapeRegExp(Object.keys(M1('wgNamespaceIds'))[FTT.userNSInt].slice(1)).replace(/_/g,'[ _]') + ':';
	}
}
if ( M1('wgServer').match('http') ) {
	FTT.serverName = M1('wgServer'); //beta cluster
} else {
	FTT.serverName = 'https:' + M1('wgServer');
}
FTT.projectIsSULWiki = window.location.hostname.match(/\.(wikipedia|wmflabs|wiktionary|wikiquote|wikinews|wikisource|wikibooks|wikiversity|wikivoyage|wikidata|wikimedia)\.org$/);
if ( window.location.hostname.match(/wikitech/) ) {//wikitech.wikimedia.org is NOT a SUL wiki
	FTT.projectIsSULWiki = false;
}
FTT.postReplyInProgress = {}; //will be filled with PRM.id: bool
FTT.MD = {}; //creating empty object for modules to store their things to avoid conflicts
FTT.msgsObj = {};
FTT.B1Obj = {};
FTT.wikiMsgsObj = {};
var api = new mw.Api();
FTT.kittehStuck = '<a href="https://commons.wikimedia.org/wiki/File:Kitten_stuck_on_top_of_a_tree_trunk.jpg"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/69/Kitten_stuck_on_top_of_a_tree_trunk.jpg/480px-Kitten_stuck_on_top_of_a_tree_trunk.jpg" class="FTTkitteh"/><br/><small>CC BY 2.0 / Guyon Morée</small></a>';
FTT.summaryCredit = ( window.FTTsummaryCredit || ' [[[w:en:User:Alexis Jazz/Factotum|Factotum]]]');
FTT.wikiMsgsObj = {
	mul:{ //multilingual and default texts. Messages that end up in wikitext or edit summaries. Selected using wgPageContentLanguage
	'difflinkname':' (DIFF diffID)',
	'difflinknamebare':'DIFF diffID',
	'difflinknameprevnext':' (DIFF ~diffID)',
	'pageHistoryLinkName':'TITLE (HISTORY)',
	'postCmtSummaryPost':'@USER' + FTT.summaryCredit,
	'editCmtSummary':'[[LINK|NAME]] USER' + FTT.summaryCredit,
	'newSectionCmt':'PLUSSNIPPET'+ FTT.summaryCredit, //"CREATE" will be replaced with "Creating comment", "PLUS" will be replaced with "+comment". Sorry, there is no "new $1", "add(ing) $1", "append(ing) $1" or any similar message available in MediaWiki by default
	'cmt':'comment',
	'plus':'+',
	'rmCmtSummary':'DELETE USER' + FTT.summaryCredit,
	'summaryCredit':FTT.summaryCredit,
	'quoteOpen':'<q>', //only used if there is no talk quote inline template
	'quoteClose':'</q>',
	'refOpen':'<ref name="REFNAME">',
	'refClose':'</ref>',
	},de:{'cmt':'Kommentar'
		//'postCmtSummaryPost':'Antwort an USER' + FTT.summaryCreditDE,
		//'editCmtSummary':'[[LINK|Kommentar bearbeiten]]USER' + FTT.summaryCreditDE,
		//'rmCmtSummary':'Kommentar gelöschtUSER' + FTT.summaryCredit,
	},en:{
		//'postCmtSummaryPost':'replying to USER' + FTT.summaryCredit,
	},nl:{'cmt':'reactie'
	},fr:{'cmt':'commentaire'
	},ja:{'cmt':'コメント'
	},es:{'cmt':'comentario'
	},ru:{'cmt':'сообщения'
	},pt:{'cmt':'comentário'
	},zh:{'cmt':'留言'
	},it:{'cmt':'commento'
	},ar:{'cmt':'تعليق'
	},pl:{'cmt':'komentarza'
	},uk:{'cmt':'коментар'
	},sv:{'cmt':'kommentar'
	},vi:{'cmt':'bình luận'
	},fa:{'cmt':'نظر'
	},id:{'cmt':'komentar'
	},he:{'cmt':'תגובה'
	},tr:{'cmt':'yorum'
	},cs:{'cmt':'komentáře'
	},sq:{'cmt':'përgjigju'
	}
};
PCL = 'wgPageContentLanguage';
if ( typeof window.FTTWikiMsgsObj != 'undefined' && window.FTTWikiMsgsObj[ M1(PCL) ] ) { // FTTwikiMsgsObj could be specified in common.js before FTT is loaded
	FTT.wikiMsgs = Object.assign(FTT.wikiMsgsObj.mul, window.FTTWikiMsgsObj[ M1(PCL) ]);
} else if ( FTT.wikiMsgsObj[ M1(PCL) ] ) {
	FTT.wikiMsgs = Object.assign(FTT.wikiMsgsObj.mul, FTT.wikiMsgsObj[ M1(PCL) ]);
} else {
	FTT.wikiMsgs = FTT.wikiMsgsObj.mul;
}
FTT.msgsObj.mul = { //multilingual and default texts, selected using wgUserLanguage. Messages that are used beyond the initial opening of the form.
	'UILabelLoad':'Load',
	'editLinks':'Insert links to edit my comments',
	'replySecLink':'Add extra reply button for the section starter to the bottom of the section if there are >3 commenters',
	'nSecLink':'Insert link to add a new section',
	'firstHeadingAdd':'Add new section link to toolbar instead of page heading',
	'nSecBottomLink':'Insert link to add a new section at the end of the page',
	'secLinks':'Insert links to add comments/subsections to existing sections',
	'sectionIsNewTO':'Take over URLs containing section=new',
	'inputBoxTO':'Take over <a href="https://www.mediawiki.org/wiki/Extension:InputBox" target="_new">InputBoxes</a>',
	'mwuibuttonTO':'Take over new section buttons with .mw-ui-button and .oo-ui-buttonElement-button class',
	'hideArchived':'Hide reply links within elements with the "archived" or "boilerplate" class',
	'hideArchivedAll':'Hide thanks/permalink links within elements with the "archived" or "boilerplate" class',
	'editFullPage':'Enable full page editing',
	'firstHeadingFull':'Add full page edit icon to toolbar instead of page heading',
	'editFullSection':'Enable full section editing',
	'editFullSHref':'Add link to section edit icon (allows opening the native editor in a new tab/window with long/right/middle click, slightly increases page load time)',
	'editFullSHide':'Hide native section edit link',
	'editRefs':'Edit references',
	'dateLinksIconSection':'Add link/permalink generator icon for sections',
	'dateLinksIconSectExtra':'Also provide non-permalink',
	'dateLinksIcon':'Permalink generator for comments',
	'dateLinksIconAlt':'When the reply form is open, use comment link icons in the same section to insert anchored links to those comments',
	'swapIcons':'Change appearance of permalink/reply icons when opening a form to indicate their function changed',
	'thankLink':'',
	'scrollTop':'Arrow in section headers to scroll back to the top of the page',
	'scrollPrev':'Arrow in section headers to scroll to the previous section',
	'scrollNext':'Arrow in section headers to scroll to the next section',
	'reverseSectionOrder':'Reverse section order',
	'reverseCollapToC':'Automatically collapse table of contents',
	'collapsible':'Collapsible sections',
	'autoCollapse':'Automatically collapse sections that contain no new comments since your last visit',
	'collapArticle':'Collapsible sections on non-talk pages',
	'collapArticleDefault':'Collapse all non-talk page sections by default on desktop',
	'collapArticleDefaultMF':'Collapse all non-talk page sections by default on mobile',
	'collapIcons':'Add "collapse section" icon after every comment',
	'autonum':'Add hierarchical outline-style numbering to headers',
	'autonumPlain':'Style as plain text',
	'autonumCopy':'Copy link on click',
	'autonumScroll':'Scroll to table of contents on click',
	'floatingToC':'Floating table of contents',
	'hideToC':'Hide table of contents',
	'discussionActivity':'Show discussion activity',
	'discussionActivityTitleOnly':'Add activity information only to title attribute of sections',
	'dateLinksLocalTime':'Display signature dates in local time ([customize])',
	'dateLinksLocalTimeUserOptTZ':'Use timezone from account preferences',
	'dateLinksLocalTimeRelative':'Show relative date (15 days ago)',
	'dateLinksLocalTimeAbsolute':'Show actual date (1 May 2022)',
	'dateLinksLocalTime12H':'12-hour clock (7:15 PM)',
	'dateLinksLocalTimeNumMonth':'Months as a number',
	'dateLinksLocalTimeLongMonth':'Full month name (December)',
	'dateLinksLocalTimeWeekday':'Abbreviated weekday (Tue)',
	'dateLinksLocalTimeWeekdayFull':'Full weekday (Tuesday)',
	'cureDTBlueStreak':'Automatically fade out comment highlighting by DiscussionTools',
	'UILabelInterface':'Interface',
	'tosNag':'Annoy me with the terms of service/license all the time (you can disable this!)',
	'markup':'Bold/italic/etc markup button',
	'markupLink':'Button to insert links',
	'undoFunc':'Undo/redo function (which also works after markup insertion)',
	'undoShortcuts':'Take over ctrl(+shift)+z/y',
	'undoBtn':'Undo button',
	'redoBtn':'Redo button',
	'pingDropDown':'Button to open user mention form',
	'pingDropDownAt':'Open user mentions menu by entering @',
	'refList':'Reference list when editing a full page',
	'onetimetools':'Button (magnifying glass) for search and replace, section moving and other advanced tools',
	'onetimetoolsSearch':'Search and replace',
	'onetimetoolsArchive':'Section archive wrapper checkbox',
	'onetimetoolsMove':'Section move tool',
	'onetimetoolsBlock':'Block tool (admin only)',
	'onetimetoolsList':'List generator tools',
	'redirAfterMove':'After moving a section, go to section in its new location',
	'clearEditFullPage':'Hide page content when editing full page',
	'editNotice':'Load <a href="https://en.wikipedia.org/wiki/Wikipedia:Editnotice" target="_new">editnotices</a>',
	'noticeNeverPopup':'Never automatically popup edit notices',
	'anoneditwarn':'Warn me when opening the form while not logged in (anoneditwarning, informs you that you will be revealing your IP-address)',
	'wrongUI':'Put submit/preview/cancel buttons on the other side',
	'checkNewComments':'Check for new comments when opening form/preview',
	'previewAboveFull':'Render preview above form instead of below when editing a full page/section',
	'previewAboveOther':'Render preview above form instead of below in other situations',
	'previewBtns':'Live preview toggle / side-by-side preview toggle style:',
	'overlayThreshold':'Open form on overlay when screen width is less than this number of pixels:',
	'markupAbove':'Put markup/link toolbar above text input NODUPEnotOverlay',
	'barRightAbove':'Put bar with settings/search-and-replace above text input NODUPEnotOverlay',
	'notOverlay':'(when not in overlay mode)',
	'limitWidth':'Limit interface width',
	'warnExit':'Warn before leaving the page with an entered comment',
	'floatReturn':'Magically appearing floating link to return to the form when you\'ve scrolled it out of view',
	'UIfontSize':'Interface size:',
	'customSummary':'Field to add your own message to the edit summary and checkbox to mark edits as minor',
	'customBackground':'Custom background',
	'cancelDestructive':'Set "destructive" flag on cancel buttons (adds red color)',
	'UILabelEditing':'Editing',
	'livePreviewCmt':'Enable live preview by default for comments',
	'livePreviewOther':'Enable live preview by default for other edit types',
	'smartLivePreview':'Improve response time and reduce server load: when only a textnode has changed, update textnode locally',
	'aggressiveLivePreview':'Reduce delay when rendering live previews. Improves responsiveness but may increase bandwidth usage.',
	'spellcheck':'Enable spellchecker features of your browser on main text input',
	'bracketToForm':'Move to link insertion form when typing [[',
	'bracketToFormT':'Move to template insertion form when typing {{',
	'bracketToFormFast':'Only move to link/template insertion when typing brackets quickly',
	'autoPing':'Auto-mention (preload reply form with mention of recipient)',
	'quoteSelect':'Include highlighted/selected text as quote with mention',
	'AWBtypos':'Automatically apply <a href="https://www.wikidata.org/wiki/Q6585066">AutoWikiBrowser RegExTypoFix</a>',
	'AWBtypoPreview':'RegExTypoFix on preview',
	'AWBtyposCustomTitle':'Page title for RegExTypoFix (not needed on WMF wikis)',
	'enableCI':'Custom inserts',
	'cI':'One insert/replacement per line. Example insert: foo[FTTCRT]barNUM:<<AUTOPOST|CMT|NONCMT|SUMMARY=foo|Label>> Example replacement:/lorem/ipsum/:<<lipsum>>',
	'autoPostAbove':'Put autopost-type custom inserts/replacements (buttons that insert/replace text and submit the form afterwards) above the text input',
	'enableCIThatRun':'Custom regular expressions that are automatically applied on preview/publish',
	'cIThatRun':'One replacement per line. Example: /[Ff]ooNUM/bar/g:<<comment that will be ignored>>',
	'enableCIThatRunCmt':'Custom regular expressions that are applied to comments only',
	'cIThatRunCmt':'One replacement per line. Example: /[Ff]ooNUM/bar/g:<<comment that will be ignored>>',
	'runCIAgain':'Run automatic replacements again after processing markup (useful to apply replacements to URLs that need to be rewritten first)',
	'monospace':'Use monospace font in editing window by default (switch with ctrl+alt+m)',
	'markdown':'Convert <a href="https://en.wikipedia.org/wiki/Markdown" target="_new">Markdown</a> markup language (partial implementation)',
	'bbcode':'Convert <a href="https://en.wikipedia.org/wiki/BBCode" target="_new">BBCode</a> markup language',
	'outdent':'Automatically outdent when exceeding level:',
	'sumSnippet':'Automatically add a snippet of your comment to the edit summary',
	'saveDraft':'Try to recover unsent comments of >100 characters after unexpected exit',
	'pingText':'Mention text:',
	'pingTextInSection':'Mention text when pressing a speech balloon within the same section:',
	'neverPing':'Never automatically ping the following users (pipe separated list):',
	'rewritun':'Rewrite external URLs to internal ones where possible, clean internal links',
	'rewriteOnBlur':'Rewrite external URLs and apply regular expressions when the main text input loses focus',
	'runRewritunAgain':'Run URL rewriting again after processing markup (useful if replacements result in links that need to be processed)',
	'rewritunOther':'CSS selector for other elements to rewrite URLs for (on blur):',
	'runCIOther':'CSS selector for other elements to apply regular expressions to (on blur):',
	'AWBtyposOther':'CSS selector for other elements to apply AutoWikiBrowser RegExTypoFix to (on blur):',
	'UILabelSubscribe':'Subscriptions',
	'stalkAddSubLinks':'Add icons to subscribe to sections',
	'stalkMaxSubsSize':'Maximum subscription object size (KiB):',
	'stalkAutoSub':'Automatically subscribe when replying',
	'stalkStoreInPrefs':'Store subscriptions in account preferences. Takes some extra bandwidth. Allows your subscriptions to be shared across multiple devices/browsers.',
	'markNewCmts':'Mark new comments since your last visit to the page',
	'markNewCmtsSubbed':'Mark new comments in sections you subscribed to since your last visit to the page',
	'stalkAddCycleBtn':'Button to scroll to the next unread comment on a page',
	'stalkAddCycleBtnSubbed':'Button to scroll to the next unread comment on a page in sections you subscribed to',
	'stalkMarkReadScroll':'Mark comments as read when you scroll them into view',
	'stalkWatchListCmts':'[unfinished] Show unread comments from subscribed sections on watchlist',
	'stalkTackOnEcho':'Notify me about new comments in the general notification area',
	'stalkTackOnMail':'Notify me about new comments by mail (mail can only be send while you are browsing)',
	'stalkInterval':'Number of minutes between checks for new comments outside the watchlist:',
	'UILabelMobile':'Mobile',
	'overlayPad':'Add padding to the fullscreen form on narrow screens',
	'MFmarkupAbove':'DUPLICATE:markupAbove',
	'MFbarRightAbove':'DUPLICATE:barRightAbove',
	'MFAdj':'Customize settings to disable/hide on mobile',
	'MFAdjeditFS':'Hide Minerva\'s native section edit markers',
	'MFAdjSumma':'DUPLICATE:customSummary',
	'MFAdjCI':'DUPLICATE:enableCI',
	'MFAdjMarkup':'DUPLICATE:markup',
	'MFAdjLink':'DUPLICATE:markupLink',
	'MFAdjPing':'DUPLICATE:pingDropDown',
	'MFAdjundoBtn':'DUPLICATE:undoBtn',
	'MFAdjredoBtn':'DUPLICATE:redoBtn',
	'MFAdjAdv':'DUPLICATE:onetimetools',
	'MFAdjSwitch':'DUPLICATE:editorSwitch',
	'MFAdjEditNotice':'Edit notice button',
	'MFAdjDiff':'Diff button',
	'MFAdjReflist':'DUPLICATE:refList',
	'MFAdjscrollTop':'DUPLICATE:scrollTop',
	'MFAdjscrollPrev':'DUPLICATE:scrollPrev',
	'MFAdjscrollNext':'DUPLICATE:scrollNext',
	'MFAdjcustomBackground':'DUPLICATE:customBackground',
	'MFAdjlivePreview':'DUPLICATE:liveOn',
	'MFAdjAWBtypos':'DUPLICATE:AWBtypos',
	'MFAdjeditor2010':'DUPLICATE:editor2010',
	'mobileMWCollapsible':'Enable .mw-collapsible elements on the mobile site (Minerva)',
	'loadMinervaD':'Load links on Minerva (desktop)',
	'loadMinervaM':'Load links on Minerva (mobile)',
	'ninjaMobile':'Load links within page content only after activating Factotum from the menu (ninja mode)',
	'hideAdvFE':'Hide/take over reply tools from advanced mobile mode',
	'UILabelAdvanced':'Advanced',
	'showUnpopular':'Show many more settings (customize Factotum to the finest detail, makes the settings form very long)',
	'showRisky':'Show expert settings (indicated by an orange triangle)',
	'showSuperRisky':'Show unfinished features and options that can completely break your experience (indicated by a darker red triangle)',
	'shortcuts':'Enable <a onclick="event.preventDefault();event.stopPropagation();FTT.popup(FTT.msgs.shortcutsMap)">keyboard shortcuts</a>',
	'submitShortcut':'Use ctrl+enter to submit form, ctrl+shift+enter to mark edit as minor and submit form',
	'HLreply':'Highlight the comment you\'re replying to',
	'HLCmtClick':'Highlight comment on click',
	'editCmtDblClick':'Edit comment on double click',
	'replyDblClick':'Reply to comment on double click (when both are available and enabled "edit" will take precedence over "reply")',
	'previewDblClick':'Refresh preview/diff by double clicking it',
	'warnCancel':'Warn before discarding changes',
	'reparseConfirm':'Warn before reparsing page content when there are unread comments',
	'hideDT':'Hide reply links from DiscussionTools',
	'hideDTStats':'Hide discussion activity from DiscussionTools',
	'hideDTSub':'Hide subscription links from DiscussionTools if Factotum subscription links are enabled',
	'hideNewSec':'Hide regular "new section" link from toolbar',
	'methodLocator':'Detect comments by Factotum locator',
	'methodLegacy':'Detect comments by legacy method (essential to reply to comments that were not posted with Factotum)',
	'extendedSigDetect':'Slightly improve detection rate of legacy signatures at the cost of slightly more CPU-time',
	'autoDash':'Automatically prepend my signature with an em dash (—)',
	'useLocator':'Add invisible comment locator to my comments, makes for a less buggy experience, DO NOT DISABLE',
	'preventDoubleHashtag':'Prevent double hashtag indentation (numbered list inside numbered list)',
	'filterDirMarks':'Filter left-to-right/right-to-left (LTR/RTL) marks on wikis using the same direction as the mark when no mark in the other direction is present',
	'enableOnDiffOldId':'Enable Factotum within page content on diffs/old revisions. Terrible idea. Don\'t do it. (not needed for full page editing of old revisions)',
	'editTheUneditable':'Show full page edit icon nearly everywhere, including special pages and protected pages',
	'theStranger':'Add edit links to comments by others. Don\'t expect comment editing to always work well on non-Factotum comments. YMMV.',
	'ninjaLoader':'Load links within page content on desktop only after pressing a section header or double-clicking page content (ninja mode)',
	'killswitch':'If adding reply/edit/etc buttons takes more than 8 seconds stop adding them',
	'recombineNowiki':'Recombine adjacent nowiki tags within syntaxhighlight. Neater wikitext, but when editing such a comment you may be greeted by #tag:syntaxhighlight instead of <syntaxhighlight lang=text> or nowiki tags may fail to be filtered out.',
	'dryRun':'Button to perform a dry run (testing/development ONLY, assume your input will be lost, submits form without making any edits) in advanced tools section (requires advanced tools icon)',
//	'debug':'Enable test/debug mode', //FTT.debug
//	'dbgLimit':'Maximum debug messages to log to console:', //FTT.debug
//	'doubleTimeout':'Assume edit failure after 20 seconds instead of 10 (editing long pages can take slightly more than 10 seconds)',//FTT.debug
	'afterPost':'After posting:',
	'afterPostReload':'Never re-parse the page, just reload instead',
	'watchlist':'Watchlist setting:',
	'watchlistexpiry':'Watchlist expiration: ',
	'watchlistexpirynew':'Watchlist expiration for page creation: ',
	'editor':'Default editor for comments and new sections:',
	'editorSwitch':'Button to switch between editors',
	'editorSwitchSkipSource':'Skip source editor when switching editors for comments and new sections',
	'editorSwitchSkip2010':'Skip 2010 wikitext editor when switching editors for comments and new sections',
	'editorSwitchSkipvisual':'Skip VisualLight when switching editors for comments and new sections',
	'2010wikitextDefault':'Load 2010 wikitext editor by default when editing a full page or section on wikitext pages',
	'2010codepageDefault':'Load 2010 wikitext editor with <a href="https://www.mediawiki.org/wiki/Extension:CodeEditor">CodeEditor</a> by default when editing JavaScript/CSS/JSON pages',
	'2010templateDefault':'Load 2010 wikitext editor with <a href="https://www.mediawiki.org/wiki/Extension:CodeMirror">CodeMirror</a> by default when editing templates',
	'2010codeMirror2023':'Always try to enable CodeMirror in the 2010 wikitext editor on wikitext pages (not working properly atm)',
	'RLmasq':'Masquerade as reply-link (use plain text links instead of icons)',
	'RLmasqSect':'Use plain text links instead of icons for section tools',
	'grayscale':'Grayscale icons',
	'blacklist':'Never load Factotum when page title matches: (one per line, /[Rr]egEx.*/ allowed)',
	'blacklistMain':'Never load Factotum in article/main space',
	'saveTo':'Save/erase settings to/from:',
	'prefLabelBrowser':' (browser)',
	'prefLabelAccountPrefs':' (account preferences)',
	'prefLabelGlobalPrefs':' (global preferences)',
	'watchlistexpirydays':'DAYS days',
	'watchlistexpiryplusdays':'+DAYS days',
	'pingTextPlaceholder':'Yo [[User:PINGUSER|PINGUSER]],',
	'pingTextInSectionPlaceholder':'[[CMTLINK|^]] [[User:PINGUSER|PINGUSER]],',
	'neverPingPlaceHolder':'User1|User2|User3',
	'UIfontSize0875em':'0.875em',
	'UIfontSizeTiny':'Tiny!',
	'UIfontSizeSmall':'Small',
	'UIfontSizeMedium':'Medium',
	'UIfontSizeLarge':'Large',
	'UIfontSizeHuge':'Huge',
	'linkafter':'Just show a link (fastest)',
	'parse':'Parse comments and new sections in place (fast)',
	'parsecmtonly':'Parse comments in place (fast)',
	'parsepage':'Re-parse page without reloading (slower)',
	'reloadafter':'Reload the page (slowest)',
	'saveToAccountPrefs':'Account preferences',
	'saveToGlobalPrefs':'Global preferences',
	'saveToBrowser':'This browser',
	'resetPreferencesButtonUnlock':'Unlock',
	'resetLocalStorFTTSubs':'Erase subscriptions',
	'resetPreferencesButtonLocalStor':'Erase other localStorage items',
	'editorSource':'Source',
	'editor2010':'2010 wikitext editor',
	'editorVisualLight':'VisualLight',
	'editorLastUsed':'Last used',
	'commentDone':' Done!',
	'commentPostedThankYou':' Thanks for using <a href="https://en.wikipedia.org/wiki/User:Alexis_Jazz/Factotum" target="_new">Factotum</a>',
	'nopingNotify':'This user doesn\'t want their username to be linked.',
	'replyToTitle':'Reply to USER / Mention USER',
	'newSubSectionTitle':'Add new comment or subsection',
	'editFullSectionTitle':'Edit section',
	'editTitle':'Change comment',
	'restoreDraft':'A copy of text you were working on but haven\'t submitted has been found. Restore this copy?',
	'removeDraft':'Deleted draft',
	'insertLinkLink':'External URL or article title',
	'insertLinkName':'Link name (optional)',
	'FTT':'Factotum',
	'messedUp1':'Awwww snap.',
	'messedUp2':'I deeply apologize, something has gone terribly wrong.',
	'messedUp3':'Please file this pre-filled out error report.',
	'messedUp4':'If you can\'t edit English Wikipedia you can file the report on META or [[NLWIKT|Dutch Wiktionary]] instead.',
	'cITSbuttonMenu':'@',
	'replaceButton':'Replace',
	'replaceAllButton':'Replace all',
	'AWBtyposButton':'AWB RegExTypoFix',
	'genBulletedList':'Generate bulleted list of links',
	'genNumberedList':'Generate numbered list of links',
	'moveContentToPage':'Move content to page',
	'allowPageCreation':'Allow page creation when moving',
	'archiveSection':'Wrap section in closed/archived block',
	'archiveSectionLabel':'close/archive',
	'subsHeader':'Section subscriptions',
	'subsSeenIt':'Seen it!',
	'readYaCmts':'There are unread comments waiting for you.',
	'sectionTitleNotUnique':'A section with that title already exists',
	'reportBug':'bug?',
	'tackOnNewCmt':'New comment in "SECTION"',
	'tackOnNewCmts':'Multiple new comments in "SECTION"',
	'newLines':'The page has been changed since you loaded it. The following lines were added or changed:',
	'matchedRETF':'Matched <code>REGEXP</code> (WORD)',
	'rewritunOtherPlaceholder':'#wpSummary,.morebits-dialog textarea',
	'nevermind':'Never mind that, it\'s fine!',
	'discActLast':'Latest: LAST',
	'discActLastTip':'Scroll to latest comment',
	'discActS':'COMMENTS comment',
	'discActP':'COMMENTS comments',
	'discActUserS':'USERS contributor',
	'discActUserP':'USERS contributors',
	'refreshDiscussion':'Refresh page',
	'reply':'reply',
	'strikeThrough':'Strikethrough',
	'editorSwitchTitle':'Switch editor',
	'editLede':'Edit lede',
	'viewEditNotice':'View edit notice',
	'cancelLinkForm':'Close insert link form',
	'editRef':'Edit reference',
	'switchSourceNotice':'Activated source editor',
	'switch2010Notice':'Activated 2010 wikitext editor',
	'switchVisualLightNotice':'Activated VisualLight editor',
	'subscribe':'Subscribe',
	'unsubscribe':'Unsubscribe',
	'settingReload':'Some settings may not take effect until you <a onclick="location.reload()">reload the page</a>.',
	'filterTip':'Search for a setting',
	'regularLinkTip':'Regular link',
	'mailHeader':'New comments on wiki:',
	'mailFooter':'',
	'mailSubject':'New messages on wiki',
	'mailBody':'\n* USER @ DATE: LINK',
	'previewSide':'Put preview and text area side by side',
	'previewApart':'Put preview and text area apart',
	'liveOff':'Activate live preview',
	'liveOn':'Deactivate live preview',
	'liveOffNotify':'Activated live preview',
	'liveOnNotify':'Deactivated live preview',
	'pbOverlayTrans':'Partially transparent, hover to view',
	'pbOverlayHidden':'Hidden, hover to view',
	'pbBarCenter':'Bar, centered buttons',
	'pbBarCenterF':'Bar, centered buttons (frameless)',
	'pbBar':'Bar',
	'pbBarF':'Bar (frameless)',
	'pbFloat':'Float',
	'pbFloatF':'Float (frameless)',
	'pbDisabled':'Disabled',
	'FTTCreditLink':'<a id="FTTCreditLink" href="https://en.wikipedia.org/wiki/User:Alexis_Jazz/Factotum" style="font-weight: bold">Factotum</a>.<br/>(<a class="triggerBugLink">bug?</a>)',
	'newHeadingSubj':'Enter a subject to post as a new subsection',
	'same':'(same)',
	'redo':'redo',
	'shortcutsMap':
		'<table class="wikitable" id="FTTshortcutsMap">'+
		'<tr><td>ctrl+enter</td><td>submit form / load links when using ninja mode</td></tr>'+
		'<tr><td>alt+shift+s</td><td>submit form</td></tr>'+
		'<tr><td>ctrl+shift+enter</td><td>check as minor+submit form</td></tr>'+
		'<tr><td>ctrl+alt+m</td><td>toggle monospace</td></tr>'+
		'<tr><td>ctrl+alt+p</td><td rowspan="2">scroll to previous header (while holding down ctrl+alt, press enter to collapse/uncollapse the highlighted section if collapsible sections are enabled, same for scroll to next section, press j/arrow left and l/arrow right to cycle focus for header icons/links)</td></tr>'+
		'<tr><td>ctrl+alt+up</td></tr>'+
		'<tr><td>ctrl+alt+n</td><td rowspan="2">scroll to next header</td></tr>'+
		'<tr><td>ctrl+alt+down</td></tr>'+
		'<tr><td>ctrl+alt+shift+p</td><td rowspan="2">scroll to previous comment (while holding down ctrl+alt, press j/arrow left and l/arrow right to cycle focus for action icons/links)</td></tr>'+
		'<tr><td>ctrl+alt+shift+up</td></tr>'+
		'<tr><td>ctrl+alt+shift+n</td><td rowspan="2">scroll to next comment</td></tr>'+
		'<tr><td>ctrl+alt+shift+down</td></tr>'+
		'<tr><td>ctrl+k</td><td>insert link</td></tr>'+
		'<tr><td>ctrl+b</td><td><b>bold</b> text</td></tr>'+
		'<tr><td>ctrl+i</td><td><i>italicize</i> text</td></tr>'+
		'<tr><td>ctrl+shift+5</td><td><s>strike</s> text</td></tr>'+
		'<tr><td>ctrl+shift+6</td><td><code>code</code> text</td></tr>'+
		'<tr><td>ctrl+u</td><td><u>underline</u> text</td></tr>'+
		'<tr><td>ctrl+, (comma)</td><td><sub>sub</sub> text</td></tr>'+
		'<tr><td>ctrl+. (period)</td><td><sup>super</sup> text</td></tr>'+
		'<tr><td>ctrl+1</td><td>insert level 1 header</td></tr>'+
		'<tr><td>ctrl+2</td><td>insert level 2 header</td></tr>'+
		'<tr><td>ctrl+3</td><td>insert level 3 header</td></tr>'+
		'<tr><td>ctrl+4</td><td>insert level 4 header</td></tr>'+
		'<tr><td>ctrl+5</td><td>insert level 5 header</td></tr>'+
		'<tr><td>ctrl+6</td><td>insert level 6 header</td></tr>'+
		'<tr><td>ctrl+8</td><td>insert quote</td></tr>'+
		'<tr><td>ctrl+alt+f</td><td>advanced tools/search and replace</td></tr>'+
		'<tr><td>alt+shift+v</td><td>view diff</td></tr>'+
		'<tr><td>alt+shift+i</td><td>toggle minor edit</td></tr>'+
		'<tr><td>ctrl+z</td><td>undo</td></tr>'+
		'<tr><td>ctrl+y</td><td rowspan="2">redo</td></tr>'+
		'<tr><td>ctrl+shift+z</td></tr>'+
		'<tr><td>alt+i</td><td>focus input</td></tr>'+
		'<tr><td>ctrl+shift+/ (slash)</td><td>open settings</td></tr>'+
		'<tr><td>ctrl+/ (slash)</td><td>show keyboard shortcuts</td></tr></table>',
	'reparseConfirmPopup':'You\'ve just performed an action that requires some of the page content to be refreshed to see the result. If you continue, comments that are currently marked as new will no longer be marked. Continue?',
	'backToTop':'Back to top',
	'backToBottom':'To bottom',
	'findPrev':'Find previous',
	'scrollPrevHeader':'Scroll to previous header',
	'scrollNextHeader':'Scroll to next header',
	'exportSettings':'Currently saved settings:',
	'importSettings':'Import settings (JSON):',
	'code':'Code',
	'underline':'Underline',
	'struck':'Struck text',
	'nochange':'No change',
};
FTT.mulKeys = Object.keys(FTT.msgsObj.mul);
FTT.B1Obj.mul = { //multilingual and default texts, selected using wgUserLanguage. Messages that are required to load the script and open the form
	'htmlform-submit':'  📢  ', //regular spaces are stripped, figure spaces are not
	'preview':'  👁  ',
	'showdiff':'Diff',
	'cancel':'  🗑️  ',
	'saveprefs':'Save',
	'restoreprefs':'Restore all default settings',
	'mw-widgets-copytextlayout-copy':'Copy',
	'mw-widgets-copytextlayout-copy-success':'Copied to clipboard.',
	'mw-widgets-copytextlayout-copy-fail':'Failed to copy to clipboard.',
	'tooltip-summary':'Edit summary',
	'actionfailed':'Action failed',
	'revid':'revision REVID',
	'diff-empty':'(no differences)',
	'anoneditwarning':'Unless you <a href="' + M1('wgArticlePath').replace('$1', 'Special:UserLogin') + '?returnto=' + D1(M1('wgPageName')) + '">log in</a> or <a href="' + M1('wgArticlePath').replace('$1', 'Special:CreateAccount') + '?returnto=' + D1(M1('wgPageName')) + '">sign up</a> your IP address will be publicly visible if you post a message!',
	'subject':'',
	'newsection':'New section',
	'bold':'B',
	'italic':'I',
	'strike':'xyz',
	'Contributions':'Special:Contributions',
	'Diff':'Special:Diff',
	'Permalink':'Special:PermanentLink',
	'thankLink':'Icon to thank for comments',
	'flow-thanks-confirmation-special':'Do you want to publicly send thanks for this comment?',
	'editsectionhint':'Edit section: $1',
	'tooltip-ca-addsection':'Start a new section',
	'AWBtyposTitle':'',//AWB RETF page title
	'newline':'',//name of template that transcludes a pure newline
	'smiley':'',//Template:Smiley title
	'atops':'',//Archive top substitution template
	'abots':'',//Archive bottom substitution template
	'tq':'',//Talk quote inline template or its most common redirect
	'citemap':'',//local JSON with template map and parameter map for citation templates
	'lockicon':'',//is Twemoji2 1f512.svg available on this wiki?
	'notification-timestamp-ago-seconds':'{{PLURAL:$1|$1s}}',
	'notification-timestamp-ago-minutes':'{{PLURAL:$1|$1m}}',
	'notification-timestamp-ago-hours':'{{PLURAL:$1|$1h}}',
	'notification-timestamp-ago-days':'{{PLURAL:$1|$1d}}',
	'editingold':'<strong>You are editing an old revision of this page.</strong> If you save it, changes that are newer than this revision will be lost.',
	'history_small':'history',
	'minoreditletter':'m',
	'tooltip-minoredit':'This is a minor edit',
	'ipbemailban':'Sending email',
	'ipb-disableusertalk':'Editing their own talk page',
	'log-action-filter-block-reblock':'Block modification',
	'unblock':'Unblock user',
	'block':'Block user',
	'ipboptions':'2 hours:2 hours,1 day:1 day,3 days:3 days,1 week:1 week,2 weeks:2 weeks,1 month:1 month,3 months:3 months,6 months:6 months,1 year:1 year,indefinite:infinite',
	'ipbother':'Other time:',//for non-English the translation includes a note sometimes that the entered value must be in English
	'TZ':'UTC',
	'copyrightwarning':'By submitting your input you agree to release your work under the license of this project and to comply with all applicable terms and conditions of this site. (if you are reading this the proper disclaimer failed to load)',
	'wikimedia-mobile-license-links':'<a href="https://creativecommons.org/licenses/by-sa/3.0/" title="Definition of the Creative Commons Attribution-ShareAlike License" target="_blank">CC BY-SA 3.0</a>+<a href="https://www.gnu.org/licenses/fdl.html" title="Definition of the GFDL" target="_blank">GFDL</a>',
	'wikimedia-copyright':'<a href="https://foundation.wikimedia.org/wiki/Terms_of_Use">Terms of Use</a>',//not the full msg as we only extract the ToU link from this
	'unblocked':'[[user]] has been unblocked.',
	'blockedtitle':'User is blocked',
	'English':'English',//to inform the block period must be entered in English
	'action-edit':'Edit this page',
	'seconds':'$1 seconds',
	'minutes':'$1 minutes',
	'hours':'$1 hours',
	'days':'$1 days',
	'months':'$1 months',
	'years':'$1 years',
	'ago':'$1 ago',
	'tag-filter-submit':'Filter',
	'userlogin-yourname':'Username',
	'pagelang-name':'Page',
	'emailmessage':'Message:',
	'echo-notification-markasread':'Mark as read',
	'echo-notification-markasread-tooltip':'Mark as read',
	'restriction-edit':'edit',
	'editlink':'edit',
	'thanks':'Send thanks',
	'thanks-thank':'thank',
	'thanks-thanked':'thanked',
	'thanks-button-action-completed':'You thanked $1',
	'permalink':'Permanent link',
	'timestamp':'', // five tildes
	'mg1':'','mg2':'','mg3':'','mg4':'','mg5':'','mg6':'','mg7':'','mg8':'','mg9':'','mg10':'','mg11':'','mg12':'', //Months, Genitive, not used in English
	'ma1':'','ma2':'','ma3':'','ma4':'','ma5':'','ma6':'','ma7':'','ma8':'','ma9':'','ma10':'','ma11':'','ma12':'', //Months, Abbreviated
	'preferences':'Preferences',
	'prefs-advancedsearchoptions':'Advanced options',
	'wikieditor-toolbar-tool-link-insert':'Insert link',
	'wikieditor-toolbar-tool-link-title':'Insert link',
	'wikieditor-toolbar-tool-link':'Link',
	'wikieditor-toolbar-tool-ilink-example':'Link title',
	'addsection':'+',
	'table_pager_empty':'No results',
	'confirmable-confirm':'Are you sure?',
	'mw-widgets-abandonedit':'Exit without saving?',
	'talkpagelinktext':'talk',
	'collapsible-collapse':'Collapse',
	'collapsible-expand':'Expand',
	'mw-widgets-categoryselector-add-category-placeholder':'Add a category...',
	'returnto':'Return to $1.',
	'wikieditor-toolbar-tool-replace-search':'Search for:',
	'wikieditor-toolbar-tool-replace-replace':'Replace with:',
	'wikieditor-toolbar-tool-reference-title':'Insert reference',
	'echo-displaysnippet-title':'New notification',
	'newarticle':'(new)',
	'mailerror':'Error sending mail: $1',
	'mw-widgets-mediasearch-noresults':'No results found.',
	'go':'Go',
	'newsectionsummary':'/* $1 */ new section',
	'autosumm-new':'Created page with "$1"',
	'autosumm-newblank':'Created blank page',
	'autosumm-blank':'Blanked the page',
	'autosumm-replace':'Replaced content with "$1"',
	'prot_1movedto2':'[[$1]] moved to [[$2]]',
	'editundo':'undo',
	'wikieditor-toolbar-tool-replace-button-findnext':'Find next',
	'wikieditor-toolbar-tool-replace-button-replace':'Replace',
	'wikieditor-toolbar-tool-replace-button-replaceall':'Replace all',
	'wikieditor-toolbar-tool-replace-nomatch':'Your search did not match anything.',
	'cite-wikieditor-help-page-references':'References',
	'diff':'diff',
	'listfiles_thumb':'Thumbnail',
	'version-entrypoints-header-url':'URL',
	'editing':'Editing $1',
	'creating':'Creating $1',
	'delete-confirm':'Delete "$1"',
	'wikieditor-toolbar-tool-bold':'Bold',
	'wikieditor-toolbar-tool-bold-example':'Bold text',
	'wikieditor-toolbar-tool-italic':'Italic',
	'wikieditor-toolbar-tool-italic-example':'Italic text',
	'wikieditor-toolbar-tool-superscript':'Superscript',
	'wikieditor-toolbar-tool-superscript-example':'Superscript text',
	'wikieditor-toolbar-tool-subscript':'Subscript',
	'wikieditor-toolbar-tool-subscript-example':'Subscript text',
	'wikieditor-toolbar-tool-small':'Small',
	'wikieditor-toolbar-tool-small-example':'Small text',
	'wikieditor-toolbar-tool-big':'Big',
	'wikieditor-toolbar-tool-big-example':'Big text',
	'wikieditor-toolbar-tool-nowiki':'No wiki formatting',
	'wikieditor-toolbar-tool-nowiki-example':'Insert non-formatted text here',
	'wikieditor-toolbar-help-page-format':'Formatting',
	'moredotdotdot':'More...',
	'exception-nologin':'Not logged in',
	'wikieditor-toolbar-tool-replace-invalidregex':'The regular expression you entered is invalid: $1',
	'content-model-wikitext':'wikitext',
	'watch':'Watch',
	'unwatch':'Unwatch',
};
FTT.clearGender = function(text,intG) {
	text = (text || ''); //if text is undefined, turn to empty string
	for (intG=0;intG<4;intG++) {
		text = text.replace(/\{\{GENDER:\$[0-9]\|(([^\{\}]|\{\{[^\}]+\}\})+)\}\}/,'$1');
	}
	return text;
};
//lz-string by Pieroxy[https://pieroxy.net/blog/pages/lz-string/index.html], originally licensed WTFPL, Version 2, modified by Alexis Jazz. See w:en:User:Alexis_Jazz/lz-string for details
/*
Just to be safe, here's the current license[https://github.com/pieroxy/lz-string/blob/master/LICENSE], though the package I took lz-string from is WTFPL:
MIT License
Copyright (c) 2013 pieroxy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*jshint ignore:start*/
FTT.LZString=function(){/*jshint bitwise:false,asi:true,boss:true,expr:true*/function o(o,r){if(!t[o]){t[o]={};for(var n=0;n<o.length;n++)t[o][o.charAt(n)]=n}return t[o][r]}var r=String.fromCharCode,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",t={},i={compressToBase64:function(o){if(null==o)return"";var r=i._compress(o,6,function(o){return n.charAt(o)});switch(r.length%4){default:case 0:return r;case 1:return r+"===";case 2:return r+"==";case 3:return r+"="}},decompressFromBase64:function(r){return null==r?"":""==r?null:i._decompress(r.length,32,function(e){return o(n,r.charAt(e))})},compressToUTF16:function(o){return null==o?"":i._compress(o,15,function(o){return r(o+32)})+" "},decompressFromUTF16:function(o){return null==o?"":""==o?null:i._decompress(o.length,16384,function(r){return o.charCodeAt(r)-32})},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;i<o.length;i+=1)if(u=o.charAt(i),Object.prototype.hasOwnProperty.call(s,u)||(s[u]=f++,p[u]=!0),c=a+u,Object.prototype.hasOwnProperty.call(s,c))a=c;else{if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();
/*jshint ignore:end*/
/*
Waiting for T312720 to become real.
A few details here on how this mess came to be. When looking for a compression method, the first good candidate I found was lz-string by Pieroxy.
It's incredibly small (about 1.5K after min+gzip), WTFPL and achieves fair compression. In UTF16 mode, it rivals base64 pako in characters (not bytes).
Sometimes it compresses better, sometimes worse. It's great for localStorage (always UTF16). In base64 it performs significantly worse. (counting chars)
Pako doesn't do UTF16. In theory a base64>UTF16 conversion could increase the compression ratio further for pako.
But the compression ratio isn't even the main issue. While lz-string claims to be fast, pako is faster with compression. Close to irrelevant on desktop.
But on (older) phones? Decompression with lz-string is as fast if not faster than pako though.
And the primary need for compression is with the subscriptions object (easily the biggest) when stored in account preferences.
Because account preferences are limited to 64K. (though I'll probably double it by using two prefs) And prefs only allow base64.
Second: reliability. lz-string is embedded in Factotum in full, so it's always there. The pako library is much bigger (14K after min+gzip),
so only pako's inflate module is embedded (which is still 7.5K) and mw.deflate (which may well be cached as VE uses it too) is used for compression.
mw.deflate also uses pako, at least currently. But it's more risky as there is no official counterpart to mw.deflate. mw.deflate is used to compress
HTML on the client before uploading to Wikimedia. Because unliky downloads (which can be gzipped and automatically unpacked by any current browser),
uploads are NEVER compressed, so the client has to. The counterpart to mw.deflate only exists on the SERVER side.
The risk is obvious: if mw.deflate is deprecated or suddenly starts to output data in another format, Factotum would instantly break.
And thus lz-string must stay for now as a backup. If mw.deflate breaks, user data can still be decompressed as that part is embedded.
The compression automatically switches to lz-string if the mw.deflate integrity check fails.
For the future, if mw.deflate DOES change or vanish, Factotum will need to either include pako's deflate+mw.deflate wrapper or just stick with lz-string.
But if T312720 gets resolved, the pako inflate module could be removed from Factotum so let's hope that happens before mw.deflate breaks.
Af of 30-10-2022, the Pako module has been dropped from Factotum. The base64<>numbered array conversion was unreliable.
Pako 1.x was perfect as it supported base64 input/output, but with pako 2.x this became something I no longer want to deal with. Lz-string wins this round.
*/
FTT.pack = function(data,format) {
//	if(FTT.debug){FTT.debug('deflate this:');FTT.debug(data);}
	if ( typeof data == 'object' && !Array.isArray(data) ) {
		data = JSON.stringify(data);
	} else if ( typeof data != 'string' ) {
//		if(FTT.debug){FTT.debug('can only compress strings!');} //if you try to deflate an array or object using mw.deflate it says "strm.input.subarray is not a function"
		FTT.badThing = data;
		throw 'FTT: tried to compress some '+typeof data+', it\'s in FTT.badThing now';
	}
	if ( format == 'UTF16' ) {
		return 'lzsdeflU16,'+FTT.LZString.compressToUTF16(data); //UTF16 is more efficient for localStorage but doesn't work for account prefs
	} else {
		return 'lzsdeflB64,'+FTT.LZString.compressToBase64(data);
	}
};
FTT.unpack = function(data) {
	FTT.packMethod = data.slice(0,11);
	if ( FTT.packMethod.slice(0,1) == '{' && data.slice(-1) == '}' ) { //likely not compressed
		return data;
	} else if ( ['rawdeflate,','zlib_plain,'].indexOf(FTT.packMethod) != -1) { //todo: remove this (removed Pako 30-10)
		return '';
	} else if ( FTT.packMethod == 'lzsdeflU16,' ) {
		return FTT.LZString.decompressFromUTF16(data.slice(11));
	} else if ( FTT.packMethod == 'lzsdeflB64,' ) {
		return FTT.LZString.decompressFromBase64(data.slice(11));
	} else {
		return data;
	}
};
FTT.deflate = FTT.pack;
FTT.inflate = FTT.unpack;
//CRC32 checksum generator. Based on https://simplycalc.com/crc32-source.php ("This source code is in the public domain. You may use, share, modify it freely, without any conditions or restrictions."). Compacted a bit.
//FTT.CRC32('test'); //should return d87f7e0c (3632233996)
FTT.genCRC32t = function(t,i,j,n) {/*jshint bitwise:false*/
	for (i = 0; i < 256; i++) { //table will have 256 entries
		n = i;
		for (j = 8; j > 0; j--) {
			if ((n & 1) == 1) {
				n = (n >>> 1) ^ 0xEDB88320; //0xEDB88320 is the reversed polynomial for CRC32, see https://en.wikipedia.org/wiki/Cyclic_redundancy_check
			} else {
				n = n >>> 1;
			}
		}
		t[i] = n;
	}
	return t;
};
FTT.CRC32 = function(s,i,c) {/*jshint bitwise:false*/
//	FTT.debug('create CRC32 checksum');
	if ( ! FTT.CRC32t ) { //CRC32 table hasn't been generated yet
		FTT.CRC32t = FTT.genCRC32t([]); //passes empty array as first argument
	}
	c = 0xFFFFFFFF;
	for (i = 0; i < s.length; i++){ //iterate through input string
		c = (c >>> 8) ^ FTT.CRC32t[(s.charCodeAt(i)) ^ (c & 255)];
	}
	return ((c > -1) ? (0xFFFFFFFF - c) : c -c-c-1).toString(16);
};
FTT.testValidJSON = function (string) {
	if ( string == null ) {
//		if(FTT.debug){FTT.debug('you didn\'t think null would be a valid JSON, did you?');}
		return false;
	}
	try {FTT.testJSON = JSON.parse(FTT.unpack(string));} catch (e) {return false;}
	return FTT.testJSON;
};
FTT.setItemLS = function(key,val,int) {
	if ( ! mw.storage.set(key,val) ) {
//		if(FTT.debug){FTT.debug('setItemLS: failed to set '+key+', will try to dump MediaWikiModuleStore');}
		if ( typeof window.localStorage == 'object' ) {
			for (int=0;int<Object.keys(window.localStorage).length;int++){
				if ( Object.keys(window.localStorage)[int].match(/^MediaWikiModuleStore/) && window.localStorage[Object.keys(window.localStorage)[int]].length > 5000 ) {
//					if(FTT.debug){FTT.debug('removing '+Object.keys(window.localStorage)[int]+' to free up space as it doesn\'t contain any personal data');}
					FTT.rmItemLS(Object.keys(window.localStorage)[int],'local');
				}
			}
		}
		if ( ! mw.storage.set(key,val) ) { //try setting it again, if this still fails, fall back to the much more volatile sessionStorage
//			if(FTT.debug){FTT.debug('localStorage still failing, store '+key+' in sessionStorage instead (better than nothing I guess)');}
			try{window.sessionStorage.setItem(key,val);} catch (e) {}
		} else {
//			if(FTT.debug){FTT.debug('localStorage no longer failing, saved '+key+' to localStorage, removing from sessionStorage if it exists there as well');}
			FTT.rmItemLS(key,'session');
		}
	} else {
//		if(FTT.debug){FTT.debug('saved '+key+' to localStorage, removing from sessionStorage if it exists there as well');}
		FTT.rmItemLS(key,'session');
	}
};
FTT.getItemLS = function(key,quiet) {
	try{FTT.itemInSessionStor = window.sessionStorage.getItem(key);} catch (e) {} // sessionStorage is used as a fallback for localStorage so if it exists it's probably more recent
	if ( FTT.itemInSessionStor != null ) {
//		if(FTT.debug && ! quiet){FTT.debug('found '+key+' in sessionStorage');}
		return FTT.itemInSessionStor;
	} else {
		FTT.itemInLS = mw.storage.get(key);
		if ( FTT.itemInLS != null ) {
//			if(FTT.debug){FTT.debug('found '+key+' in localStorage');}
			return FTT.itemInLS;
		} else {
//			if(FTT.debug){FTT.debug(key+' not found in sessionStorage either, sorry');}
			return null;
		}
	}
};
FTT.rmItemLS = function(key,type) {
	if ( ! type || type == 'local' ) {
//		if(FTT.debug){FTT.debug('removing '+key+' from localStorage');}
		mw.storage.remove(key);
	}
	if ( ! type || type == 'session' ) {
//		if(FTT.debug){FTT.debug('removing '+key+' from sessionStorage');}
		try{window.sessionStorage.removeItem(key);} catch (e) {}
	}
};
FTT.setCollapHints = function(){
	mw.util.addCSS('#mw-content-text .FTTH2SectContainer:not(.collapsedSection) .FTTSVGChevronIcon:not(.FTTSVGChevronIconRot) .FTTScreenReaderLabel:before{content:\''+(FTT.B1['collapsible-collapse'] || 'Collapse')+'\'} #mw-content-text .FTTSVGChevronIconRot .FTTScreenReaderLabel:before{content:\''+(FTT.B1['collapsible-expand'] || 'Expand')+'\'}');
};
FTT.B1 = FTT.B1Obj.mul; //just in case everything goes to shit
FTT.basicmsgs = FTT.B1;
FTT.B1LocalStor = FTT.testValidJSON(FTT.getItemLS('FTTBasicLang'));
if ( typeof window.FTTBasicmsgsObj != 'undefined' && window.FTTBasicmsgsObj[ FTT.userLang ] ) { // FTTBasicmsgsObj could be specified in common.js before FTT is loaded
	FTT.B1 = Object.assign(FTT.B1Obj.mul, window.FTTBasicmsgsObj[ FTT.userLang ]);
	FTT.basicmsgs = FTT.B1;
	FTT.setCollapHints();
	FTT.wikiTimezone = FTT.B1.TZ;
} else if ( FTT.B1LocalStor && FTT.B1LocalStor[FTT.userLang] && FTT.B1LocalStor[FTT.userLang].version == FTT.VERSIONDATE && ( !FTT.projectIsSULWiki || FTT.B1LocalStor[FTT.userLang].WikiDataValidated == '1' ) ) {
	FTT.B1 = Object.assign(FTT.B1Obj.mul, FTT.B1LocalStor[FTT.userLang]);
	FTT.basicmsgs = FTT.B1;
	FTT.setCollapHints();
	FTT.wikiTimezone = FTT.B1.TZ;
} else {
	FTT.basicLangKeys = ['#timel:e','#special:Contributions','#special:Permalink','#special:Diff','#special:PageHistory','#language:en','#expr:2/2','#ifexist:Template:truenewline|truenewline','#ifexist:Template:smiley|smiley','#ifexist:Project:RETF|Project:RETF','#ifexist:Template:atops|atops','#ifexist:Template:abots|abots','#ifexist:Template:tq|tq','#ifexist:Media:Twemoji2 1f512.svg|Twemoji2 1f512.svg','{|~~~~~}','#special:EditPage','MediaWiki:revid','MediaWiki:jan','MediaWiki:feb','MediaWiki:mar','MediaWiki:apr','MediaWiki:may','MediaWiki:jun','MediaWiki:jul','MediaWiki:aug','MediaWiki:sep','MediaWiki:oct','MediaWiki:nov','MediaWiki:dec','MediaWiki:january-gen','MediaWiki:february-gen','MediaWiki:march-gen','MediaWiki:april-gen','MediaWiki:may-gen','MediaWiki:june-gen','MediaWiki:july-gen','MediaWiki:august-gen','MediaWiki:september-gen','MediaWiki:october-gen','MediaWiki:november-gen','MediaWiki:december-gen','#tag:nowiki|{{MediaWiki:newsectionsummary}}','#tag:nowiki|{{MediaWiki:autosumm-new}}','#tag:nowiki|{{MediaWiki:autosumm-newblank}}','#tag:nowiki|{{MediaWiki:autosumm-blank}}','#tag:nowiki|{{MediaWiki:autosumm-replace}}','#tag:nowiki|{{MediaWiki:prot_1movedto2}}','#tag:nowiki|{{MediaWiki:editing}}','#tag:nowiki|{{MediaWiki:creating}}','#tag:nowiki|{{MediaWiki:delete-confirm}}','htmlform-submit','preview','showdiff','cancel','saveprefs','restoreprefs','mw-widgets-copytextlayout-copy','mw-widgets-copytextlayout-copy-success','mw-widgets-copytextlayout-copy-fail','tooltip-summary','actionfailed','diff-empty','anoneditwarning','subject','newsection','thanks-desc','flow-thanks-confirmation-special','thanks-submit','editsectionhint','tooltip-ca-addsection','notification-timestamp-ago-seconds','notification-timestamp-ago-minutes','notification-timestamp-ago-hours','notification-timestamp-ago-days','editingold','history_small','minoreditletter','tooltip-minoredit','actioncomplete','ipbemailban','ipb-disableusertalk','log-action-filter-block-reblock','unblock','block','blockedtitle','unblocked','ipboptions','ipbother','checkbox-none','wikimedia-mobile-license-links','wikimedia-copyright','MediaWiki:copyrightwarning','action-edit','seconds','minutes','hours','days','months','years','ago','tag-filter-submit','userlogin-yourname','pagelang-name','emailmessage','echo-notification-markasread','echo-notification-markasread-tooltip','restriction-edit','editlink','thanks','thanks-thank','thanks-thanked','thanks-button-action-completed','permalink','wikieditor-toolbar-tool-bold','wikieditor-toolbar-tool-italic','preferences','prefs-advancedsearchoptions','wikieditor-toolbar-tool-link-insert','wikieditor-toolbar-tool-link-title','wikieditor-toolbar-tool-link','wikieditor-toolbar-tool-ilink-example','addsection','table_pager_empty','confirmable-confirm','mw-widgets-abandonedit','talkpagelinktext','collapsible-collapse','collapsible-expand','mw-widgets-categoryselector-add-category-placeholder','returnto','wikieditor-toolbar-tool-replace-search','wikieditor-toolbar-tool-replace-replace','wikieditor-toolbar-tool-reference-title','echo-displaysnippet-title','newarticle','mailerror','mw-widgets-mediasearch-noresults','go','editundo','wikieditor-toolbar-tool-replace-button-findnext','wikieditor-toolbar-tool-replace-button-replace','wikieditor-toolbar-tool-replace-button-replaceall','wikieditor-toolbar-tool-replace-nomatch','cite-wikieditor-help-page-references','diff','listfiles_thumb','version-entrypoints-header-url','wikieditor-toolbar-tool-superscript','wikieditor-toolbar-tool-superscript-example','wikieditor-toolbar-tool-subscript','wikieditor-toolbar-tool-subscript-example','wikieditor-toolbar-tool-small','wikieditor-toolbar-tool-small-example','wikieditor-toolbar-tool-big','wikieditor-toolbar-tool-big-example','wikieditor-toolbar-tool-nowiki','wikieditor-toolbar-tool-nowiki-example','wikieditor-toolbar-help-page-format','moredotdotdot','exception-nologin','wikieditor-toolbar-tool-replace-invalidregex','content-model-wikitext','watch','unwatch'];
	FTT.B1WikiText = '';
	for (FTT.basicLangKeysInt=0;FTT.basicLangKeysInt<FTT.basicLangKeys.length;FTT.basicLangKeysInt++){
		if ( FTT.basicLangKeys[FTT.basicLangKeysInt].match(/^[a-z\-_]*$/) ) {
			FTT.B1WikiTextPre = 'int:';
		} else {
			FTT.B1WikiTextPre = '';
		}
		FTT.B1WikiText = FTT.B1WikiText + '<div class="FTTTRANS">{{' + FTT.B1WikiTextPre + FTT.basicLangKeys[FTT.basicLangKeysInt] + '}}</div>';
	}
	FTT.B1WikiText = FTT.B1WikiText + '<div class="FTTTRANS"></div>';
	mw.storage.remove('FTTCitemap'); //dump cached citemap when FTT is updated, so there's a way to get the citemap reloaded if it's altered
	api.post( {format: 'json', contentmodel:'wikitext', action: 'parse', pst:1, disablelimitreport: true, uselang: FTT.userLang, text: FTT.B1WikiText} ).then( function ( data,int ) {
		FTT.B1ArrayRegExp = new RegExp('(?:<div class="FTTTRANS">)(([^<]|<(?!\/div><div class="FTTTRANS">))*)(?:<\/div>)', 'g');
		FTT.B1Array = data.parse.text['*'].match(FTT.B1ArrayRegExp);
		if ( FTT.B1Array ) {
			FTT.B1Obj[FTT.userLang] = {};
			FTT.B1ObjKeys = {0:'TZ',5:'English',6:'PF',7:'newline',8:'smiley',9:'AWBtyposTitle',10:'atops',11:'abots',12:'tq',13:'lockicon',14:'timestamp','thanks-desc':'thankLink'};
			FTT.B1MonthAbbr = ['','jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'];
			FTT.B1MonthGen = ['','january-gen','february-gen','march-gen','april-gen','may-gen','june-gen','july-gen','august-gen','september-gen','october-gen','november-gen','december-gen'];
			for(int=0;int<FTT.basicLangKeys.length;int++){
				FTT.BasicNewName = FTT.basicLangKeys[int].replace(/^#special:/,'').replace(/^#tag:nowiki\|\{\{MediaWiki:(.*)\}\}$/,'$1').replace(/^MediaWiki:([A-Za-z0-9\-]+)$/,'$1');
				if ( FTT.B1MonthAbbr.indexOf(FTT.BasicNewName) != -1 ) {
					FTT.B1ObjKeys[FTT.basicLangKeys[int]] = 'ma'+FTT.B1MonthAbbr.indexOf(FTT.BasicNewName);
				} else if ( FTT.B1MonthGen.indexOf(FTT.BasicNewName) != -1 ) {
					FTT.B1ObjKeys[FTT.basicLangKeys[int]] = 'mg'+FTT.B1MonthGen.indexOf(FTT.BasicNewName);
				} else if ( FTT.BasicNewName != FTT.basicLangKeys[int] ) {
					FTT.B1ObjKeys[FTT.basicLangKeys[int]] = FTT.BasicNewName;
				}
			}
			for (FTT.basicLangKeysInt=0;FTT.basicLangKeysInt<FTT.basicLangKeys.length;FTT.basicLangKeysInt++){
				if ( FTT.B1ObjKeys[FTT.basicLangKeysInt] ) {
					FTT.B1ObjKey = FTT.B1ObjKeys[FTT.basicLangKeysInt];
				} else if ( FTT.B1ObjKeys[FTT.basicLangKeys[FTT.basicLangKeysInt]] ) {
					FTT.B1ObjKey = FTT.B1ObjKeys[FTT.basicLangKeys[FTT.basicLangKeysInt]];
				} else if ( FTT.basicLangKeys[FTT.basicLangKeysInt].match(/^[a-z\-_]*$/) ) {
					FTT.B1ObjKey = FTT.basicLangKeys[FTT.basicLangKeysInt];
				}
				if ( ! FTT.B1Array[FTT.basicLangKeysInt].match('⧼') ) { //if it does match ⧼ it's probably a message that doesn't exist on this MediaWiki installation, it should fallback to the message in FTT.B1Obj.mul
					FTT.B1Obj[FTT.userLang][FTT.B1ObjKey] = FTT.B1Array[FTT.basicLangKeysInt].replace(FTT.B1ArrayRegExp,'$1');
				}
				if ( FTT.B1ObjKey == 'revid' ) {
					FTT.B1Obj[FTT.userLang][FTT.B1ObjKey] = FTT.B1Obj[FTT.userLang][FTT.B1ObjKey].replace('$1','REVID');
				} else if ( FTT.B1ObjKey == 'anoneditwarning' ) {
					FTT.B1Obj[FTT.userLang][FTT.B1ObjKey] = FTT.B1Obj[FTT.userLang][FTT.B1ObjKey].replace(/\[\$1 ([^\]]*)\]/,'<a href="' + M1('wgArticlePath').replace('$1', 'Special:UserLogin') + '?returnto=' + D1(M1('wgPageName')) + '">$1</a>').replace(/\[\$2 ([^\]]*)\]/, '<a href="' + M1('wgArticlePath').replace('$1', 'Special:CreateAccount') + '?returnto=' + D1(M1('wgPageName')) + '">$1</a>');
				} else if ( FTT.B1ObjKey == 'subject' ) {
					FTT.B1Obj[FTT.userLang][FTT.B1ObjKey] = FTT.B1Obj[FTT.userLang][FTT.B1ObjKey].replace(/(\&\#160;|:)/g,'');
				} else if ( FTT.B1ObjKey == 'timestamp' ) { //for urwiki/urwikiquote where UTC is a wikilink WTF! (I wonder where that's configured?)
					FTT.B1Obj[FTT.userLang][FTT.B1ObjKey] = FTT.B1Obj[FTT.userLang][FTT.B1ObjKey].replace(/<a href.*/,'UTC)');
				}
			}
			FTT.B1Obj[FTT.userLang].difflinkname = FTT.wikiMsgs.difflinkname.replace('DIFF',FTT.B1Obj[FTT.userLang].Diff.replace(/.*:/,''));
			FTT.B1Obj[FTT.userLang].difflinknamebare = FTT.wikiMsgs.difflinknamebare.replace('DIFF',FTT.B1Obj[FTT.userLang].Diff.replace(/.*:/,''));
			FTT.B1Obj[FTT.userLang].difflinknameprevnext = FTT.wikiMsgs.difflinknameprevnext.replace('DIFF',FTT.B1Obj[FTT.userLang].Diff.replace(/.*:/,''));
			FTT.B1Obj[FTT.userLang].pageHistoryLinkName = FTT.wikiMsgs.pageHistoryLinkName.replace('HISTORY',FTT.B1Obj[FTT.userLang].history_small);
			FTT.B1Obj[FTT.userLang].revid = FTT.B1Obj[FTT.userLang].revid.replace('$1','$7');
			FTT.B1Obj[FTT.userLang]['thanks-thank'] = FTT.clearGender(FTT.B1Obj[FTT.userLang]['thanks-thank']).replace(/.*diolch.*/,'diolch'); //one of these should be enough, but not sure. cy translation seems a bit messy
			if ( FTT.B1Obj[FTT.userLang]['thanks-thanked'] ) { //check to avoid error on wikis without thanks extension
				FTT.B1Obj[FTT.userLang]['thanks-thanked'] = FTT.B1Obj[FTT.userLang]['thanks-thanked'].replace(/\$2/,'');
			}
			FTT.B1Obj[FTT.userLang]['thanks-button-action-completed'] = FTT.clearGender(FTT.B1Obj[FTT.userLang]['thanks-button-action-completed']);
			FTT.B1Obj[FTT.userLang]['confirmable-confirm'] = FTT.clearGender(FTT.B1Obj[FTT.userLang]['confirmable-confirm']);
			for(FTT.kyMonthInt=1;FTT.kyMonthInt<13;FTT.kyMonthInt++){
				FTT.B1Obj[FTT.userLang]['mg'+FTT.kyMonthInt] = FTT.B1Obj[FTT.userLang]['mg'+FTT.kyMonthInt].replace(/ \(.*/,'');
			}
			FTT.B1Obj[FTT.userLang].version = FTT.VERSIONDATE;
			FTT.localBold = {'de':'F','sv':'F','fr':'G','it':'G','es':'N','pt':'N','ru':'ж','uk':'ж'}; //ja, pl, vi, zh use B/I
			FTT.localItalic = {'de':'K','es':'K','sv':'K','it':'C','ru':'к','uk':'к'};
			if ( FTT.localBold[FTT.userLang] ) {
				FTT.B1Obj[FTT.userLang].bold = FTT.localBold[FTT.userLang];
			}
			if ( FTT.localItalic[FTT.userLang] ) {
				FTT.B1Obj[FTT.userLang].italic = FTT.localItalic[FTT.userLang];
			}
			FTT.basicLangString = {};
			if ( typeof FTT.B1LocalStor == 'object' ) {
				FTT.basicLangString = FTT.B1LocalStor; //in case you have other languages already in our localStorage, we don't want them to get lost. Unless it has already exceeded 100K (you'd need to load a site in dozens of languages to achieve that) in which case bye-bye
			}
			if ( FTT.getItemLS('FTTBasicLang') && FTT.getItemLS('FTTBasicLang').length > 100000 ) { //how many translations did you load exactly? Just English is like 2275..
				FTT.rmItemLS('FTTBasicLang');
			}
			if ( FTT.B1Obj[FTT.userLang].PF != '1' ) {
//				FTT.debug('ParserFunctions is not available on your wiki. Some of the parsed output is garbage..');
				FTT.B1Obj[FTT.userLang].PF = '0';
				FTT.B1Obj[FTT.userLang].newline = '';
				FTT.B1Obj[FTT.userLang].smiley = '';
				FTT.B1Obj[FTT.userLang].AWBtyposTitle = '';
				FTT.B1Obj[FTT.userLang].atops = '';
				FTT.B1Obj[FTT.userLang].abots = '';
				FTT.B1Obj[FTT.userLang].tq = '';
				FTT.B1Obj[FTT.userLang].lockicon = '';
				FTT.dbg('get wiki timezone from API');
				api.get( {
					format: 'json', action: 'query', meta: 'siteinfo', siprop: 'general'
				} ).done( function ( data ) {
					FTT.TZObj = JSON.stringify({'version':FTT.versionDate,'timezone':data.query.general.timezone});
					FTT.B1Obj[FTT.userLang].TZ = data.query.general.timezone;
					FTT.basicLangString[FTT.userLang] = FTT.B1Obj[FTT.userLang];
					FTT.basicLangStringified = JSON.stringify(FTT.basicLangString);
					FTT.B1 = Object.assign(FTT.B1Obj.mul, FTT.B1Obj[FTT.userLang]);
					FTT.basicmsgs = FTT.B1;
					FTT.setCollapHints();
					FTT.setItemLS('FTTBasicLang',FTT.pack(FTT.basicLangStringified,'UTF16')); //slower compression of lz-string is no problem here as language objects only need to be compressed when FTT changes
				} );
			}
			FTT.basicLangString[FTT.userLang] = FTT.B1Obj[FTT.userLang];
			FTT.basicLangStringified = JSON.stringify(FTT.basicLangString);
			FTT.setItemLS('FTTBasicLang',FTT.pack(FTT.basicLangStringified,'UTF16'));
			FTT.B1 = Object.assign(FTT.B1Obj.mul, FTT.B1Obj[FTT.userLang]);
			FTT.basicmsgs = FTT.B1;
			FTT.setCollapHints();
			FTT.wikiTimezone = FTT.B1.TZ;
			if ( FTT.projectIsSULWiki && FTT.projectIsSULWiki[0] == '.wmflabs.org' ) {
				FTT.wikiDataApi = 'https://wikidata.beta.wmflabs.org/w/api.php';
				FTT.wikiDataRETF = '';
				FTT.wikiDataNewLine = 'Q609020';
				FTT.wikiDataSmiley = '';
				FTT.wikiDataAtops = '';
				FTT.wikiDataAbots = '';
				FTT.wikiDataCitemap = '';
				FTT.wikiDataTq = '';
				FTT.wikiDataTqRedir = '';
				FTT.wikiDataIDs = [FTT.wikiDataNewLine];
			} else {
				FTT.wikiDataApi = 'https://www.wikidata.org/w/api.php';
				FTT.wikiDataRETF = 'Q6585066';
				FTT.wikiDataNewLine = 'Q111996190';
				FTT.wikiDataSmiley = 'Q6021960';
				FTT.wikiDataAtops = 'Q112108086';
				FTT.wikiDataAbots = 'Q112108089';
				FTT.wikiDataCitemap = 'Q112131235';
				FTT.wikiDataTq = 'Q11394737';
				FTT.wikiDataTqRedir = 'Q112199474';
				FTT.wikiDataIDs = [FTT.wikiDataRETF,FTT.wikiDataNewLine,FTT.wikiDataSmiley,FTT.wikiDataAtops,FTT.wikiDataAbots,FTT.wikiDataCitemap,FTT.wikiDataTq,FTT.wikiDataTqRedir];
			}
			if ( FTT.projectIsSULWiki ) {
//				FTT.debug('you are on a Wikimedia SUL wiki, get names for some templates FTT can use');
				mw.loader.using(['mediawiki.ForeignApi'], function(){
					var apiWikidata = new mw.ForeignApi(FTT.wikiDataApi,{anonymous:true});
					apiWikidata.get({format:'json',action:'wbgetentities',props:'sitelinks',ids:FTT.wikiDataIDs}).then(function(data){
//						FTT.debug('got template names from Wikidata');
						if ( data.entities[FTT.wikiDataRETF] && data.entities[FTT.wikiDataRETF].sitelinks[M1('wgWikiID')] ) {
							FTT.B1Obj[FTT.userLang].AWBtyposTitle = data.entities[FTT.wikiDataRETF].sitelinks[M1('wgWikiID')].title;
						}
						if ( data.entities[FTT.wikiDataNewLine] && data.entities[FTT.wikiDataNewLine].sitelinks[M1('wgWikiID')] ) {
							FTT.B1Obj[FTT.userLang].newline = data.entities[FTT.wikiDataNewLine].sitelinks[M1('wgWikiID')].title.replace(/[^:]*:/,'');
						}
						if ( data.entities[FTT.wikiDataSmiley] && data.entities[FTT.wikiDataSmiley].sitelinks[M1('wgWikiID')] ) {
							FTT.B1Obj[FTT.userLang].smiley = data.entities[FTT.wikiDataSmiley].sitelinks[M1('wgWikiID')].title.replace(/[^:]*:/,'');
						}
						if ( data.entities[FTT.wikiDataAtops] && data.entities[FTT.wikiDataAtops].sitelinks[M1('wgWikiID')] ) {
							FTT.B1Obj[FTT.userLang].atops = data.entities[FTT.wikiDataAtops].sitelinks[M1('wgWikiID')].title.replace(/[^:]*:/,'');
						}
						if ( data.entities[FTT.wikiDataAbots] && data.entities[FTT.wikiDataAbots].sitelinks[M1('wgWikiID')] ) {
							FTT.B1Obj[FTT.userLang].abots = data.entities[FTT.wikiDataAbots].sitelinks[M1('wgWikiID')].title.replace(/[^:]*:/,'');
						}
						if ( data.entities[FTT.wikiDataCitemap] && data.entities[FTT.wikiDataCitemap].sitelinks[M1('wgWikiID')] ) {
							FTT.B1Obj[FTT.userLang].citemap = data.entities[FTT.wikiDataCitemap].sitelinks[M1('wgWikiID')].title;
						}
						if ( data.entities[FTT.wikiDataTq] && data.entities[FTT.wikiDataTq].sitelinks[M1('wgWikiID')] ) {
							FTT.B1Obj[FTT.userLang].tq = data.entities[FTT.wikiDataTq].sitelinks[M1('wgWikiID')].title.replace(/[^:]*:/,'');
						}
						if ( data.entities[FTT.wikiDataTqRedir] && data.entities[FTT.wikiDataTqRedir].sitelinks[M1('wgWikiID')] ) { //don't care where tq actually is. if there's a common shortcut, use that instead
							FTT.B1Obj[FTT.userLang].tq = data.entities[FTT.wikiDataTqRedir].sitelinks[M1('wgWikiID')].title.replace(/[^:]*:/,'');
						}
						FTT.B1Obj[FTT.userLang].WikiDataValidated = '1';
						FTT.basicLangString[FTT.userLang] = FTT.B1Obj[FTT.userLang];
						FTT.basicLangStringified = JSON.stringify(FTT.basicLangString);
						FTT.B1 = Object.assign(FTT.B1Obj.mul, FTT.B1Obj[FTT.userLang]);
						FTT.basicmsgs = FTT.B1;
						FTT.setCollapHints();
						FTT.setItemLS('FTTBasicLang',FTT.pack(FTT.basicLangStringified,'UTF16'));
						if ( ! FTT.goNinja ) {
//							FTT.debug('run searchNodeContentsLoop (initial, after having obtained template info from Wikidata)');
							FTT.wikiTimezone = FTT.B1.TZ;
							FTT.getTZOffset();
							FTT.searchNodeContentsLoop();
						}
					}, function ( code, data ) { FTT.APIError(code, data, 'Possible cross-origin configuration error.');
					});
				});
			} else {
//				FTT.debug('run searchNodeContentsLoop (initial, after having loaded basicmsgs)');
				FTT.searchNodeContentsLoop(); //loading links before the translation is loaded may be unsafe, especially if the UI isn't English
			}
		}
	} );
}
if ( typeof window.FTTExtraFileLocation != "undefined" ) {
	FTT.extraFileLocation = window.FTTExtraFileLocation;
} else {
	FTT.extraFileLocation = 'User:Alexis_Jazz/Factotum-extra.js';
}
FTT.loadXtra = function() {
	FTT.msgsObjKeyName = 'msgsObj';//as this key is shared with Factotum-extra.js it needs protection from AJSJSMangler
	FTT.loadXtraLang = function(){
		FTT[FTT.msgsObjKeyName][M1('wgUserLanguage')].date = FTT.VERSIONDATE;
		FTT.msgs = Object.assign(FTT.msgsObj.mul, FTT[FTT.msgsObjKeyName][ M1('wgUserLanguage') ]);
		FTT.newLocalStorageObj = FTT.testValidJSON(FTT.getItemLS('FTTLang'));
		if ( ! FTT.newLocalStorageObj ) {
			FTT.newLocalStorageObj = {};
		}
		FTT.newLocalStorageObj[M1('wgUserLanguage')] = FTT.msgsObj[ M1('wgUserLanguage') ];
		FTT.setItemLS('FTTLang',FTT.pack(FTT.newLocalStorageObj,'UTF16'));
	};
	if ( typeof FTT.xtraJSDL == 'undefined' ) {
		FTT.xtraFileLocation = M1('wgScript') + '?title=MediaWiki:Gadget-FTT.js/Factotum-extra.js&action=raw&ctype=text/javascript';
		mw.loader.getScript(FTT.xtraFileLocation).then(
		function() {
			if ( typeof FTT[FTT.msgsObjKeyName].de == 'object' ) {
				console.log('FTT: loaded Factotum-extra.js from MediaWiki: namespace successfully');
				FTT.loadXtraLang();
			} else {
				console.log('FTT: no local copy, load Factotum-extra.js from English Wikipedia');
				mw.loader.getScript('https://en.wikipedia.org/w/index.php?title=User:Alexis_Jazz/Factotum-extra.js&action=raw&ctype=text/javascript').then(
				function() {
					if ( typeof FTT[FTT.msgsObjKeyName].de == 'object' ) {
						console.log('FTT: loaded Factotum-extra.js from enwiki userspace successfully');
						FTT.loadXtraLang();
					}
				});
			}
		}, function ( errorExtra ) {
				console.log( 'FTT: could not load Factotum-extra.js: ' + errorExtra.message );
		});
	}
	FTT.xtraJSDL = true;
};
FTT.smoothScroll = {behavior: 'smooth',block: 'center',inline: 'nearest'};
//FTT.debugMsgCount = 0;FTT.resetDbg=function(skipturnon){if(!skipturnon){FTT.settings.debug=true;if(FTT.settings.dbgLimit<1000){FTT.settings.dbgLimit = 1000;}}FTT.debugMsgCount = 0;FTT.debugMsgCountReported = false;return 'reset, '+FTT.settings.dbgLimit+' debug messages left';};
//FTT.debug=function(errortext){
//	if(FTT.settings&&FTT.settings.debug==true&&FTT.debugMsgCount<FTT.settings.dbgLimit){
//		FTT.debugMsgCount++;if(typeof errortext=="string"){
//			console.log('FTT: '+errortext);//FTT.debug
//		}else{//FTT.debug
//			console.log(errortext);//FTT.debug
//		}//FTT.debug
//	}else if(FTT.settings&&typeof FTT.debugMsgCountReported == 'undefined' && FTT.debugMsgCount==FTT.settings.dbgLimit){
//		FTT.debugMsgCountReported=true;
//		mw.notify('Exceeded ' + FTT.settings.dbgLimit + ' debug messages, see console.');//FTT.debug
//		console.log('exceeded ' + FTT.settings.dbgLimit + ' debug messages, reporting halted. Raise limit in settings and/or enter FTT.resetDbg() to reset counter.');//FTT.debug
//	}//FTT.debug
//};//FTT.debug
//FTT.bench = function(func,arg1,arg2,arg3) {//FTT.debug
//	FTT.benchStart = new Date().getTime();//FTT.debug
//	func(arg1,arg2,arg3);//FTT.debug
//	FTT.benchEnd = new Date().getTime();//FTT.debug
//	console.log('took '+(FTT.benchEnd-FTT.benchStart)+' ms');//FTT.debug
//};//FTT.debug
FTT.langLocalStor = FTT.testValidJSON(FTT.getItemLS('FTTLang'));
if ( window.FTTmsgsObj && window.FTTmsgsObj[ FTT.userLang ] ) { // FTTmsgsObj could be specified in common.js before FTT is loaded
	FTT.msgs = Object.assign(FTT.msgsObj.mul, window.FTTmsgsObj[ FTT.userLang ]);
} else if ( FTT.langLocalStor[FTT.userLang] && FTT.langLocalStor[FTT.userLang].date == FTT.VERSIONDATE ) {
	FTT.msgs = Object.assign(FTT.msgsObj.mul, FTT.langLocalStor[FTT.userLang] );
} else {
	FTT.msgs = FTT.msgsObj.mul;
	if ( FTT.translations.indexOf(FTT.userLang) != -1 ) {
		FTT.loadXtra();
	}
}
FTT.NS = M1('wgFormattedNamespaces');
//default wiki config
FTT.pingText = '[[' + FTT.NS[2] + ':PINGUSER|]], ';
FTT.pingTextInSection = '[[' + FTT.NS[2] + ':PINGUSER|]], '; //[[CMTLINK|^]] is supported, but not so sure it's sensible by default
FTT.pingTextAnon = '[[' + FTT.NS[-1] + ':Contributions/PINGUSER|PINGUSER]], ';
FTT.commentTextIndentWikiDefault = ':';
FTT.EnglishMonths = ['','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
FTT.endOfCommentLocator = '<span id="USERTIME" class="FTTCmt">INNERCONTENT</span>';
//Fair question: is there no easier way to do this? Possibly, but none that I know of. {{#time:M}} would work in some cases (like dewiki), but not on zh-min-nan for example. That would also require ParserFunctions to be available, while true on Wikimedia that may not be the case on every wiki.
FTT.getMonthNames = function(mInt) {
	if ( FTT.monthNames ) {
//		FTT.debug('already know the months');
		return;
	}
	FTT.monthNames = M1('wgMonthNames'); // months as found in signatures.. well, often anyways
	if ( ['ha'].includes(M1('wgContentLanguage')) ) {
		for(mInt=1;mInt<13;mInt++){
			FTT.monthNames[mInt] = M1('wgMonthNames')[mInt].replace(/,/g,''); //commas on hawiki wtf why
		}
	}
	try {FTT.testmg1 = FTT.B1.mg1;} catch (e) {}
	if ( ['be','be-tarask','crh','cu','cv','dsb','el','kaa','koi','kk','kv','ky','la','lbe','mdf','mrj','myv','pnt','os','ru','rue','sah','se','tg','tyv','udm','uk','xal'].includes(M1('wgContentLanguage')) ) {
//		FTT.debug('use genitive months as found in MediaWiki:january-gen etc.');
		if ( ! FTT.testmg1 || FTT.testmg1 == '' ) {
//			FTT.debug('basicmsgs not available yet');
			delete FTT.monthNames;
			return false;
		}
		FTT.monthNames = [''];
		for ( mInt=1;mInt<13;mInt++){
			FTT.monthNames.push(FTT.B1['mg'+mInt]);
		}
	}
	if ( ['an','ast','ay','bar','br','ca','cbk-zam','co','csb','da','de','eml','eo','es','ext','fo','frr','fur','fy','gsw','io','it','kl','ksh','lad','lb','li','lid','lij','lld','lmo','nah','nap','nb','nds','nds-nl','nl','no','pdc','pfl','pl','pms','qu','roa-tara','sc','scn','srn','stq','szl','tt','vec','vls','zea'].includes(M1('wgContentLanguage')) ) {
//		FTT.debug('use abbreviated months as found in MediaWiki:jan etc.');
		if ( ! FTT.testmg1 || FTT.testmg1 == '' ) {
//			FTT.debug('basicmsgs not available yet');
			delete FTT.monthNames;
			return false;
		}
		FTT.monthNames = [''];
		for ( mInt=1;mInt<13;mInt++){
			FTT.monthNames.push(FTT.B1['ma'+mInt].replace(/\./g,'')); //at least nowiki has periods..
		}
	}
	if ( ['ami','cdo','cs','fi','gn','hak','hy','ii','jbo','lzh','nan','olo','pwn','szy','tay','trv','za'].includes(M1('wgContentLanguage')) ) {
		FTT.haklt = ['','1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];
		FTT.specialMonthNames = {
			'ami':FTT.haklt,
			'cdo':['','nièng 1 nguŏk','nièng 2 nguŏk','nièng 3 nguŏk','nièng 4 nguŏk','nièng 5 nguŏk','nièng 6 nguŏk','nièng 7 nguŏk','nièng 8 nguŏk','nièng 9 nguŏk','nièng 10 nguŏk','nièng 11 nguŏk','nièng 12 nguŏk'],
			'cs':FTT.EnglishMonths,
			'gn':FTT.EnglishMonths,
			'hak':FTT.haklt,
			'ii':FTT.haklt,
			'jbo':FTT.EnglishMonths,
			'lzh':FTT.haklt,
			'nan':['','1','2','3','4','5','6','7','8','9','10','11','12'],
			'pwn':FTT.haklt,
			'szy':FTT.haklt,
			'tay':FTT.haklt,
			'trv':FTT.haklt,
			'za':FTT.haklt,
			'qqq':['','1','2','3','4','5','6','7','8','9','10','11','12'],
		};
		if ( FTT.specialMonthNames[M1('wgContentLanguage')] ) {
			FTT.monthNames = FTT.specialMonthNames[M1('wgContentLanguage')];
		}
		FTT.appendToMonthNames = { //on some wikis wgMonthNames is almost correct, but some garbage gets appended to the month in the signature.
			'fi':'ta',
			'hy':'ի',
			'olo':'ta',
			'nan':'-goe̍h',
		};
		if ( FTT.appendToMonthNames[M1('wgContentLanguage')] ) {
//			FTT.debug('appending garbage to month names');
			for(mInt=1;mInt<13;mInt++){
				FTT.monthNames[mInt] = FTT.monthNames[mInt] + FTT.appendToMonthNames[M1('wgContentLanguage')];
			}
		}
	}
	FTT.monthRegExpPrep = '';
	for (mInt = 1; mInt < 13; mInt++) {
		FTT.monthRegExpPrep = FTT.monthRegExpPrep + FTT.monthNames[mInt].replace(/\./g,'') + '|';
	}
	if ( ['sq'].includes(M1('wgContentLanguage')) ) { //sqwiki changed from capitalized to non-capitalized months at some point, this allows replying to old comments. Which DT can't
		FTT.monthNamesI = [];
		for (mInt = 1; mInt < 13; mInt++) {
			FTT.monthNamesI[mInt] = FTT.monthNames[mInt].replace(/^./,'['+FTT.monthNames[mInt].slice(0,1).toUpperCase()+FTT.monthNames[mInt].slice(0,1).toLowerCase()+']');
		}
		FTT.monthRegExpPrep = FTT.monthNamesI.join('|').replace(/\./g,'').slice(1);
	} else {
		FTT.monthRegExpPrep = FTT.monthNames.join('|').replace(/\./g,'').slice(1);
	}
	FTT.signDateRegExp = new RegExp('([0-2]?[0-9]:[0-5][0-9] |[0-3]?[0-9] |(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) |[0-9]{4} |\\([A-Z0-9\+\-]{1,9}\\)){5}'); //English
	//Largely universal regex, adjustments are made by signDateCleanerFunc and cleanTimestamp. In some ways, customizing the regex for a language would be easier than adjusting the input.
	//But the regexes get complicated real fast, and to actually convert anything a function would still be needed.
	FTT.signDateRegExpLocalMonths = new RegExp('([0-2]?[0-9][\.:][0-5][0-9][\.,]? |[0-3]?[0-9][\.,]? |(' + FTT.monthRegExpPrep + ')[\.,]? |[0-9]{4}[\.,]? |\\([A-Z0-9\+\-]{1,9}\\)){5}');
//	FTT.debug('signDateRegExp:');
//	FTT.debug(FTT.signDateRegExp);
//	FTT.debug('signDateRegExpLocalMonths:');
//	FTT.debug(FTT.signDateRegExpLocalMonths);
	/*
	/،/ = arzwiki Arabic comma
	à = frwiki, atjwiki
	kl\. = svwiki
	\([^a-zA-Z]*\) = jawiki, kowiki (matches e.g. (日) for "sunday", FTT doesn't need the day of week. May match similar stuff as well, anything within brackets that isn't a timezone would invalidate the timestamp
	ngày = viwiki (day)
	năm = viwiki (year)
	kell[o]? = fiwiki (kello=clock, used like "kello 12:00"), et (kell=clock)
	x ב x = hewiki (gets appended to months, encapsulated with X because RTL)
	ás = glwiki
	ж. = kkwiki
	gada / plkst. = lvwiki
	d[ea] = brwiki
	tme = smnwiki
	di/lis: furwiki
	*/
	FTT.cleanTimestampReplaceChar = FTT.B1.timestamp.match(/(، | [aà] | kl\. | d[ae] | \([^a-zA-Z]*\) | ngày | năm | kell[o]? | ב| ás | ж\. | (gada|plkst\.) | d\' | b\. | dii\. | tme |lis | j\. | числа, )/g); //this can only run after basicmsgs have been loaded
	return;
};
FTT.pageLoadTimeStamp = new Date().toISOString(); //to detect new comments since loading the page
FTT.editConflictRetries = 3;
FTT.defaultSettings = { //default settings
	'UILabelLoad':'TAB',
	'editLinks':true,
	'replySecLink':false,
	'nSecLink':true,
	'firstHeadingAdd':true,
	'nSecBottomLink':true,
	'secLinks':false,
	'sectionIsNewTO':true,
	'inputBoxTO':true,
	'mwuibuttonTO':true,
	'hideArchived':true,
	'hideArchivedAll':false,
	'editFullPage':true,
	'firstHeadingFull':false,
	'editFullSection':false,
	'editFullSHref':false,
	'editFullSHide':false,
	'editRefs':true,
	'dateLinksIconSection':false,
	'dateLinksIconSectExtra':false,
	'dateLinksIcon':false,
	'dateLinksIconAlt':true,
	'swapIcons':true,
	'thankLink':false,
	'scrollTop':false,
	'scrollPrev':false,
	'scrollNext':false,
	'reverseSectionOrder':false,
	'reverseCollapToC':true,
	'collapsible':false,
	'autoCollapse':false,
	'collapArticle':false,
	'collapArticleDefault':false,
	'collapArticleDefaultMF':false,
	'collapIcons':false,
	'autonum':false,
	'autonumPlain':true,
	'autonumCopy':true,
	'autonumScroll':false,
	'floatingToC':false,
	'hideToC':false,
	'discussionActivity':true,
	'discussionActivityTitleOnly':false,
	'dateLinksLocalTime':false,
	'dateLinksLocalTimeUserOptTZ':true,
	'dateLinksLocalTimeRelative':false,
	'dateLinksLocalTimeAbsolute':true,
	'dateLinksLocalTime12H':false,
	'dateLinksLocalTimeNumMonth':false,
	'dateLinksLocalTimeLongMonth':false,
	'dateLinksLocalTimeWeekday':false,
	'dateLinksLocalTimeWeekdayFull':false,
	'cureDTBlueStreak':false,
	'UILabelInterface':'TAB',
	'tosNag':true,
	'markup':true,
	'markupLink':true,
	'undoFunc':true,
	'undoShortcuts':true,
	'undoBtn':true,
	'redoBtn':false,
	'markupAbove':true,
	'barRightAbove':false,
	'pingDropDown':true,
	'pingDropDownAt':true,
	'refList':false,
	'onetimetools':true,
	'onetimetoolsSearch':true,
	'onetimetoolsArchive':false,
	'onetimetoolsMove':false,
	'onetimetoolsBlock':true,
	'onetimetoolsList':false,
	'redirAfterMove':true,
	'clearEditFullPage':true,
	'editNotice':true,
	'noticeNeverPopup':false,
	'anoneditwarn':true,
	'wrongUI':false,
	'checkNewComments':true,
	'previewAboveFull':false,
	'previewAboveOther':false,
	'previewBtns':'pbFloatF',
	'overlayThreshold':800,
	'limitWidth':false,
	'warnExit':true,
	'floatReturn':true,
	'UIfontSize':'small',
	'customSummary':false,
	'customBackground':'',
	'cancelDestructive':true,
	'UILabelEditing':'TAB',
	'livePreviewCmt':true,
	'livePreviewOther':false,
	'smartLivePreview':true,
	'aggressiveLivePreview':false,
	'spellcheck':true,
	'bracketToForm':false,
	'bracketToFormT':false,
	'bracketToFormFast':true,
	'autoPing':true,
	'quoteSelect':false,
	'AWBtypos':false,
	'AWBtypoPreview':true,
	'AWBtyposCustomTitle':'',
	'enableCI':true,
	'cI':[''], //examples: /youtu\.be(.)/youtube.com$1watch\?/, '''Keep''', {{AIV|chk}}, {{subst:AfC invite}}
	'autoPostAbove':false,
	'enableCIThatRun':true,
	'cIThatRun':[''], //regexp that get automatically executed
	'enableCIThatRunCmt':true,
	'cIThatRunCmt':[''], //regexp that get automatically executed but only on comments
	'runCIAgain':false,
	'monospace':false,
	'markdown':false,
	'bbcode':false,
	'outdent':10,
	'sumSnippet':true,
	'saveDraft':true,
	'pingText':FTT.pingText,
	'pingTextInSection':FTT.pingTextInSection,
	'neverPing':'',
	'rewritun':true,
	'rewriteOnBlur':false,
	'runRewritunAgain':false,
	'rewritunOther':'',
	'runCIOther':'',
	'AWBtyposOther':'',
	'UILabelSubscribe':'TAB',
	'stalkAddSubLinks':false,
	'stalkMaxSubsSize':64,
	'stalkAutoSub':false,
	'stalkStoreInPrefs':false,
	'markNewCmts':false, //disabled by default again. If you are logged out and your localStorage is broken everything is always new which is a bit annoying
	'stalkAddCycleBtn':false,
	'markNewCmtsSubbed':true,
	'stalkAddCycleBtnSubbed':false,
	'stalkMarkReadScroll':false,
	'stalkWatchListCmts':false,
	'stalkTackOnEcho':true,
	'stalkTackOnMail':false,
	'stalkInterval':10,
	'UILabelMobile':'TAB',
	'overlayPad':false,
	'MFmarkupAbove':true,
	'MFbarRightAbove':false,
	'MFAdj':false,
	'MFAdjeditFS':false,
	'MFAdjSumma':false,
	'MFAdjCI':false,
	'MFAdjMarkup':false,
	'MFAdjLink':false,
	'MFAdjPing':false,
	'MFAdjundoBtn':false,
	'MFAdjredoBtn':false,
	'MFAdjAdv':false,
	'MFAdjSwitch':false,
	'MFAdjEditNotice':false,
	'MFAdjDiff':false,
	'MFAdjReflist':false,
	'MFAdjscrollTop':false,
	'MFAdjscrollPrev':false,
	'MFAdjscrollNext':false,
	'MFAdjcustomBackground':false,
	'MFAdjlivePreview':false,
	'MFAdjAWBtypos':false,
	'MFAdjeditor2010':false,
	'mobileMWCollapsible':true,
	'loadMinervaD':true,
	'loadMinervaM':false,
	'ninjaMobile':false,
	'hideAdvFE':true,
	'UILabelAdvanced':'TAB',
	'showUnpopular':false,
	'showRisky':false,
	'showSuperRisky':false,
	'shortcuts':true,
	'submitShortcut':false,
	'HLreply':false,
	'HLCmtClick':false,
	'editCmtDblClick':false,
	'replyDblClick':false,
	'previewDblClick':true,
	'warnCancel':true,
	'reparseConfirm':true,
	'hideDT':true,
	'hideDTStats':true,
	'hideDTSub':true,
	'hideNewSec':true,
	'methodLocator':true,
	'methodLegacy':true,
	'extendedSigDetect':false,
	'autoDash':true,
	'useLocator':true,
	'preventDoubleHashtag':true,
	'filterDirMarks':true,
	'enableOnDiffOldId':false,
	'editTheUneditable':false,
	'theStranger':false,
	'ninjaLoader':false,
	'killswitch':true,
	'recombineNowiki':false,
	'dryRun':false,
//	'debug':false, //FTT.debug
//	'dbgLimit':3000, //FTT.debug
//	'doubleTimeout':false,//FTT.debug
	'afterPost':'parse', //link, reload, parsepage, parse, parsecmtonly
	'afterPostReload':false,
	'watchlist':'watch',
	'watchlistexpiry':'+30 days',
	'watchlistexpirynew':'same',
	'editor':'source',
	'editorSwitch':false,
	'editorSwitchSkipSource':false,
	'editorSwitchSkip2010':true,
	'editorSwitchSkipvisual':false,
	'2010wikitextDefault':false,
	'2010codepageDefault':true,
	'2010templateDefault':true,
	'2010codeMirror2023':false,
	'RLmasq':false,
	'RLmasqSect':false,
	'grayscale':false,
	'blacklist':'',
	'blacklistMain':false,
	'saveTo':'browser',
	'resetPrefsButtonBar':'BUTTONBAR',
};
if ( ! window.FTTModules ) { window.FTTModules=[]; }
window.FTTModules.push({
	'load':['afterDefaultSettings','afterSettingElement','beforeLinkLoad','beforeButtonBar'],
	'afterDefaultSettingsFunc':function(){
		FTT.defaultSettings.reminder = false; //if this module is ever separated from FTT the default should be made true
		FTT.msgsObj.mul.reminder = 'Memoria notification scheduler (will be available as a sidebar link)';
		FTT.msgsObj.mul.reminderRepeatDays = "Repeat every # days";
		FTT.msgsObj.mul.reminderIcon = 'Icon';
		FTT.msgsObj.mul.reminderSimple = 'Simple form';
		FTT.msgsObj.mul.reminderSubs = 'List of JSON pages (on meta-wiki for WMF SUL-Wikis) with reminders to subscribe to, one page per line:';
		if ( M1('wgUserLanguage') == 'de' ) {
			FTT.msgsObj.mul.reminder = 'Benachrichtigungsplaner (Memoria)';
			FTT.msgsObj.mul.reminderRepeatDays = 'Alle # Tage wiederholen';
			FTT.msgsObj.mul.reminderIcon = 'Ikone';
			FTT.msgsObj.mul.reminderSimple = 'Einfaches Formular';
		}
	},'afterSettingElementFunc':function(){
	},'beforeLinkLoadFunc':function(){
		if ( ! FTT.settings.reminder ) {
//			FTT.debug('Memoria disabled');
			return;
		}
		FTT.MD.reminder = {};
		if ( ! $('#FTTMemoriaLink')[0] ) { //prevents the link from getting duplicated by FTT.parsePageInPlace()
			FTT.MD.reminder.portletLink = mw.util.addPortletLink(
				'p-tb',
				'',
				'Memoria',
				'FTTMemoriaLink',
				'Memoria notification scheduler',
				undefined,
				'#t-specialpages'
			);
			$('#FTTMemoriaLink').on('click',function(event){event.preventDefault();FTT.MD.reminder.toggleForm();});
		}
		FTT.svgClockData = 'data:image/svg+xml,' + D1(FTT.svgHeader + 'width="720px" height="720px" viewBox="0 0 720 720"><circle cx="360" cy="360" r="320" fill="none" stroke="#333" stroke-width="60" /><circle cx="360" cy="360" r="20" fill="#333"/><rect x="340" y="100" width="40" height="260" fill="#333"/><rect transform="rotate(-60)" x="-332" y="472" width="200" height="40" fill="#333"/></svg>');
		FTT.svgClock = '<img style="height:1.75em !important" src="' + FTT.svgClockData + '">';
		if ( FTT.projectIsSULWiki ) {
			FTT.MD.reminder.prefType = 'globalpreferences';
		} else {
			FTT.MD.reminder.prefType = 'options'; //for non-SUL wikis
		}
		FTT.MD.reminder.existingMsgs = [];
		FTT.MD.reminder.data = {};
		FTT.MD.reminder.data = FTT.testValidJSON(mw.user.options.get('userjs-FTTMemoria')+(mw.user.options.get('userjs-FTTMemoria2') || '')); //load existing messages
		if ( FTT.MD.reminder.data ) { 
			for(FTT.MD.reminder.existingMsgsInt=0;FTT.MD.reminder.existingMsgsInt<Object.keys(FTT.MD.reminder.data).length;FTT.MD.reminder.existingMsgsInt++){
				FTT.MD.reminder.existingMsgs.push(FTT.MD.reminder.data[Object.keys(FTT.MD.reminder.data)[FTT.MD.reminder.existingMsgsInt]]);
			}
		}
		FTT.MD.reminder.processSubbed = function(subbedPages) {
			for(FTT.MD.reminder.subbedInt=0;FTT.MD.reminder.subbedInt<Object.keys(subbedPages.query.pages).length;FTT.MD.reminder.subbedInt++){
				FTT.MD.reminder.addedFromSubs = 0;
				if ( Object.keys(subbedPages.query.pages)[FTT.MD.reminder.subbedInt] > 0 ) { //negative numbers are missing pages etc
					FTT.MD.reminder.subbedMsgJSON = FTT.testValidJSON(subbedPages.query.pages[Object.keys(subbedPages.query.pages)[FTT.MD.reminder.subbedInt]].revisions[0].slots.main['*']);
					if ( FTT.MD.reminder.subbedMsgJSON ){
						for(FTT.MD.reminder.subbedEntryInt=0;FTT.MD.reminder.subbedEntryInt<Object.keys(FTT.MD.reminder.subbedMsgJSON).length;FTT.MD.reminder.subbedEntryInt++){
							if ( Object.keys(FTT.MD.reminder.subbedMsgJSON)[FTT.MD.reminder.subbedEntryInt].match(/^[0-9]*$/) ) {
								FTT.MD.reminder.subbedMsgDate = Number(Object.keys(FTT.MD.reminder.subbedMsgJSON)[FTT.MD.reminder.subbedEntryInt]);
								FTT.MD.reminder.subbedMsgJSONdata = FTT.MD.reminder.subbedMsgJSON[FTT.MD.reminder.subbedMsgDate];
							} else {
								FTT.MD.reminder.subbedMsgDate = 0;
							}
							if ( FTT.MD.reminder.subbedMsgDate > FTT.timestampInit ) { //ignore reminders in the past, since we don't keep track of delivered reminders due to storage limits these would get delivered again and again otherwise
								if ( typeof FTT.MD.reminder.subbedMsgJSONdata.icon == 'string' && typeof FTT.MD.reminder.subbedMsgJSONdata.link == 'string' && typeof FTT.MD.reminder.subbedMsgJSONdata.msg == 'string' && typeof FTT.MD.reminder.subbedMsgJSONdata.user == 'string' && typeof FTT.MD.reminder.subbedMsgJSONdata.repeat == 'number' && FTT.MD.reminder.subbedMsgJSONdata.icon.length < 100 && FTT.MD.reminder.subbedMsgJSONdata.link.length < 1000 && FTT.MD.reminder.subbedMsgJSONdata.msg.length < 1000 && FTT.MD.reminder.subbedMsgJSONdata.user.length < 100 && FTT.MD.reminder.subbedMsgJSONdata.repeat < 999 ) {
									if ( FTT.MD.reminder.existingMsgs.indexOf(FTT.MD.reminder.subbedMsgJSONdata.msg) == -1 && FTT.MD.reminder.addedFromSubs < 4 ) { //add no more than 3 messages per page per hourly check to prevent sudden floods
										FTT.MD.reminder.newSubbedMsgDate = FTT.MD.reminder.subbedMsgDate;
										while ( typeof FTT.MD.reminder.data[FTT.MD.reminder.subbedMsgDate] != 'undefined' ) {
											FTT.MD.reminder.newSubbedMsgDate++;
										}
										FTT.MD.reminder.data[FTT.MD.reminder.newSubbedMsgDate] = FTT.MD.reminder.subbedMsgJSONdata;
										FTT.MD.reminder.updateReminders = true;
									}
								}
							}
						}
					}
				}
			}
		};
		FTT.MD.reminder.checkSubbed = function() {
			FTT.setItemLS('FTTReminderSubsLastCheck',FTT.timestampInit);
			FTT.MD.reminder.subbed = FTT.MD.reminder.data.subbed.split(/\n/);
			FTT.MD.reminder.updateReminders = false;
			if ( FTT.projectIsSULWiki ) {
				if ( FTT.projectIsSULWiki[0] == '.wmflabs.org' ) {
					FTT.MD.reminder.metaApi = 'https://meta.wikimedia.beta.wmflabs.org/w/api.php';
				} else if ( FTT.projectIsSULWiki ) {
					FTT.MD.reminder.metaApi = 'https://meta.wikimedia.org/w/api.php';
				}
				mw.loader.using(['mediawiki.ForeignApi'], function(){
					var apiMeta = new mw.ForeignApi(FTT.MD.reminder.metaApi,{anonymous:true});
					apiMeta.post({action:'query',prop:'revisions',format:'json',titles:FTT.MD.reminder.subbed,rvlimit:1,rvprop:'content',rvslots:'*'}).then(function(data){
//						FTT.debug(data);
						FTT.MD.reminder.subbedData = data;
						FTT.MD.reminder.processSubbed(data);
						if ( FTT.MD.reminder.updateReminders ) {
							FTT.MD.reminder.newFromSubs = JSON.stringify(FTT.MD.reminder.data); //this is better not compressed. Preferences are gzipped when downloaded and not updated often enough to warrant the reduced upload size. So don't waste CPU time on compression.
							FTT.queueUpdatePref('globalpreferences','userjs-FTTMemoria',FTT.MD.reminder.newFromSubs.slice(0,65035));
							FTT.queueUpdatePref('globalpreferences','userjs-FTTMemoria2',FTT.MD.reminder.newFromSubs.slice(65035));
						}
					}, function ( code, data ) { FTT.APIError(code, data, 'Couldn\'t fetch reminder subscriptions');
					});
				});
			} else {
				api.post({action:'query',prop:'revisions',format:'json',titles:FTT.MD.reminder.subbed,rvlimit:1,rvprop:'content',rvslots:'*'}).then(function(data){
//					FTT.debug(data);
					FTT.MD.reminder.processSubbed(data);
					if ( FTT.MD.reminder.updateReminders ) {
						FTT.MD.reminder.newFromSubs = JSON.stringify(FTT.MD.reminder.data);
						FTT.queueUpdatePref('options','userjs-FTTMemoria',FTT.MD.reminder.newFromSubs.slice(0,65035));
						FTT.queueUpdatePref('options','userjs-FTTMemoria2',FTT.MD.reminder.newFromSubs.slice(65035));
					}
				}, function ( code, data ) { FTT.APIError(code, data, 'Couldn\'t fetch reminder subscriptions');
				});
			}
		}; 
		FTT.MD.reminder.data = FTT.testValidJSON(mw.user.options.get('userjs-FTTMemoria')+(mw.user.options.get('userjs-FTTMemoria2') || '')); //load reminders
		if ( ! FTT.MD.reminder.data ) { 
			FTT.MD.reminder.data = {};
		}
		FTT.MD.reminder.remindArray = [];
		for(FTT.MD.reminder.int=0;FTT.MD.reminder.int<Object.keys(FTT.MD.reminder.data).length;FTT.MD.reminder.int++){
			if ( Object.keys(FTT.MD.reminder.data)[FTT.MD.reminder.int] != 'subbed' && Number(Object.keys(FTT.MD.reminder.data)[FTT.MD.reminder.int]) < FTT.timestampInit ) {
				FTT.MD.reminder.singleData = Object.values(FTT.MD.reminder.data)[FTT.MD.reminder.int];
				FTT.MD.reminder.remindArray.push({'date':Object.keys(FTT.MD.reminder.data)[FTT.MD.reminder.int],'icon':FTT.MD.reminder.singleData.icon,'user':FTT.MD.reminder.singleData.user,'link':FTT.MD.reminder.singleData.link,'msg':FTT.MD.reminder.singleData.msg});
				if ( Number(FTT.MD.reminder.singleData.repeat) > 0 ) {
					FTT.MD.reminder.newDateForRepeat = Number(Object.keys(FTT.MD.reminder.data)[FTT.MD.reminder.int]);
					while ( FTT.MD.reminder.newDateForRepeat < FTT.timestampInit ) {
						FTT.MD.reminder.newDateForRepeat = FTT.MD.reminder.newDateForRepeat + ( Number(FTT.MD.reminder.singleData.repeat) * 86400000 );
					}
					FTT.MD.reminder.data[FTT.MD.reminder.newDateForRepeat] = FTT.MD.reminder.singleData;
				}
				delete FTT.MD.reminder.data[Object.keys(FTT.MD.reminder.data)[FTT.MD.reminder.int]];
			}
		}
		if ( FTT.MD.reminder.remindArray.length > 0 ) {
//			FTT.debug('deliver reminders to userjs-FTTTackOnEchoGlobal and remove delivered reminders from userjs-FTTMemoria (' + FTT.MD.reminder.prefType+')');
			FTT.MD.reminder.newMinusDelivered = JSON.stringify(FTT.MD.reminder.data);
			FTT.tackOnEchoWrite(FTT.MD.reminder.remindArray); //render reminders
			FTT.queueUpdatePref(FTT.MD.reminder.prefType,'userjs-FTTMemoria',FTT.MD.reminder.newMinusDelivered.slice(0,65035)); //remove rendered reminders from userjs-FTTMemoria
			FTT.queueUpdatePref(FTT.MD.reminder.prefType,'userjs-FTTMemoria2',FTT.MD.reminder.newMinusDelivered.slice(65035));
		} else if ( ( ! FTT.getItemLS('FTTReminderSubsLastCheck') || FTT.getItemLS('FTTReminderSubsLastCheck') < FTT.timestampInit-3600000 ) && FTT.MD.reminder.data.subbed && FTT.MD.reminder.data.subbed != '' ) { //easiest way to avoid possibly conflicting pref changes, either deliver a reminder OR possibly check subbed reminders
//			FTT.debug('more than 1 hour since last check for subbed reminders');
			FTT.MD.reminder.checkSubbed();
		}
		FTT.MD.reminder.toggleForm = function(){
			mw.loader.using('mediawiki.widgets.datetime',function(){
			if ( $('#FTTReminder')[0] ) {
				$('#FTTReminder').remove();
				$('#mw-content-text').removeClass('FTTNoDisplay');
				return;
			}
			FTT.MD.reminder.formDateInputs = [];
			FTT.MD.reminder.formRepeatInputs = [];
			FTT.MD.reminder.formIconInputs = [];
			FTT.MD.reminder.formUserInputs = [];
			FTT.MD.reminder.formLinkInputs = [];
			FTT.MD.reminder.formMsgInputs = [];
			FTT.MD.reminder.formHorizontalLayouts = [];
			FTT.MD.reminder.toggleSimpleForm = new OO.ui.CheckboxInputWidget( { selected:false } );
			FTT.MD.reminder.toggleSimpleFormFieldLayout = new OO.ui.FieldLayout( FTT.MD.reminder.toggleSimpleForm, { label: FTT.msgsObj.mul.reminderSimple, align: 'inline', classes: [ 'FTTOOuiFieldLayout' ] } );
			FTT.MD.reminder.toggleSimpleFormHorizontal = new OO.ui.HorizontalLayout({items:[FTT.MD.reminder.toggleSimpleFormFieldLayout]});
			FTT.MD.reminder.toggleSimpleForm.on('change',function(int){
				for ( int=0;int<FTT.MD.reminder.formIconInputs.length;int++ ) {
					FTT.MD.reminder.formIconInputs[int].toggle();
					FTT.MD.reminder.formUserInputs[int].toggle();
					FTT.MD.reminder.formRepeatInputs[int].toggle();
				}
			});
			FTT.MD.reminder.createFormEntry = function(date,repeat,icon,user,link,msg,num){
				FTT.MD.reminder.formDateInputs[num] = new mw.widgets.datetime.DateTimeInputWidget({value:new Date(date).toISOString()}); //https://www.mediawiki.org/wiki/Manual:DateTimeInputWidget / https://www.mediawiki.org/wiki/Manual:DateInputWidget
				FTT.MD.reminder.formRepeatInputs[num] = new OO.ui.NumberInputWidget({value:repeat,classes:['FTTReminderInput'],title:FTT.msgs.reminderRepeatDays});
				if ( icon == '' ) { icon = 'svgClock'; }
				FTT.MD.reminder.formIconInputs[num] = new OO.ui.ComboBoxInputWidget( {
					classes:['FTTReminderInput'],
					value:icon,
					options: [{data:'svgClock',label:'Clock'},{data:'svgFTTIconLinkBlack',label:'Link'},{data:'svgFTTIconSettings',label:'Gear'},{data:'svgMagnifier',label:'Magnifier'},{data:'svgFTTHeartIcon',label:'Heart'},{data:'svgFTTHeartRedIcon',label:'Red heart'},{data:'svgFTTIconEdit',label:'Marker'}],
					spellcheck: false,
					title:FTT.msgs.reminderIcon,
					menu: {filterFromInput: true}
				} );
				FTT.MD.reminder.formUserInputs[num] = new OO.ui.TextInputWidget({value:user,classes:['FTTReminderUserInput'],placeholder:FTT.B1['userlogin-yourname'],title:FTT.B1['userlogin-yourname']});
				FTT.MD.reminder.formLinkInputs[num] = new OO.ui.TextInputWidget({value:link,classes:['FTTReminderLinkInput'],placeholder:FTT.B1['pagelang-name'],title:FTT.B1['pagelang-name']});
				FTT.MD.reminder.formMsgInputs[num] = new OO.ui.TextInputWidget({value:msg,classes:['FTTReminderMsgInput'],placeholder:FTT.B1.emailmessage.replace(/:/,''),title:FTT.B1.emailmessage.replace(/:/,'')});
				FTT.MD.reminder.formHorizontalLayouts[num] = new OO.ui.HorizontalLayout({items:[FTT.MD.reminder.formDateInputs[num],FTT.MD.reminder.formRepeatInputs[num],FTT.MD.reminder.formIconInputs[num],FTT.MD.reminder.formUserInputs[num],FTT.MD.reminder.formLinkInputs[num],FTT.MD.reminder.formMsgInputs[num]]});
			};
			FTT.MD.reminder.saveReminders = function(){
				FTT.MD.reminder.saveButton.setDisabled(true);
				$('#FTTReminderSave').addClass('FTTPendingBlink');
				FTT.MD.reminder.newReminders = {};
				FTT.MD.reminder.newReminders.subbed = FTT.MD.reminder.reminderSubs.getValue();
				for(FTT.MD.reminder.formInt=0;FTT.MD.reminder.formInt<FTT.MD.reminder.formMsgInputs.length;FTT.MD.reminder.formInt++){
					if ( FTT.MD.reminder.formMsgInputs[FTT.MD.reminder.formInt].getValue() != '' && FTT.MD.reminder.formDateInputs[FTT.MD.reminder.formInt].getValue() != '' ) {
						FTT.MD.reminder.reminderUser = FTT.MD.reminder.formUserInputs[FTT.MD.reminder.formInt].getValue();
						if ( FTT.MD.reminder.reminderUser == '' ) {
							FTT.MD.reminder.reminderUser = M1('wgUserName');
						}
						FTT.MD.reminder.timestamp = new Date(FTT.MD.reminder.formDateInputs[FTT.MD.reminder.formInt].getValue()).getTime();
						while ( typeof FTT.MD.reminder.newReminders[FTT.MD.reminder.timestamp] == 'object' ) {
							FTT.MD.reminder.timestamp++;
						}
						FTT.MD.reminder.newReminders[FTT.MD.reminder.timestamp] = {'repeat':FTT.MD.reminder.formRepeatInputs[FTT.MD.reminder.formInt].getValue(),'icon':FTT.MD.reminder.formIconInputs[FTT.MD.reminder.formInt].getValue(),'user':FTT.MD.reminder.reminderUser,'link':FTT.MD.reminder.formLinkInputs[FTT.MD.reminder.formInt].getValue(),'msg':FTT.MD.reminder.formMsgInputs[FTT.MD.reminder.formInt].getValue()};
					}
				}
				if ( Object.keys(FTT.MD.reminder.newReminders).length == 0 ) {
					FTT.MD.reminder.newReminderString = '';
				} else {
					FTT.MD.reminder.newReminderString = JSON.stringify(FTT.MD.reminder.newReminders);
				}
				FTT.queueUpdatePref(FTT.MD.reminder.prefType,'userjs-FTTMemoria',FTT.MD.reminder.newReminderString.slice(0,65035));
				FTT.queueUpdatePref(FTT.MD.reminder.prefType,'userjs-FTTMemoria2',FTT.MD.reminder.newReminderString.slice(65035));
				var CheckPrefsUpdated = setInterval(function (int) {
					if ( Object.keys(FTT.prefQueue[FTT.MD.reminder.prefType]).length == 0 ) {
						clearInterval(CheckPrefsUpdated);
						FTT.MD.reminder.saveButton.setDisabled(false);
						FTT.MD.reminder.toggleForm();
					}
				},200);
			};
			mw.util.addCSS('#FTTReminder .mw-widgets-datetime-dateTimeInputWidget{width:unset}.FTTReminderInput a{min-height:unset !important;min-width:unset !important;}.FTTReminderInput{max-width:9em;}.FTTReminderIconInput{max-width:8em}.FTTReminderUserInput{max-width:8em}.FTTReminderLinkInput{max-width:8em}.FTTReminderMsgInput{max-width:16em});');
			if ( ! FTT.MD.reminder.data ) {
				FTT.MD.reminder.data = {}; //init empty object if we have no reminders yet
			}
//			FTT.debug('create pre-filled out entries for existing reminders');
			FTT.MD.reminder.singleFormData = {};
			for (FTT.MD.reminder.formEntriesInt=0;FTT.MD.reminder.formEntriesInt<Object.keys(FTT.MD.reminder.data).length+5;FTT.MD.reminder.formEntriesInt++){
				if ( Object.keys(FTT.MD.reminder.data)[FTT.MD.reminder.formEntriesInt] && Object.keys(FTT.MD.reminder.data)[FTT.MD.reminder.formEntriesInt] != 'subbed' ) {
					FTT.MD.reminder.singleFormData = FTT.MD.reminder.data[Object.keys(FTT.MD.reminder.data)[FTT.MD.reminder.formEntriesInt]];
					FTT.MD.reminder.createFormEntry(Number(Object.keys(FTT.MD.reminder.data)[FTT.MD.reminder.formEntriesInt]),Number(FTT.MD.reminder.singleFormData.repeat),FTT.MD.reminder.singleFormData.icon,FTT.MD.reminder.singleFormData.user,FTT.MD.reminder.singleFormData.link,FTT.MD.reminder.singleFormData.msg,FTT.MD.reminder.formEntriesInt);
				} else {
					FTT.MD.reminder.createFormEntry(new Date(new Date().getFullYear(),new Date().getMonth(),new Date().getDate(),0,0,0,0).toISOString(),0,'svgClock','','','',FTT.MD.reminder.formEntriesInt);
				}
			}
			if ( ! FTT.MD.reminder.data.subbed ) {
				FTT.MD.reminder.data.subbed = '';
			}
			FTT.MD.reminder.reminderSubs = new OO.ui.MultilineTextInputWidget( {
				id: 'FTTReminderSubs',
				autosize: true,
				value: FTT.MD.reminder.data.subbed,
				classes: [ 'FTTMarginHalfEmTop', 'FTTMarginHalfEm' ],
			} );
			FTT.MD.reminder.formHorizontalLayouts.push(FTT.MD.reminder.toggleSimpleFormHorizontal);
			FTT.MD.reminder.reminderSubsFieldLayout = new OO.ui.FieldLayout( FTT.MD.reminder.reminderSubs, { label: FTT.msgsObj.mul.reminderSubs, align: 'inline', classes: [ 'FTTOOuiFieldLayout' ] } );
			FTT.MD.reminder.formHorizontalLayouts.push(FTT.MD.reminder.reminderSubsFieldLayout);
			FTT.MD.reminder.saveButton = new OO.ui.ButtonWidget({id:'FTTReminderSave',label:FTT.B1.saveprefs,flags:['primary','progressive']});
			FTT.MD.reminder.cancelButton = new OO.ui.ButtonWidget({label:FTT.B1.cancel,flags:['primary','destructive']});
			FTT.MD.reminder.cancelButton.on('click',function(){FTT.MD.reminder.toggleForm();});
			FTT.MD.reminder.saveButton.on('click',function(){FTT.MD.reminder.saveReminders();});
			FTT.MD.reminder.formHorizontalLayouts.push(new OO.ui.HorizontalLayout({items:[FTT.MD.reminder.saveButton,FTT.MD.reminder.cancelButton]}));
			FTT.MD.reminder.FieldsetLayout = new OO.ui.FieldsetLayout({id:'FTTReminder',items:FTT.MD.reminder.formHorizontalLayouts});
			$('#mw-content-text').addClass('FTTNoDisplay');
			$('#mw-content-text')[0].parentElement.insertBefore(FTT.MD.reminder.FieldsetLayout.$element[0],$('#mw-content-text')[0]);
			FTT.MD.reminder.toggleSimpleForm.setSelected(true);
			$('#FTTReminder')[0].scrollIntoView(FTT.smoothScroll);
			}); //mediawiki.datetime
		};
	}
});
FTT.insertModuleButton = function(moduleNum,b) {
	if ( b == 'generic' ) {
		FTT.moduleButtons[moduleNum].on('click', function(){FTT.loadedModules[moduleNum].buttonFunc();});
	} else if ( b == 'selection' ) {
		FTT.moduleButtons[moduleNum].on('click', function(){FTT.applyFunctionToSelection(FTT.loadedModules[moduleNum].selectionFunc);});
	}
};
FTT.applyModules = function(a,processText){
	FTT.moduleProcessedText = processText;
	if ( ( ! FTT.loadedModules && window.FTTModules ) || ( FTT.loadedModules && window.FTTModules && FTT.loadedModules.length != window.FTTModules.length ) ) {
		FTT.loadedModules = window.FTTModules;
	}
	if ( typeof FTT.loadedModules == 'object' && FTT.loadedModules.length>0 ) {
//		FTT.debug('applyModules: modules found, looking for type '+a);
		FTT.moduleButtons = {};
		for(FTT.loadedModulesInt=0;FTT.loadedModulesInt<FTT.loadedModules.length;FTT.loadedModulesInt++){
//			if ( a != 'afterDefaultSettings' ) { FTT.debug('applyModules: check module #' + FTT.loadedModulesInt); }//debug not available yet at afterDefaultSettings
			if ( FTT.loadedModules[FTT.loadedModulesInt].load.includes(a) ) { //execute some function contained within the module
//				FTT.debug('applyModules: found module for type '+a);
//				if ( a != 'afterDefaultSettings' ) { FTT.debug('applyModules: module includes "'+a+'" loadtype'); }
				if ( typeof FTT.loadedModules[FTT.loadedModulesInt][a+'Func'] == 'function' ) {
//					if ( a != 'afterDefaultSettings' ) { FTT.debug('applyModules: run function from module with type '+a); }
					FTT.loadedModules[FTT.loadedModulesInt][a+'Func']();
				}
			}
			if ( a == 'afterOpenForm' && FTT.loadedModules[FTT.loadedModulesInt].load.includes('afterOpenForm') ) {
				if ( typeof FTT.loadedModules[FTT.loadedModulesInt].extraUI == 'object' ) {
					$('#FTTCustomInserts').removeClass('FTTNoDisplay');
					$('#FTTCustomInserts').prepend(FTT.loadedModules[FTT.loadedModulesInt].extraUI.$element);
				}
				if ( typeof FTT.loadedModules[FTT.loadedModulesInt].buttonLabel == 'string' ) {
					$('#FTTCustomInserts').removeClass('FTTNoDisplay');
					FTT.moduleButtons[FTT.loadedModulesInt] = new OO.ui.ButtonWidget( {
						label:FTT.loadedModules[FTT.loadedModulesInt].buttonLabel,
						id:FTT.loadedModules[FTT.loadedModulesInt].buttonId,
						classes:['FTTMarginHalfEm',FTT.buttonOnTheWrongSideClass,'FTTModuleButton'+FTT.loadedModules[FTT.loadedModulesInt].buttonLabel]
					} );
					if ( typeof FTT.loadedModules[FTT.loadedModulesInt].buttonFunc == 'function' ) {
//						FTT.debug('applyModules: add event to button #' + FTT.loadedModulesInt);
						FTT.insertModuleButton(FTT.loadedModulesInt,'generic');
					}
					if ( typeof FTT.loadedModules[FTT.loadedModulesInt].selectionFunc == 'function' ) {
						FTT.insertModuleButton(FTT.loadedModulesInt,'selection');
					}
					$('#FTTCustomInserts').prepend(FTT.moduleButtons[FTT.loadedModulesInt].$element);
				}
			} else if ( a == 'processComment' && FTT.loadedModules[FTT.loadedModulesInt].load.includes('processComment') && typeof FTT.loadedModules[FTT.loadedModulesInt].processCommentFunc == 'function' ) {
//				FTT.debug('applyModules: module includes "processComment" loadtype');
				FTT.moduleProcessedText = FTT.loadedModules[FTT.loadedModulesInt].processCommentFunc((FTT.moduleProcessedText||''));
				continue;
			}
		}
//	} else { FTT.debug('applyModules: no modules found');
	}
	if ( a == 'processComment' ) {
		return FTT.moduleProcessedText;
	}
};
FTT.applyModules('afterDefaultSettings');
FTT.settings = $.extend( true, {}, FTT.defaultSettings ); // copy as = only creates a shortcut
FTT.tosNagContainer = document.createElement('div');
FTT.tosNagContainer.id = 'FTTToSNag';
FTT.loadSettings = function() {
	FTT.customizedSettingsLocal = FTT.testValidJSON(FTT.getItemLS('FTT'));
	if ( FTT.customizedSettingsLocal ) {
//		FTT.debug('load settings from localStorage');
		FTT.customizedSettings = FTT.customizedSettingsLocal;
	} else {
		FTT.customizedSettingsPrefs = FTT.testValidJSON(mw.user.options.get('userjs-FTT'));
		if ( FTT.customizedSettingsPrefs ) {
//			FTT.debug('load settings from mw.user.options');
			FTT.customizedSettings = FTT.customizedSettingsPrefs;
		} else {
//			FTT.debug('no customized settings loaded');
			FTT.customizedSettings = {};
		}
	}
	//remove obsolete/renamed settings
	delete FTT.customizedSettings.MFAdjUndo;
	FTT.settings = Object.assign(FTT.settings, FTT.customizedSettings);
	if ( ! Array.isArray(FTT.settings.cI) ) { //todo: remove after a while (2022-09-14)
		FTT.settings.cI = Object.values(FTT.settings.cI);
		FTT.settings.cIThatRun = Object.values(FTT.settings.cIThatRun);
		FTT.settings.cIThatRunCmt = Object.values(FTT.settings.cIThatRunCmt);
	}
};
FTT.loadSettings();
FTT.goNinja = ( ( FTT.settings.ninjaLoader && ! FTT.isMobile ) || ( FTT.settings.ninjaMobile && FTT.isMobile ) );
if ( FTT.settings.cureDTBlueStreak ) {
	mw.util.addCSS('.ext-discussiontools-init-targetcomment{background-color:unset !important}');
}
if ( FTT.settings.cancelDestructive ) {
	FTT.cancelFlag = 'destructive';
} else {
	FTT.cancelFlag = '';
}
FTT.checkBL = function(BLint) {
	if ( FTT.settings.blacklist != '' ) {
		FTT.blacklist = FTT.settings.blacklist.split(/\n/);
		FTT.checkPageTitle = FTT.PN;
		for ( BLint=0;BLint<FTT.blacklist.length;BLint++ ) {
			if ( ( FTT.blacklist[BLint].match(/^\/.*\/[gmi]{0,3}$/) && FTT.PN.match(new RegExp(FTT.blacklist[BLint].replace(/^\/(.*)\/[gmi]{0,3}$/,'$1'),FTT.blacklist[BLint].match(/([gmi]{0,3})$/)[0])) ) || ( FTT.blacklist[BLint].length && FTT.PN.match(FTT.blacklist[BLint]) ) ) {
//				FTT.debug('page title matches "'+FTT.blacklist[BLint]+'" ('+BLint+') in blacklist');
				FTT.blacklistMatch = true;
				return;
			}
		}
	}
};
FTT.checkBL();
if ( FTT.blacklistMatch && ! window.location.hash.match(/^#f[at](t|ctotum)edit$/i) ) {
//	FTT.debug('page title matches a blacklisted title, exit FTT.run');
	return;
}
if ( FTT.settings.blacklistMain && M1('wgNamespaceNumber') == 0 ) {
//	FTT.debug('disabled FTT in mainspace');
	return;
}
FTT.reloadForm = function() {
	FTT.loadSettings();
	FTT.cancelReply(); //clear form entirely
	FTT.reloaded = true;
	FTT.openReplyForm(FTT.PRMOpened, 'reload'); //force reloading everything
};
FTT.disableForm = function(setDisableForm) {
	if ( FTT.UITextInputTitle ) {
		FTT.UITextInputTitle.setReadOnly(setDisableForm);
		FTT.UITextInput.setReadOnly(setDisableForm);
		FTT.UIReplyButton.setDisabled(setDisableForm);
		FTT.UIPreviewButton.setDisabled(setDisableForm);
		FTT.UICancelButton.setDisabled(setDisableForm);
		FTT.UIDiffButton.setDisabled(setDisableForm);
		if ( setDisableForm == false ) {
			$('#FTTUIReplyButton').removeClass('FTTPendingBlink');
		}
	}
};
FTT.getWikitextFromExport = function(text) {
	if ( text.match(/[^]*<text bytes[^>]*>([^]*)<\/text>[^]*/) ) {
		//hack for signatures in templates on enwiki, fixing them on the fly
		if ( FTT.PRMOpened.pageTitle.match(/:Requested moves/) ) {
			text = text.replace(/(\[\[[^\n\]\|]*)\|([^\n\]]*\]\])/g,'$1PIPE_UNIQ_HACK$2').replace(/(\{\{RMassist\/core.*)(\|sig=)([^\}\|]*)(.*\}\})/g,'$1$4 $3').replace(/PIPE_UNIQ_HACK/g,'|')+'\n';
		}
		return text.replace(/[^]*<text bytes[^>]*>([^]*)<\/text>[^]*/, '$1').replace(/\&lt\;/g, '<').replace(/\&gt\;/g, '>').replace(/\&amp\;/g, '&');
	} else {
		return '';
	}
};
FTT.queueUpdatePref = function(type, key, val, instanceID, int) { //stuffs prefs you want to change in localStorage (so works across tabs), if no further prefs have changed for 1.2s it saves them. If you close a tab before that in theory it should get picked up on the next page load
	//type = options/globalpreferences/globalpreferenceoverrides
	//key = key of pref to change
	//val = value to change it to
	//just call this function for every change that needs to be made, the changes will automatically be combined within one request (when made within 1.2 seconds)
//	FTT.debug('queueUpdatePref: allowing 1.2s to accumulate changes to preferences');
	instanceID = Math.random();
	FTT.queueUpdatePrefInstanceID = instanceID;
	FTT.setPrefLocal = {};
	FTT.setPrefLocal[key] = val;
	mw.user.options.set(FTT.setPrefLocal); //this changes the value in mw.user.options (non-persistent) so we don't have to reload the page to apply the settings. We update it early so if some other process reads this pref it'll get the latest version so later requests won't be based on old data
	FTT.prefQueueLS = FTT.testValidJSON(FTT.getItemLS('FTTPrefQueue'));
	FTT.prefQueue = ( FTT.prefQueueLS || FTT.prefQueue || { options:{}, globalpreferences:{}, globalpreferenceoverrides:{} } ); //First try localStorage. If LS isn't working, see if FTT.prefQueue already exists for just this page. If not, init new object
	FTT.prefQueue[type][key] = val;
	FTT.setItemLS('FTTPrefQueue',JSON.stringify(FTT.prefQueue));
	FTT.setItemLS('FTTQueueUpdatePrefInstanceID',instanceID); //to detect more recent instances of this function in other tabs
	var DelayUpdatePrefs = setInterval(function () { //allow for 1.2s to accumulate preference changes 
		clearInterval(DelayUpdatePrefs);
		if ( instanceID != ( FTT.getItemLS('FTTQueueUpdatePrefInstanceID') || FTT.queueUpdatePrefInstanceID ) ) {
//			FTT.debug('this isn\'t the latest instance ('+instanceID+') of queueUpdatePref, we\'ll wait for the new instance to handle the API request.');
			return;
		}
		FTT.prefQueueLS = FTT.testValidJSON(FTT.getItemLS('FTTPrefQueue'));
		FTT.prefQueue = ( FTT.prefQueueLS || FTT.prefQueue || { options:{}, globalpreferences:{}, globalpreferenceoverrides:{} } ); //First try localStorage. If LS isn't working, see if FTT.prefQueue already exists for just this page. If not, init new object
		FTT.prefQueueoptionsArr = [];
		for ( int=0;int<Object.keys(FTT.prefQueue.options).length;int++ ) {
			if ( Object.values(FTT.prefQueue.options)[int] == '' ) {
				FTT.prefQueueoptionsArr.push(Object.keys(FTT.prefQueue.options)[int]);
			} else {
				FTT.prefQueueoptionsArr.push(Object.keys(FTT.prefQueue.options)[int]+'='+Object.values(FTT.prefQueue.options)[int]);
			}
			if ( ! ['userjs-FTTSubs'].includes(Object.keys(FTT.prefQueue.globalpreferences)[int]) ) { //prefs that are never global
				if ( val != '' && type != 'globalpreferences' ) {
					FTT.prefQueueoptionsArr.push(Object.keys(FTT.prefQueue.options)[int]+'-local-exception=1');
				} else {
					FTT.prefQueueoptionsArr.push(Object.keys(FTT.prefQueue.options)[int]+'-local-exception');
				}
			}
		}
		FTT.prefQueueglobalpreferencesArr = [];
		for ( int=0;int<Object.keys(FTT.prefQueue.globalpreferences).length;int++ ) {
			//if ( Object.values(FTT.prefQueue.globalpreferences)[int] == '' ) { // phab:T317638
			//	FTT.prefQueueglobalpreferencesArr.push(Object.keys(FTT.prefQueue.globalpreferences)[int]);
			//} else {
				FTT.prefQueueglobalpreferencesArr.push(Object.keys(FTT.prefQueue.globalpreferences)[int]+'='+Object.values(FTT.prefQueue.globalpreferences)[int]);
			//}
			if ( mw.user.options.get(Object.keys(FTT.prefQueue.globalpreferences)[int]+'-local-exception') ) {
				FTT.prefQueueoptionsArr.push(Object.keys(FTT.prefQueue.globalpreferences)[int]+'-local-exception'); //remove local exception when setting global preference
			}
		}
		FTT.prefQueueglobalpreferenceoverridesArr = [];
		for ( int=0;int<Object.keys(FTT.prefQueue.globalpreferenceoverrides).length;int++ ) {
			//if ( Object.values(FTT.prefQueue.globalpreferenceoverrides)[int] == '' ) { // phab:T317638
			//	FTT.prefQueueglobalpreferenceoverridesArr.push(Object.keys(FTT.prefQueue.globalpreferenceoverrides)[int]);
			//	FTT.prefQueueglobalpreferenceoverridesArr.push(Object.keys(FTT.prefQueue.globalpreferenceoverrides)[int]+'-local-exception');
			//} else {
				FTT.prefQueueglobalpreferenceoverridesArr.push(Object.keys(FTT.prefQueue.globalpreferenceoverrides)[int]+'='+Object.values(FTT.prefQueue.globalpreferenceoverrides)[int]);
			//}
		}
		if ( FTT.prefQueueoptionsArr.length > 0 ) {
			FTT.updatePref('options',FTT.prefQueueoptionsArr);
		}
		if ( FTT.prefQueueglobalpreferencesArr.length > 0 ) {
			FTT.updatePref('globalpreferences',FTT.prefQueueglobalpreferencesArr);
		}
		if ( FTT.prefQueueglobalpreferenceoverridesArr.length > 0 ) {
			FTT.updatePref('globalpreferenceoverrides',FTT.prefQueueglobalpreferenceoverridesArr);
		}
	},1200);
};
FTT.updatePrefInProgress = {};
FTT.updatePref = function(type, change) {
	if ( FTT.updatePrefInProgress[type] == true ) {
		var updatePrefConflict = setInterval(function (int) {
//			FTT.debug('updatePref: there is an ongoing pref change in progress, wait for it to complete');
			if ( FTT.updatePrefInProgress[type] == false ) {
//				FTT.debug('updatePref: ongoing pref change just completed');
				clearInterval(updatePrefConflict);
				FTT.updatePref(type, change);
			}
		},200);
		return;
	}
	FTT.updatePrefInProgress[type] = true;
	FTT.prefQueueLSUpdate = FTT.testValidJSON(FTT.getItemLS('FTTPrefQueue'));
	if ( FTT.prefQueueLSUpdate ) {
		FTT.prefQueueLSBackup = FTT.prefQueueLSUpdate[type];
		FTT.prefQueueLSUpdate[type] = {}; //empty
		FTT.setItemLS('FTTPrefQueue',JSON.stringify(FTT.prefQueueLSUpdate));
	}
	FTT.prefQueue[type] = {};
	api.postWithEditToken( {format: 'json', assert:FTT.assert, action: type, change:change } ).then( function ( data ) { //change is an array, e.g.: ['userjs-FTTSubs=','userjs-FTTSubs2=']. Do not use alternative multiple-value separator for change parameter: T306319
		FTT.updatePrefInProgress[type] = false;
//		FTT.debug(data);
//		if ( ( data.globalpreferences || data.options || data.globalpreferenceoverrides ) == 'success' ) {FTT.debug('updated preferences');}
	}, function ( code, data ) {
		FTT.updatePrefInProgress[type] = false;
		FTT.prefQueue[type] = Object.assign(FTT.prefQueueLSBackup,FTT.prefQueue[type]); //failed. restore queue
		FTT.setItemLS('FTTPrefQueue',JSON.stringify(FTT.prefQueue));
		FTT.APIError(code, data);
	});
};
FTT.deleteSubsFromPrefs = function() {
	FTT.queueUpdatePref('options','userjs-FTTSubs','');
	FTT.queueUpdatePref('options','userjs-FTTSubs2','');
};
FTT.changePreferences = function(actionToPerform, type) {
	if ( type == 'accountprefs' && FTT.projectIsSULWiki ) {
		FTT.prefsParamsAction = 'options';
	} else if ( type == 'accountprefs' ) {
		FTT.prefsParamsAction = 'options'; //for non-SUL wikis
	} else if ( type == 'globalprefs' ) {
		FTT.prefsParamsAction = 'globalpreferences';
	}
	if ( actionToPerform == 'delete' ) {
		FTT.prefsParamsSettings = ''; //will remove equal sign in queueUpdatePref, otherwise we'd just set the pref with an empty string
	} else {
		FTT.prefsParamsSettings = FTT.encodedPrefs;
	}
	if ( type == 'globalprefs' ) {
		FTT.queueUpdatePref('options','userjs-FTT',''); //remove local overrides
	}
	FTT.queueUpdatePref(FTT.prefsParamsAction,'userjs-FTT',FTT.prefsParamsSettings);
	var CheckPrefsUpdated2 = setInterval(function (int) {
		if ( ( ! FTT.MD || ! FTT.MD.reminder || Object.keys(FTT.prefQueue[FTT.MD.reminder.prefType]).length == 0 ) && FTT.updatePrefInProgress[FTT.prefsParamsAction] == false ) {
			clearInterval(CheckPrefsUpdated2);
			if ( FTT.PRMOpened.justSettings ) {
				FTT.closeSettings();
			} else {
				FTT.reloadForm();
			}
			mw.notify($('<span>'+FTT.msgs.settingReload+'</span>'));
		}
	},200);
};
FTT.saveSettings = function(saveCiInt) {
	FTT.disableForm(true); //disable buttons so you can't double click
	$(document.getElementById('saveSettingsButton')).addClass('FTTPendingBlink');
//	FTT.debug('Saving settings..');
	FTT.prefCount = Object.keys(FTT.defaultSettings).length;
	for (FTT.prefint = 0; FTT.prefint < FTT.prefCount; FTT.prefint++) {
		FTT.processKey = Object.keys(FTT.defaultSettings)[FTT.prefint];
//		FTT.debug('process element for saving ' + (FTT.prefint + 1) + '/' + FTT.prefCount + ': ' + FTT.processKey);
		if ( FTT.sEl[FTT.processKey] && typeof FTT.sEl[FTT.processKey].isSelected == 'function' ) {
			FTT.settings[FTT.processKey] = FTT.sEl[FTT.processKey].isSelected();
		} else if ( FTT.sEl[FTT.processKey] && typeof FTT.sEl[FTT.processKey].getValue == 'function' ) {
			FTT.settings[FTT.processKey] = FTT.sEl[FTT.processKey].getValue();
		}
	}
	if ( FTT.settings.pingText == '' ) {
		FTT.settings.pingText = FTT.pingText;
	}
	if ( ! FTT.settings.saveDraft ) {
		FTT.rmItemLS('FTTDrafts');
	}
	if ( FTT.settings.editor != 'lastused' ) {
		FTT.rmItemLS('FTTLastCmtEditor');
	}
	if ( ( FTT.settings.stalkAddSubLinks || FTT.settings.stalkAutoSub || FTT.settings.markNewCmts || FTT.settings.markNewCmtsSubbed ) && ( (FTT.settings.stalkStoreInPrefs && FTT.getItemLS('FTTDrafts')) || (!FTT.settings.stalkStoreInPrefs && mw.user.options.get('FTTSubs') ) ) ) {
		FTT.stalkSaveSubs(FTT.stalkGetSubs());
	}
	if ( FTT.settings.stalkStoreInPrefs ) {
		FTT.rmItemLS('FTTSubs');
	}
	if (!FTT.settings.stalkStoreInPrefs && mw.user.options.get('FTTSubs') ) {
//		FTT.debug('removing FTTSubs from account preferences');
		FTT.deleteSubsFromPrefs();
	}
	FTT.settings.cI = D1(FTT.sEl.cI.getValue().replace(/^[\n]*/,'').replace(/[\n]*$/,'').replace(/\n/g,'FTTSPLIT'+FTT.semiRandom)).split(new RegExp('FTTSPLIT'+FTT.semiRandom));
	FTT.settings.cIThatRun = D1(FTT.sEl.cIThatRun.getValue().replace(/^[\n]*/,'').replace(/[\n]*$/,'').replace(/\n/g,'FTTSPLIT'+FTT.semiRandom)).split(new RegExp('FTTSPLIT'+FTT.semiRandom));
	FTT.settings.cIThatRunCmt = D1(FTT.sEl.cIThatRunCmt.getValue().replace(/^[\n]*/,'').replace(/[\n]*$/,'').replace(/\n/g,'FTTSPLIT'+FTT.semiRandom)).split(new RegExp('FTTSPLIT'+FTT.semiRandom));
	if ( window.FTTGlobalPrefs ) {window.FTTGlobalPrefs = FTT.settingsToSave;} //refreshing this would otherwise require a page reload
	if ( window.FTTPrefs ) { window.FTTPrefs = FTT.settingsToSave; }
	FTT.prefCount = Object.keys(FTT.defaultSettings).length;
	FTT.settingsToSave = $.extend( true, {}, FTT.settings );
//	FTT.debug('compacting preferences');
	for (FTT.prefint = 0; FTT.prefint < FTT.prefCount; FTT.prefint++) { //compacting settings by removing values that match defaults
		FTT.processKey = Object.keys(FTT.defaultSettings)[FTT.prefint];
//		FTT.debug('checking ' + FTT.prefint + '/' + FTT.prefCount + ': ' + FTT.processKey);
		if ( ( typeof FTT.settingsToSave[FTT.processKey] == "boolean" || typeof FTT.settingsToSave[FTT.processKey] == "string" ) && FTT.settingsToSave[FTT.processKey] == FTT.defaultSettings[FTT.processKey] ) {
//			FTT.debug('removing ' + FTT.processKey + ' from settings to save, same as default');
			delete FTT.settingsToSave[FTT.processKey];
		} else {
//			FTT.debug(FTT.processKey + ': ' + FTT.settingsToSave[FTT.processKey] + ' does not match ' + FTT.defaultSettings[FTT.processKey]);
		}
	}
//	FTT.debug('finished compacting preferences');
	FTT.encodedPrefs = JSON.stringify(FTT.settingsToSave); //store object as a base64 encoded JSON. D1 because btoa only supports Latin1
	if ( FTT.testValidJSON(FTT.sEl.importSettings.getValue()) ) {
		FTT.encodedPrefs = FTT.sEl.importSettings.getValue();
	}
	if ( FTT.settings.saveTo == 'browser' ) {
//		FTT.debug('saving settings to localStorage.FTT');
		FTT.setItemLS('FTT', FTT.encodedPrefs);
		if ( FTT.PRMOpened.justSettings ) {
			FTT.closeSettings();
		} else {
			FTT.reloadForm();
		}
		mw.notify($('<span>'+FTT.msgs.settingReload+'</span>'));
	} else { //saving to accountprefs or globalprefs
//		FTT.debug('saving settings to ' + FTT.settings.saveTo);
		FTT.resetPreferences('browser'); //removing localStorage so that won't override accountprefs
		if ( FTT.settings.saveTo == 'globalprefs' ) {
			FTT.queueUpdatePref('options','userjs-FTT',''); //remove local overrides
		}
		FTT.changePreferences('save',FTT.settings.saveTo);
	}
};
FTT.resetPreferences = function(prefType) {
	if ( ! prefType ) { // the reset/delete preferences button was pressed
		FTT.disableForm(true);
		$(document.getElementById('resetPreferencesButton')).addClass('FTTPendingBlink');
	}
	prefType = ( prefType || FTT.sEl.saveTo.getValue() );
	if ( prefType == 'browser' ) {
		FTT.rmItemLS('FTT');
		if ( ! prefType ) {
			FTT.reloadForm();
		}
	} else if ( prefType == 'accountprefs' ) {
//		FTT.debug('removing settings from account preferences');
		FTT.changePreferences('delete','accountprefs');
	} else if ( prefType == 'globalprefs' ) {
//		FTT.debug('removing settings from global preferences');
		FTT.changePreferences('delete','globalprefs');
	}
};
FTT.clearLocalStorage = function(a) {
	FTT.rmItemLS('FTTBasicLang');
	FTT.rmItemLS('FTTLang');
	FTT.rmItemLS('ENOM');
	FTT.rmItemLS('FTTDrafts');
	FTT.rmItemLS('FTTThanks');
	mw.notify(FTT.B1.actioncomplete);
//	FTT.debug('bye localStorage items');
};
window.onbeforeunload = function() { FTT.cancelScrewed = true; };
FTT.FFDarkMode = function(SCROLL) {
	if ( SCROLL && ( FTT.pageXOffset || FTT.pageYOffset ) ) {
		window.scrollTo(FTT.pageXOffset,FTT.pageYOffset);
		return;
	}
	if ( navigator.userAgent.match(/Firefox/) && $('html.client-dark-mode')[0]) { //Dark mode gadget on Firefox
		FTT.pageXOffset = window.pageXOffset;
		FTT.pageYOffset = window.pageYOffset;
//		FTT.debug('seems you have dark mode enabled and are using Firefox, scroll to top due to Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=1650522');
		window.scrollTo(0,0);
	} else {
		delete FTT.pageXOffset;
		delete FTT.pageYOffset;
	}
};
FTT.popup = function(msg,removeElement,isEncoded) { //second argument allows an element to be removed after the popup is dismissed so it doesn't linger invisibly
//	FTT.debug('FTT.popup');
	if ( isEncoded ) {
		msg = D2(msg);
	}
	mw.loader.using(['oojs-ui-core','oojs-ui-windows']).then(function(){
		FTT.FFDarkMode();
		$('#FTTOverlay').addClass('FTTNoDisplay');
		OO.ui.alert(new OO.ui.HtmlSnippet(msg),{size:'large'}).done( function() {
			$('#FTTOverlay').removeClass('FTTNoDisplay');
			if ( removeElement ) {
				$(removeElement).remove();
			}
			FTT.FFDarkMode(1);
			if ( FTT.PRMOpened && FTT.PRMOpened.highlightRef && ! FTT.formChanged ) { //dismissing popup ruins our reference selection..
//				FTT.debug('popup: dismissed popup, run HLSelectRef');
				FTT.HLSelectRef(FTT.refIndexStart,FTT.refIndexEnd);
			}
		});
	});
};
FTT.addScrewedLink = function(errorDescription,moreInfo,CSSclass) {
	if ( FTT.cancelScrewed ) {
//		FTT.debug('you\'re leaving? guess this error won\'t interest you then.');
		var DelayedScrew = setInterval(function () { //sometimes errors seem to happen BECAUSE we're leaving the page (which probably causes http requests to get canceled), but just in case the user decides to stay, just delay this error. 5 seconds should be enough to finalize leaving.
			clearInterval(DelayedScrew);
			FTT.addScrewedLink(errorDescription,moreInfo);
		}, 5000);
		return;
	}
//	FTT.debug('WELL NOW YOU\'VE DONE IT.');
//	FTT.debug('Description: ' + errorDescription + ', moreInfo: ' + moreInfo);
	if ( typeof FTT.processElementArray == 'object' ) {
		FTT.addScrewedProcessLength = FTT.processElementArray.length;
		FTT.addScrewedProcessTypesArray = [];
		FTT.addScrewedProcessTypes = '';
		for(FTT.addScrewedInt=0;FTT.addScrewedInt<FTT.processElementArray.length;FTT.addScrewedInt++){ //collect HTML element types that are present in the processElementArray. If case of some gadget or script conflict you may see something missing here.
			if ( FTT.processElementArray[FTT.addScrewedInt] && ! FTT.addScrewedProcessTypesArray.includes(FTT.processElementArray[FTT.addScrewedInt].nodeName) ) {
				FTT.addScrewedProcessTypesArray.push(FTT.processElementArray[FTT.addScrewedInt].nodeName);
				FTT.addScrewedProcessTypes = FTT.addScrewedProcessTypes + FTT.processElementArray[FTT.addScrewedInt].nodeName + ', ';
			}
		}
		FTT.addScrewedProcessTypes = FTT.addScrewedProcessTypes.replace(/, $/,'');
	} else {
		FTT.addScrewedProcessLength = -1;
		FTT.addScrewedProcessTypes = 'none';
	}
	if ( $('.FTTLinks')[0] ) {
		FTT.addScrewedRLCount = $('.FTTLinks').length;
	} else {
		FTT.addScrewedRLCount = 0;
	}
	FTT.screwedUrlParamRLP = 'n%2Fa';
	if ( FTT.PRMOpened ) {
		FTT.screwedUrlParamRLP = D1(JSON.stringify(FTT.PRMOpened));
	}
	FTT.failURL = 'index.php?title=User_talk:Alexis_Jazz/Factotum&action=edit&section=new&loadFTT=0&preloadtitle=You%20messed%20up!%20(' + D1(errorDescription) + ')&preload=User_talk%3AAlexis%20Jazz/Factotum/preload&preloadparams%5b%5d=' + FTT.screwedUrlParamRLP + '&preloadparams%5b%5d=' + D1(new Date().toLocaleString('en-GB',{timeZone:'UTC'})) + '%20UTC&preloadparams%5b%5d=' + D1(JSON.stringify(FTT.customizedSettings)) + '&preloadparams%5b%5d=' + D1(moreInfo) + '&preloadparams%5b%5d=' + M1('skin') + '&preloadparams%5b%5d=' + D1(new Date(FTT.timestampInit).toLocaleString('en-GB',{timeZone:'UTC'})) + '&preloadparams%5b%5d=' + D1(FTT.addScrewedProcessLength) + '&preloadparams%5b%5d=' + D1(FTT.addScrewedProcessTypes) + '&preloadparams%5b%5d=' + FTT.addScrewedRLCount + '&preloadparams%5b%5d=' + D1(window.location.href) + '&preloadparams%5b%5d=' + FTT.activeEditor;
	FTT.failMsg = '<div id="FTTScrewed" style="text-align:center;min-height:25em" class="'+(CSSclass||'')+'"><h3 style="padding-top:0">' + FTT.msgs.messedUp1 + ' <a href="https://en.wikipedia.org/w/' + FTT.failURL + '">' + FTT.msgs.messedUp3 + '</a></h3>"' + FTT.escapeHTML(errorDescription) + '"<br/>' + FTT.kittehStuck + '<p><b>' + FTT.msgs.messedUp2 + '</b><br/>' + FTT.msgs.messedUp4.replace('META','<a href="https://meta.wikimedia.org/w/' + FTT.failURL + '">Meta-Wiki</a>').replace(/\[\[NLWIKT\|([^\]]*)\]\]/,'<a href="https://nl.wiktionary.org/w/' + FTT.failURL.replace('You%20messed%20up!','Je%20hebt%20het%20verprutst!') + '">$1</a>') + '</p></div>';
	FTT.popup(FTT.failMsg,'#FTTScrewed');
	FTT.disableForm(false);
};
FTT.insertToSnag = function() {
	$('#FTTReplyForm').append(FTT.tosNagContainer);
	FTT.WMCopyright = '';
	try {FTT.WMCopyright = FTT.escapeReplacement(FTT.B1['wikimedia-copyright'].replace(/<[\/]?a[^>]*>/g,'').replace(/&gt;/g,'>').replace(/&lt;/g,'<').match(/<a[^>\n]*href="[^"\n]*Terms[^"\n]*"[^>]*>[^<]*<\/a>/)[0]);} catch (e) {}
	$('#FTTToSNag')[0].innerHTML = FTT.B1.copyrightwarning.replace('$2',FTT.escapeReplacement(FTT.B1['wikimedia-mobile-license-links'])).replace('$1',FTT.WMCopyright);
};
FTT.toggleTosNag = function() {
	$('#FTTToSNag').remove();
	if ( FTT.sEl.tosNag.isSelected() ) {
		FTT.insertToSnag();
	}
};
try {FTT.CSSContentDir = $('#mw-content-text')[0].attributes.dir.value;FTT.normalFontSize = getComputedStyle($('#mw-content-text')[0])['font-size'];} catch (e) {}
if ( FTT.CSSContentDir == 'rtl' ) {
	FTT.CSSDirectionR = 'left';
	FTT.CSSDirectionL = 'right';
	FTT.CSSRot = '-90';
} else {
	FTT.CSSDirectionR = 'right';
	FTT.CSSDirectionL = 'left';
	FTT.CSSRot = '90';
}
if ( !FTT.normalFontSize || !FTT.normalFontSize.match(/[0-9]+px/) ) {
	FTT.normalFontSize = 'medium';
}
//ensure a concistent look, icons are own work
FTT.svgGray = '777';
FTT.svgHeader = '<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" ';
FTT.svgFTTIcon = 'width="1080px" height="720px" viewBox="0 0 1080 720"><defs><mask id="cut"><rect fill="#FFF" x="0" y="0" width="720" height="720"/><ellipse fill="#000" cx="870" cy="370" rx="300" ry="400"/></mask><mask id="cut2"><rect fill="#0ff" x="665" y="260" width="640" height="480"/></mask></defs><ellipse fill="#8af" cx="700" cy="420" rx="450" ry="280" mask="url(#cut)"/><ellipse fill="#8af" cx="360" cy="310" rx="340" ry="280"/>';
FTT.svgFTTIconData = 'data:image/svg+xml,' + D1(FTT.svgHeader + FTT.svgFTTIcon.replace(/1080/g,'720').replace(/<mask id="cut2">.*<\/mas>/,'') + '</svg>');
FTT.svgFTTIconFirstReplyData = FTT.svgFTTIconData.replace(/8af/g,'68d');
FTT.svgFTTIconEditData = 'data:image/svg+xml,' + D1(FTT.svgHeader + 'width="720px" height="720px" viewBox="0 0 720 720"><g transform="rotate(40)"><g transform="translate(530 200) scale(1 1.5) rotate(45)"><rect fill="#8bf" x="0" y="0" width="127" height="127"/></g><rect fill="#8af" x="440" y="-365" width="180" height="700"/><rect fill="#79e" x="460" y="-230" width="40" height="520"/><rect fill="#67e" x="440" y="-365" width="180" height="100"/></g></svg>');
FTT.svgFTTIconEditBlackData = FTT.svgFTTIconEditData.replace(/8af/g,FTT.svgGray).replace(/8bf/g,'c1c1c1').replace(/79e/g,'aaa').replace(/67e/g,'999');
FTT.svgFTTIconEditRedData = FTT.svgFTTIconEditData.replace(/8af/g,'d97').replace(/8bf/g,'da7').replace(/79e/g,'c86').replace(/67e/g,'c65');
FTT.svgFTTIconEdit = '<img style="height:2em !important" src="' + FTT.svgFTTIconEditData + '">';
FTT.svgFTTIconLinkData = 'data:image/svg+xml,' + D1(FTT.svgHeader + 'width="720px" height="720px" viewBox="0 0 720 720"><defs><mask id="c1"><rect x="0" width="720" height="360" fill="#fff"/><rect x="0" width="240" height="720" fill="#fff"/><circle cx="440" cy="360" r="30" fill="#fff"/><circle cx="240" cy="510" r="30" fill="#fff"/></mask></defs><g mask="url(#c1)"><ellipse cx="240" cy="360" rx="200" ry="150" stroke="#556" stroke-width="60" fill="none"/></g><g mask="url(#c1)" transform="rotate(180) translate(-720,-720)"><ellipse cx="240" cy="360" rx="200" ry="150" stroke="#556" stroke-width="60" fill="none"/></g></svg>');
FTT.svgFTTIconSettingsData = 'data:image/svg+xml,' + D1(FTT.svgHeader + 'width="720px" height="720px" viewBox="0 0 720 720"><defs><clipPath id="cut1"><rect fill="#f0f" width="220" height="720"/><rect fill="#f0f" x="500" width="220" height="720"/><rect fill="#f0f" width="720" height="220"/><rect fill="#f0f" y="500" width="720" height="220"/></clipPath><clipPath id="cut"><circle fill="#333" cx="360" cy="360" r="355"/></clipPath></defs><g clip-path="url(#cut)"><rect fill="#333" x="300" y="0" width="120" height="720" clip-path="url(#cut1)"/><rect fill="#333" x="0" y="300" width="720" height="120" clip-path="url(#cut1)"/></g><g clip-path="url(#cut)"><g clip-path="url(#cut1)"><g transform="translate(360,-277)"><g transform="rotate(45)"><rect fill="#333" x="390" y="0" width="120" height="900"/><rect fill="#333" x="0" y="390" width="900" height="120"/></g></g></g></g><circle cx="360" cy="360" r="180" fill="none" stroke="#333" stroke-width="120"/></svg>');
FTT.svgFTTIconLinkBlack = '<img style="height:0.9em !important" src="' + FTT.svgFTTIconLinkData.replace(/\%23556/g, '%23333').replace('height%3D%22720px%22%20viewBox%3D%220%200%20720%20720','height%3D%22380px%22%20viewBox%3D%220%20170%20720%20380') + '">';
FTT.svgFTTIconSettings = '<img style="height:1.3em" src="' + FTT.svgFTTIconSettingsData + '">';
FTT.svgMagnifierData = 'data:image/svg+xml,' + D1(FTT.svgHeader + 'width="720px" height="720px" viewBox="0 0 720 720"><circle cx="290" cy="290" r="260" stroke="#333" stroke-width="60" fill="none"/><rect transform="rotate(-45)" fill="#333" x="-50" y="650" width="100" height="300"/></svg>');
FTT.svgMagnifier = '<img style="height:1.3em" src="' + FTT.svgMagnifierData + '">';
FTT.svgFTTIconBell = 'width="720px" height="720px" viewBox="0 0 720 720"><defs><mask id="c1"><rect fill="#fff" width="720" height="560"/></mask></defs><g mask="url(#c1)"><ellipse fill="#8af" cx="360" cy="460" rx="200" ry="440"/><ellipse fill="#8af" cx="360" cy="600" rx="280" ry="380"/></g><circle cx="360" cy="650" r="70" fill="#8af"/>';
FTT.svgFTTIconBellData = 'data:image/svg+xml,' + D1(FTT.svgHeader + FTT.svgFTTIconBell + '</svg>');
FTT.svgFTTIconBellStruckData = 'data:image/svg+xml,' + D1(FTT.svgHeader + FTT.svgFTTIconBell + '<rect transform="rotate(45)" fill="#444" x="450" y="-440" width="100" height="880"/></svg>');
FTT.svgFTTHeartIconData = 'data:image/svg+xml,' + D1(FTT.svgHeader + 'width="720px" height="720px" viewBox="0 0 720 720"><g transform="translate(360,120) scale(1.00,0.95) rotate(45)"><rect fill="#8af" x="0" y="0" width="420" height="420"/><circle cx="0" cy="210" r="210" fill="#8af"/><circle cx="210" cy="0" r="210" fill="#8af"/></g></svg>');
FTT.svgFTTHeartRedIconData = FTT.svgFTTHeartIconData.replace(/8af/g,'f44');
FTT.svgFTTHeartIcon = '<img style="height:2em !important" src="' + FTT.svgFTTHeartIconData + '">';
FTT.svgFTTHeartRedIcon = '<img style="height:2em !important" src="' + FTT.svgFTTHeartRedIconData + '">';
FTT.svgFTTChevronIconData = 'data:image/svg+xml,' + D1(FTT.svgHeader + 'width="720px" height="720px" viewBox="0 0 720 720"><g transform="translate(360,720) rotate(-135)"><rect fill="#333" x="120" y="120" width="480" height="60"/><rect fill="#333" x="120" y="120" width="60" height="480"/></g></svg>');
FTT.svgFTTChevronIcon = '<img style="height:1em !important;padding:0 1.2em 0 1.2em" src="' + FTT.svgFTTChevronIconData + '">';
FTT.svgFTTChevronBlackIcon = FTT.svgFTTChevronIcon.replace(/%23333/g,'%23000');
FTT.svgFTTChevronIconTopData = FTT.svgFTTChevronIconData.replace('720%20720%22%3E','720%20720%22%3E'+D1('<rect fill="#333" y="680" width="720" height="40"/>'));
FTT.svgFTTWarningData = 'data:image/svg+xml,' + D1(FTT.svgHeader) + D1('width="720px" height="720px" viewBox="0 0 720 720"><polygon points="360,60 35,670 685,670" style="fill:none;stroke:#333;stroke-width:40px" /><rect x="325" y="240" width="70" height="270" fill="#333" /><circle cx="360" cy="580" r="40" fill="#333"/></svg>');
FTT.svgFTTWarning = '<img style="height:1.3em" src="' + FTT.svgFTTWarningData + '">';
FTT.svgFTTPingIconData = FTT.svgFTTIconData.replace('%3C%2Fdefs%3E','%3C%2Fdefs%3E%3Cg%20opacity%3D%220.5%22%3E').replace('%3C%2Fsvg%3E',D1('</g><g transform="translate(-100,-140) rotate(45)"><rect fill="#333" x="280" y="0" width="440" height="120"/><rect fill="#333" x="600" y="0" width="120" height="440"/></g></svg>')); //Speech bubble with arrow to insert mention
FTT.svgFTTIconLinkArrowData = FTT.svgFTTIconLinkData.replace('%3C%2Fdefs%3E','%3C%2Fdefs%3E%3Cg%20opacity%3D%220.5%22%3E').replace('%3C%2Fsvg%3E',D1('</g><g transform="translate(-100,-140) rotate(45)"><rect fill="#333" x="280" y="0" width="440" height="120"/><rect fill="#333" x="600" y="0" width="120" height="440"/></g></svg>')); //Link icon with arrow to insert link to comment
FTT.svgFTTPreviewArrow = '<g mask="url(#c1)"><circle cx="360" cy="360" r="240" fill="none" stroke="#333" stroke-width="60" /></g><g transform="rotate(45) translate(293,120)"><rect width="160" height="60" fill="#333"/><rect width="60" height="160" fill="#333"/></g>';
FTT.svgFTTPreviewDefs = 'width="720px" height="720px" viewBox="0 0 720 720"><defs><mask id="c1"><rect y="360" width="450px" height="360px" fill="#fff"/><rect x="650" y="-120" width="150px" height="110px" fill="#fff" transform="rotate(60)"/></mask><mask id="c2"><rect x="120" width="460px" height="780px" fill="#fff" transform="rotate(-30)"/></mask><mask id="c3"><rect y="360" width="720px" height="360px" fill="#fff"/></mask></defs>';
FTT.svgFTTPreview = 'data:image/svg+xml,'+D1(FTT.svgHeader+FTT.svgFTTPreviewDefs+FTT.svgFTTPreviewArrow+'<g mask="url(#c2)"><circle cx="360" cy="360" r="240" fill="none" stroke="#333" stroke-width="60" /></g></svg>');
FTT.svgFTTLiveOn = 'data:image/svg+xml,'+D1(FTT.svgHeader+FTT.svgFTTPreviewDefs+FTT.svgFTTPreviewArrow+'<g transform="rotate(180) translate(-720,-720)">'+FTT.svgFTTPreviewArrow+'</g></svg>');
FTT.svgFTTLiveOff = FTT.svgFTTLiveOn.replace(/(%3C%2Fsvg%3E)/,D1('<rect x="330" width="60" height="720" fill="#333"/><rect y="330" width="720" height="60" fill="#333"/>')+'$1');
FTT.svgFTTUndo = FTT.svgFTTPreview.replace('%23c2','%23c3').replace(/%23333/g,'%23000').replace('height%3D%22720px%22%20viewBox%3D%220%200%20720%20720%22',D1('height="420px" viewBox="0 250 720 420"'));
FTT.svgFTTCancelIcon = '<img style="height:1em !important;padding:0 0.2em 0 0.2em;display:inline-block;vertical-align:middle" src="data:image/svg+xml,' + D1(FTT.svgHeader + 'width="720px" height="720px" viewBox="0 0 720 720"><g transform="translate(360 -150) rotate(45)"><circle cx="360" cy="360" r="320" fill="none" stroke="#a00" stroke-width="60"/><rect x="30" y="320" width="660" height="80" fill="#a00"/></g></svg>')+'">';
if ( FTT.settings.grayscale ) {
	FTT.svgFTTIconData = FTT.svgFTTIconData.replace(/8af/g,FTT.svgGray);
	FTT.svgFTTIconFirstReplyData = FTT.svgFTTIconFirstReplyData.replace(/68d/g,'000');
	FTT.svgFTTIconEditData = FTT.svgFTTIconEditBlackData;
	FTT.svgFTTIconBellData = FTT.svgFTTIconBellData.replace(/8af/g,FTT.svgGray);
	FTT.svgFTTIconBellStruckData = FTT.svgFTTIconBellStruckData.replace(/8af/g,FTT.svgGray);
	FTT.svgFTTHeartIconData = FTT.svgFTTHeartIconData.replace(/8af/g,FTT.svgGray);
	FTT.svgFTTHeartRedIconData = FTT.svgFTTHeartIconData.replace(new RegExp(FTT.svgGray,'g'),'333');
	FTT.svgFTTPingIconData = FTT.svgFTTPingIconData.replace(/8af/g,FTT.svgGray);
	FTT.svgFTTIconLinkArrowData = FTT.svgFTTIconLinkArrowData.replace(/556/g,FTT.svgGray);
	$('#mw-content-text').addClass('FTTGrayScale');
}
FTT.getComBG = function(el,c) {
	try{c = getComputedStyle($(el)[0])['background-color'];} catch (e) {}
	if ( c && c != 'rgba(0, 0, 0, 0)' ) {
		return c;
	} else {
		return false;
	}
};
if ( FTT.getComBG('#mw-content-text') ) {
//	FTT.debug('use original background color from #mw-content-text');
	FTT.origBGColor = FTT.getComBG('#mw-content-text');
} else if ( FTT.getComBG('#content') ) {
//	FTT.debug('use original background color from #content');
	FTT.origBGColor = FTT.getComBG('#content');
} else if ( FTT.getComBG('.mw-page-container') ) {
//	FTT.debug('use original background color from body');
	FTT.origBGColor = FTT.getComBG('.mw-page-container');
} else if ( FTT.getComBG('body') ) {
//	FTT.debug('use original background color from body');
	FTT.origBGColor = FTT.getComBG('body');
} else {
//	FTT.debug('use #fff as original background color');
	FTT.origBGColor = 'rgb(255,255,255)';
}
if ( FTT.origBGColor.match(/#[0-9A-Fa-f]{3}$/) ) { //convert #f0f to #ff00ff
	FTT.origBGColor = '#'+FTT.origBGColor.slice(1,2)+FTT.origBGColor.slice(1,2)+FTT.origBGColor.slice(2,3)+FTT.origBGColor.slice(2,3)+FTT.origBGColor.slice(3,4)+FTT.origBGColor.slice(3,4);
}
if ( FTT.origBGColor.match(/#[0-9A-Fa-f]{6}/) ) { //convert #ff00ff to rgb(255,0,255). This allows adding transparency like rgba(255,0,255,0.5) later using FTT.origBGColor.replace(/^rgb\((.*)\)$/,'rgba($1,0.5)')
	FTT.origBGR = parseInt(FTT.origBGColor.slice(1,3),16);
	FTT.origBGG = parseInt(FTT.origBGColor.slice(3,5),16);
	FTT.origBGB = parseInt(FTT.origBGColor.slice(5,7),16);
} else {
	FTT.origBGParts = FTT.origBGColor.match(/rgb[a]?\(([0-9]+),[ ]?([0-9]+),[ ]?([0-9]+)/);
	FTT.origBGR = FTT.origBGParts[1];
	FTT.origBGG = FTT.origBGParts[2];
	FTT.origBGB = FTT.origBGParts[3];
}
if ((FTT.origBGR+FTT.origBGG+FTT.origBGB) > 380 ) {
	FTT.origBGRD = Math.floor(FTT.origBGR * 0.98);
	FTT.origBGGD = Math.floor(FTT.origBGG * 0.98);
	FTT.origBGBD = Math.floor(FTT.origBGB * 0.98);
} else {
	FTT.origBGRD = Math.floor(FTT.origBGR * 1.1);
	FTT.origBGGD = Math.floor(FTT.origBGG * 1.1);
	FTT.origBGBD = Math.floor(FTT.origBGB * 1.1);
}
FTT.origBGColor = 'rgb('+FTT.origBGR+','+FTT.origBGG+','+FTT.origBGB+')';
FTT.origBGColorDark = 'rgb('+FTT.origBGRD+','+FTT.origBGGD+','+FTT.origBGBD+')';
FTT.selectorForBGOpacity = '.FTTCustomBG #FTTTextAndPreview,.FTTCustomBG .HLRefDropDownL .oo-ui-dropdownWidget-handle,.FTTCustomBG #FTTSummaHoriLayout,.HLRefDropDownL .oo-ui-buttonElement-button';
mw.util.addCSS(
'.FTTReplyLink:hover{text-decoration:none;cursor:pointer}'+
'.FTTRLMasq:hover{text-decoration:underline}'+
'.FTTReplyLink:active{text-decoration:none;box-shadow:inset 0 0 0 0.1em rgba(0,0,255,0.5)}'+
'.FTTSVG{background-repeat:no-repeat;background-position:center;display:inline-block;vertical-align:middle;margin-bottom:0.1em}'+
'.FTTSVG>.FTTScreenReaderLabel{color:rgba(0,0,0,0);font-size:0.2px !important;user-select:none;-ms-user-select:none;-webkit-user-select:none}'+ //very tiny text with 0% opacity, selection disabled to prevent copy-pasting, screen readers should pick it up though
'.FTTSVG>.FTTScreenReaderLabel:before{content:attr(data-content)}'+
'.FTTSVGLinkIcon{background-image:url("'+FTT.svgFTTIconLinkData+'");background-size:1.6em;padding:0 1.5em 0 1.5em}'+
'.FTTGenLink{margin-'+FTT.CSSDirectionL+':0.5em}'+
'.FTTSVGLinkIconArrow{background-image:url("'+FTT.svgFTTIconLinkArrowData+'");background-size:1em;padding:0 1.5em 0 1.5em}'+
'.FTTSVGIcon,.FTTSVGNewSectionIcon{background-image:url("'+FTT.svgFTTIconData+'");background-size:1em;padding:0 1.2em 0 1.2em}'+
'.FTTSVGPingIcon{background-image:url("'+FTT.svgFTTPingIconData+'");background-size:1em;padding:0 1.2em 0 1.2em}'+
'.FTTSVGLargeIcon{background-repeat:no-repeat;background-position:center;height:2em;display:inline-block;background-image:url("'+FTT.svgFTTIconData+'");background-size:2em;padding:0 1em 0 1em}'+
'.FTTSVGFirstReplyIcon{background-image:url("'+FTT.svgFTTIconFirstReplyData+'");background-size:1em;padding:0 1em 0 1em}'+
'.FTTSVGEditIcon{background-image:url("'+FTT.svgFTTIconEditData+'");background-size:1em;padding:0 1.1em 0 1.1em}'+
'.FTTSVGEditIconM:before{background-image:url("'+FTT.svgFTTIconEditBlackData+'");background-size:1.25em;padding:0;background-repeat:no-repeat}'+
'.FTTSVGEditRedIcon{background-image:url("'+FTT.svgFTTIconEditRedData+'");background-size:1em;padding:0 1.1em 0 1.1em}'+
'.FTTSVGEditIcon.FTTRef{padding:0 1.1em 0 1.1em}'+
'.FTTSVGBellIcon{background-image:url("'+FTT.svgFTTIconBellData+'");background-size:1em;padding:0 1.5em 0 1.5em}'+
'.FTTSVGBellStruckIcon{background-image:url("'+FTT.svgFTTIconBellStruckData+'");background-size:1em;padding:0 1.5em 0 1.5em}'+
'.FTTSVGHeartIcon{background-image:url("'+FTT.svgFTTHeartIconData+'");background-size:1em;padding:0 1.2em 0 1.2em}'+
'.FTTSVGHeartRedIcon{background-image:url("'+FTT.svgFTTHeartRedIconData+'");background-size:1em;padding:0 1.2em 0 1.2em;transform:rotate(20deg)}'+
'.FTTSVGChevronIcon{background-image:url("'+FTT.svgFTTChevronIconData+'");background-size:1em;padding:0 1em 0 1em;height:1em}'+
'.FTTSVGChevronTopIcon{background-image:url("'+FTT.svgFTTChevronIconTopData+'");background-size:1em;padding:0 1em 0 1em}'+
'.FTTSVGChevronIconMini{background-image:url("'+FTT.svgFTTChevronIconData.replace(/%23333/g,'%23000')+'");background-size:1em;padding:0 1em 0 1em}'+
'.FTTSVGChevronIconRot{transform:rotate(' + FTT.CSSRot + 'deg)}'+
'.FTTSVGChevronIcon180{transform:rotate(180deg)}'+
'.FTTSVGRefresh{background-image:url("'+FTT.svgFTTPreview+'");background-size:1em;padding:0 1em 0 1em;height:1em}'+
'.FTTSVGLiveOn{background-image:url("'+FTT.svgFTTLiveOn+'");background-size:1em;padding:0 1em 0 1em;height:1em}'+
'.FTTSVGLiveOff{background-image:url("'+FTT.svgFTTLiveOff+'");background-size:1em;padding:0 1em 0 1em;height:1em}'+
'.FTTSVGRedo{background-image:url("'+FTT.svgFTTUndo+'");background-size:1.3em;height:0.9em;padding:0 0.6em 0 0.6em;transform:rotate(90deg)}'+
'.FTTSVGUndo{transform:rotate(90deg) scale(1,-1)}'+
'.FTTEditIndicator{border:double rgba(0,0,0,0.5)}'+
'.FTTCustomBG{background-repeat:no-repeat;background-size:cover;padding:1em;background-position:center}'+
'.FTTCustomBG.FTTPortrait{background-position:center 25%}'+
FTT.selectorForBGOpacity+'{opacity:0.88}'+
FTT.selectorForBGOpacity.replace(/(,|$)/g,':hover$1')+'{opacity:0.95}'+
'.FTTCustomBG .HLRefDropDown{background:none}'+
'#FTTReplyForm:not(.FTTOverlay) #FTTMarkupButtonBar,#FTTReplyForm:not(.FTTOverlay) #FTTButtonBarRight{font-size:larger !important}'+
'.FTTNoDisplay{display:none !important;visibility:hidden}'+
'.FTTYesDisplay{display:inline !important}'+
'.FTTNoDisplayMWEd{display:none !important}'+
'.FTTMarginHalfEm{margin-bottom:0.5em}'+
'.FTTMarginHalfEmTop{margin-top:0.5em}'+
'.FTTNewLinesBox{padding:0.1em 1em 0.1em 1em;border:thin dashed gray;word-wrap:break-word;overflow-wrap:break-word;background-color:rgba(0, 70, 255, 0.05)}'+
'.FTTFloatRight{float:' + FTT.CSSDirectionR + '}'+
'.FTTForm{clear:both;font-style:normal;text-align:initial;transition:all .5s ease-in;padding:0.5em 0.5em 0em 0.5em;font-size:' + FTT.settings.UIfontSize + ';font-family:sans-serif;font-weight:normal;max-width:97vw}'+
'.FTTMonoSpace{font-family:monospace; !important}'+
'.FTTLimitWidth{max-width:50em}'+
'.FTTUnsetLimitWidth{max-width:100vw !important}'+
'.FTTSettings{background:#FEFEFF;padding:0.1em 0.5em 0.5em 0.5em;border:thin solid lightgray;clear:both}'+
'#FTTSettingsTop{display:flex}'+
'.FTTSettingTabs{padding:0.2em 0.5em 0 0.5em !important}'+
'.FTTDropDownMenu{max-width:35em}'+
'.FTTOOuiFieldLayout{margin-top:0.5em !important;margin-bottom:0.75em}'+
'.FTTOOuiFieldLayout::after{clear:none}'+
'.FTTShakeIt{animation:shake 0.12s;animation-iteration-count:3} @keyframes shake{0%{margin:-0.1em 0em 0.6em 0em} 9%{margin:-0.2em 0em 0.7em 0em} 18%{margin:-0.3em 0em 0.8em 0em} 27%{margin:-0.2em 0em 0.7em 0em} 36%{margin:-0.1em 0em 0.6em 0em} 45%{margin:0em 0em 0.5em 0em} 54%{margin:0.1em 0em 0.4em 0em} 63%{margin:0.2em 0em 0.3em 0em} 72%{margin:0.3em 0em 0.2em 0em} 81%{margin:0.2em 0em 0.3em 0em} 90%{margin:0.1em 0em 0.4em 0em} 100%{margin:0em 0em 0.5em 0em}}'+
'.FTTSmallerText{font-size:70%}'+
'@keyframes FTTBlinker{50%{opacity:0.5}}'+
'.FTTPendingBlink{animation:FTTBlinker 1s linear infinite}'+
'.FTTLeftRightMargin{margin-left:0.6em;margin-right:0.6em}'+
'.FTTMarkupBold .oo-ui-labelElement-label,#FTTcITSbuttonMenu .oo-ui-labelElement-label,#FTTUIMarkupLinkButton .oo-ui-labelElement-label{font-weight:bold}'+
'.FTTMarkupItalic .oo-ui-labelElement-label{font-weight:normal;font-style:italic}'+
'.FTTMarkupStrike .oo-ui-labelElement-label{text-decoration:line-through;font-weight: normal}'+
'#FTTMarkupButtonBar{z-index:10}'+
'#FTTUIMarkUpDropDownLabel{text-decoration:underline;font-style:italic;font-weight:bold}'+
'.FTTForm.FTTOtherSide #FTTButtonBarRight,#FTTReplyForm>#FTTButtonBarRight{float:' + FTT.CSSDirectionR + '}'+
'#FTTMarkupButtonBar .oo-ui-labelElement-label{padding-left:0.8em !important;padding-right:0.8em !important}'+
'#FTTButtonBarRight .oo-ui-labelElement-label{padding-left:0.3em !important;padding-right:0.3em !important}'+
'.FTTPreviewAfterPost{transition:all .5s ease-in;margin-left:0;font-family:inherit;font-weight:normal}'+
'.FTTFirstReply{display:block !important;text-align:center;margin:-0.6em 0 -0.5em 0;height:1em}'+
'.FTTNoMaxWidth{max-width:none}'+
'.FTTInsertLinkForm{transition:all .5s ease-in;background:#EEEEFF;border:0.1em solid #CCC}'+
'#FTTReplyForm:not(.FTTOverlay) #UIinsertLinkForm{padding:0.5em 0.5em 0 0.5em;}'+
'#FTTReplyForm{clear:both}'+
'.FTTInsertLinkForm:focus{border:0.2em solid #69F}'+
'.FTTPurpleBG{background-color:rgba(170, 70, 255, 0.2) !important}'+
'.FTTEaseIn{transition:all 1.5s ease-in}'+
'.FTTEaseInSlow{transition:all 3.5s ease-in}'+
'.FTTDiffPlusBig{font-weight:bold;color:#006400}'+
'.FTTDiffPlus{color:#006400}'+
'.FTTDiffMinBig{font-weight:bold;color:#8b0000}'+
'.FTTDiffMin{color:#8b0000}'+
'#FTTVisualLight{background:#fff;display:inline-block;transition:all .5s ease-in;padding:0.5em;margin:0em 0.5em 0.5em 0;border:0.2em solid #CCC;width:98%;height:10em;word-wrap:break-word;overflow-wrap:break-word;overflow:auto;outline:none}'+
'#FTTVisualLight:focus{border:0.2em solid #69F}'+
'.FTTEditorSwitch{font-weight:bold;font-family:mono}'+
'.FTTHalfOpacity{opacity:0.5}.FTTHalfOpacity:hover{opacity:0.95}'+
'.FTT075Opacity{opacity:0.75}.FTT075Opacity:hover{opacity:0.95}'+
'.FTTWarning{background:#FFBBBB;padding:1em;text-align:center}'+
'.FTTpermaLink{font-size:'+FTT.settings.UIfontSize+'}'+
'.FTTpermaLinkText{width:25em}'+
'.FTTCreditTop{position:absolute;z-index:999}'+
'.FTTSearchSettingsInput input{width:15em !important}'+
'.FTTSearchSettingsInput{width:15em !important;margin:0.5em auto 0 auto !important}'+
'.FTTIndented{margin-'+FTT.CSSDirectionL+':1.5em}'+
'.FTTonetimetools{background-color:#eee;padding:1em}'+
'.FTTSubsDiv{background-color:rgba(0,0,0, 0.02);padding:1em;margin:0.5em}'+
'.FTTRedBG{background-color:rgba(255,0,0,0.1) !important}'+
'.FTTCycleBtnsFixed{cursor:pointer;position:fixed;top:50%;background-color:#eee;z-index:3;' + FTT.CSSDirectionL + ':0%;border-' + FTT.CSSDirectionR + ':0.1em solid #777;border-top:0.1em solid #777;border-bottom:0.1em solid #777}'+
'#mw-content-text .FTTNewCmtText:after{content:"\\00a0'+FTT.B1.newarticle+'\\00a0";transition:background 4s ease-in;transition-delay:6s}'+
'.FTTCycleBtnsFixed .FTTNewCmt,#mw-content-text .FTTNewCmt:after{background-color:rgba(0,255,0, 0.1)}'+
'.FTTCycleBtnsFixed .FTTNewCmtSubscribed,#mw-content-text .FTTNewCmtSubscribed:after{background-color:rgba(0,150,0, 0.3)}'+
'.FTTLinks{display:inline-block}'+
'.skin-timeless .mw-editsection .FTTLinks{font-size:larger}.mw-editsection .FTTSubscribe{padding:0 1.5em 0 1.5em !important}'+//".mw-editsection a" applies padding 0.5em 0 0.25em. no idea why
'.skin-minerva H1 .mw-editsection .FTTLinks,.skin-minerva H2 .mw-editsection .FTTLinks{font-size:smaller}'+
'.skin-minerva .mw-editsection .FTTSVG{padding-top:0.75em}'+
'.skin-minerva .FTTSVGChevronIcon.sectioncollapse,.skin-minerva #FTTCollapseAllSections .FTTSVGChevronIcon{padding-top:0.5em;margin-bottom:-0.5em}'+
'.FTTFlexLayout{display:flex}'+
'.FTTFloatingToC{'+FTT.CSSDirectionR+':0;bottom:0;position:fixed;overflow:auto;display:block !important;max-height:60%;z-index:999}'+
'#FTTReplyForm .codeEditor-status,#FTTReplyForm .wikiEditor-ui-text{font-size:initial}'+
'#FTTUITextInput>textarea{max-width:100%;max-height:80vh}'+
'#FTTUITextInput .wikiEditor-ui-text>textarea{max-height:75vh}'+//2010 wikitext editor tends to merge with the summary and other bits when using a stupidly low viewport. Why that textarea can't just be contained within #FTTUITextInput I will never know. At 75vh it triggers less easily.
'#FTTOverlay{width:100%;height:100%;position:fixed;top:0;bottom:0;left:0;right:0;z-index:100;background-color:'+FTT.origBGColor+';overflow:auto;padding-top:0.5em}'+
'.FTTActiveOverlay{position:relative;overflow:hidden}'+
'#FTTOverlay #FTTSettingsButtonBar{position:sticky;top:0;z-index:998;width:100%;text-align:center}'+
'#FTTOverlay #scrollUpSett{max-width:16vw}#FTTOverlay #saveSettingsButton>a,#FTTOverlay #cancelSettingsButton>a{text-overflow:ellipsis;overflow:hidden}'+
'#FTTOverlay #saveSettingsButton>a{max-width:45vw}#FTTOverlay #cancelSettingsButton>a{max-width:35vw}'+
'#FTTOverlay.FTTpadded>*{padding-left:2%;padding-right:2%}'+
'#FTTOverlay.FTTpadded>#FTTPreviewBox{margin-left:2%;margin-right:2%;max-width:92%}'+
'.FTTForm .oo-ui-numberInputWidget-field a,.FTTForm .oo-ui-comboBoxInputWidget-field>.oo-ui-comboBoxInputWidget-dropdownButton>a{min-height:unset !important;min-width:unset !important}'+
'.FTTForm .maxSubsSize{width:10em}'+
'.FTTDiscussionActivity{color:grey}'+
'.FTTMinervaEditMarkers{visibility:visible !important}'+
'@media screen and (max-height:480px){#FTTScrewed>a>img{max-height:250px}}'+
'@media screen and (max-width:600px){.FTTCreditTop{display:none}}'+
'#FTTOverlay #FTTReplyForm{padding-bottom:1.5em}'+
'.content a.edit-page{visibility:visible !important}'+
'.content .collapsible-heading a.edit-page{display:none}'+ //why do I have to fix Minerva?? just changing visibility means it still takes up space, you have to use display.. am I missing something?
'.content .collapsible-heading.open-block a.edit-page{display:inline-block}'+
'@media screen and (max-width:600px){.skin-minerva.FTTWithHeadingLinks .content .mw-parser-output h1,.skin-minerva.FTTWithHeadingLinks .content .mw-parser-output h2,.skin-minerva.FTTWithHeadingLinks .content .mw-parser-output h3,.skin-minerva.FTTWithHeadingLinks .content .mw-parser-output h4,.skin-minerva.FTTWithHeadingLinks .content .mw-parser-output h5,.skin-minerva.FTTWithHeadingLinks .content .mw-parser-output h6{display:inline-block;width:100%}}'+ //why are headers flex??? okay with infoboxes it can make sense, but at limited width?
'@media screen and (max-width:600px){body.skin-minerva .content .collapsible-heading:not(.open-block) .FTTHeadingLinks>a,body.skin-minerva .content .collapsedSection .FTTHeadingLinks>a,body.skin-minerva .content .collapsedSection .mw-editsection>a.edit-page{display:none}.FTTHeadingLinks>a.FTTScrollTop{display:unset}}'+
'body.skin-minerva .FTTHeadingLinks .FTTSVGLinkIcon{padding-right:1em;padding-left:1em}'+
'#showNewLines{text-align:center;padding:0.5em}'+
'#showNewLines .mw-parser-output{text-align:initial}'+
'.FTTThanked{cursor:default}'+
'.FTTBracket{color:#000}'+
'.FTTLinks a:not(.FTTRLMasq):hover{text-decoration:none}'+
'#FTTFirstHeadingLinks{font-size:smaller}#FTTFirstHeadingLinks span:not(.FTTScreenReaderLabel){font-size:smaller}#FTTFirstHeadingLinks a{vertical-align:middle}'+
'html.ve-active #FTTFirstHeadingLinks,html.ve-active #FTTEditLede,html.ve-active #FTTCollapseAllSections{display:none}'+
'#FTTButtonBarRight .oo-ui-labelElement-label,#FTTMarkupButtonBar .oo-ui-labelElement-label{font-size:larger;line-height:1.2em !important}'+
'#FTTButtonBarRight a{line-height:1.4em !important}'+
'#FTTMainButtonBar{min-height:1.3em}'+
'#FTTCustomInserts{margin-top:0.5em}'+
'.FTTDiffSize.FTTFloatRight{padding:0.5em}'+
'.FTTCustomBG .FTTDiffSize{background:rgba(255,255,255,0.5)}'+
'#FTTFloatReturn{transition:all 0.5s ease-in;left:50%;bottom:2em;position:fixed;background-color:#f0e6ff;padding:0.5em;border:1px #aa46ff solid;text-decoration:none}'+
'.FTTFloatReturnTop{bottom:unset !important;top:2em}'+
'#UIinsertLinkForm{margin-bottom:0.5em !important}'+
'.FTTSpamPopup .ext-discussiontools-emptystate{display:none !important}'+ //GTFO
'.FTTTocTitle{cursor:pointer;color:#0645ad}'+
'.FTTFloatToCMinerva{overflow:hidden}.FTTFloatToCMinerva ul{display:none}.FTTFloatToCMinerva .toctitle .mw-ui-icon{display:none}'+
'#mw-content-text .FTTCommentLinks .FTTSVGChevronIcon{padding:0 1.2em 0 1.2em !important}'+
'#FTTUITextInput,#FTTUITextInput textarea{max-width:100vw;height:100%;margin-left:0;margin-right:0}'+
'#FTTSummaHoriLayout{width:100%}'+//stop the summary field from getting squashed if the textarea is made too big
'#FTTTextAndPreview{max-height:80vh;margin-bottom:0.5em;margin-top:0.5em;clear:both}'+
'#FTTTextAndPreview #FTTUITextInput{display:inline-flex;vertical-align:top !important}'+
'#FTTTextAndPreview #FTTPreviewBox{overflow:auto;display:inline-block;width:47%;vertical-align:top;height:100%}'+
'.FTTForm>#FTTPreviewBox,#FTTReplyForm>#FTTPreviewBox,#FTTOverlay>#FTTPreviewBox{width:98%;max-width:98%}'+
'#FTTTextAndPreview #FTTPreviewBtns{position:sticky}'+
'#FTTTextAndPreview #FTTPreviewBox.pbOverlayTrans #FTTPreviewBtns,#FTTTextAndPreview #FTTPreviewBox.pbOverlayHidden #FTTPreviewBtns{text-align:'+FTT.CSSDirectionR+'}'+
'#FTTPreviewBox{padding:0.1em 1% 0 1%;font-size:'+FTT.normalFontSize+';word-wrap:break-word;overflow-wrap:break-word;background:'+FTT.origBGColorDark+';position:relative;min-height:4em;margin-bottom:0.5em}'+
'.skin-minerva #FTTPreviewBox{display:inline-block}'+
'#FTTPreviewBox:not(.FTTGreenInset){box-shadow:inset 0 0 0 0.1em #ccc}'+
'#FTTPreviewBox>.mw-parser-output>.ext-discussiontools-emptystate{display:none}'+ //no signature? here's some stupid shit
'#FTTPreviewBox span.mw-editsection{display:none}'+
'.client-js .ns-0 #FTTPreviewBox ol>li>ul{display: unset !important}'+ //hack to show collapsed quotations on English Wiktionary, which they themselves collapsed with what amounts to a hack. Should have been possible to use mw-collapsible (for personal reference: mw.loader.using('jquery.makeCollapsible').then( function () {$($('.mw-collapsible')[0]).makeCollapsible()}); ) or better yet DON'T collapse quotations but nooooooo
'.skin-minerva #FTTPreviewBox .section-heading{display:flex !important}'+
'#FTTTextAndPreview #FTTPreviewToggle .FTTSVG{margin-top:-0.3em}'+
'.FTTToggleOut{transform:rotate(225deg)}'+
'.FTTToggleIn{transform:rotate(45deg)}'+
'.FTTForm:not(.FTTFormComment) #FTTUITextInput .wikiEditor-ui-text .ui-resizable{height:25em !important;max-height:80vh}'+
'@media screen and (max-height:600px){.FTTForm:not(.FTTFormComment) #FTTUITextInput .wikiEditor-ui-text .ui-resizable{max-height:65vh}}'+
'.FTTForm.FTTFormComment .CodeMirror{height:unset;min-height:10em}'+
'.ve-init-mw-desktopArticleTarget #FTTUITextInput .CodeMirror{position:unset}'+
'#FTTPreviewBox .mw-disambig{background-color:#AFEEEE; }'+
'#FTTPreviewBox .mw-redirect{background-color:#F5CB7D; }'+
'#FTTPreviewBox .mw-parser-output>p:first-child{margin-top:0 !important}'+
'#FTTPreviewBox .ext-discussiontools-init-replylink-buttons,#FTTPreviewBox .ext-discussiontools-init-section-subscribeButton,#FTTPreviewBox .ext-discussiontools-init-section-bar{display:none !important}'+
'.FTTForm>#FTTPreviewBox,#FTTOverlay>#FTTPreviewBox{margin-bottom:0.5em}'+
'#FTTPreviewBox>.mw-parser-output{padding:0.5em}'+
'.FTTFilterLabel>H2{margin-top:0}'+
'#FTTnoresults{text-align:center;margin-top:0.5em}'+
'#FTTPreviewBtns{transition:all .2s ease-in;padding-top:0.3em;font-size:larger}'+
'#FTTTextAndPreview #FTTPreviewBox#FTTPreviewBtns{padding-top:0}'+
'@media screen and (max-width:500px){#FTTOverlay #FTTTextAndPreview #FTTPreviewBtns .FTTSVG{padding:0 0.7em 0 0.7em}#FTTOverlay #FTTTextAndPreview #FTTPreviewBtns{padding:0;left:unset !important;right:unset !important;text-align:center}#FTTOverlay #FTTTextAndPreview #FTTPreviewBtns>span{margin-left:0;margin-right:0}}'+
'#FTTPreviewBox.pbFloat #FTTPreviewBtns{top:0.3em}'+
'#FTTPreviewBox.pbOverlayTrans #FTTPreviewBtns:hover,#FTTPreviewBox.pbOverlayHidden:hover #FTTPreviewBtns{opacity:1 !important}'+
'.FTTForm>#FTTPreviewBox.pbOverlayTrans #FTTPreviewBtns,#FTTReplyForm>#FTTPreviewBox.pbOverlayTrans #FTTPreviewBtns,#FTTPreviewBox.pbOverlayHidden #FTTPreviewBtns,#FTTOverlay>#FTTPreviewBox.pbOverlayTrans #FTTPreviewBtns{position:absolute;'+FTT.CSSDirectionR+':0.5em}'+
'#FTTTextAndPreview>#FTTPreviewBox.pbOverlayTrans #FTTPreviewBtns,#FTTTextAndPreview>#FTTPreviewBox.pbOverlayHidden #FTTPreviewBtns{position:sticky;'+FTT.CSSDirectionR+':0.5em}'+
'#FTTPreviewBox.pbOverlayTrans #FTTPreviewBtns{opacity:0.3}'+
'#FTTPreviewBox.pbOverlayTrans:hover #FTTPreviewBtns{opacity:0.6}'+
'#FTTPreviewBox.pbOverlayHidden #FTTPreviewBtns{opacity:0}'+
'#FTTPreviewBox.pbBarCenter #FTTPreviewBtns,#FTTPreviewBox.pbBar #FTTPreviewBtns,#FTTPreviewBox.pbBarCenterF #FTTPreviewBtns,#FTTPreviewBox.pbBarF #FTTPreviewBtns{width:100%}'+
'#FTTPreviewBox.pbBarCenter #FTTPreviewBtns,#FTTPreviewBox.pbBarCenterF #FTTPreviewBtns{text-align:center}'+
'#FTTPreviewBox.pbBar #FTTPreviewBtns,#FTTUIversionDate{text-align:'+FTT.CSSDirectionR+'}'+
'#FTTPreviewBox.pbFloat #FTTPreviewBtns,#FTTPreviewBox.pbFloatF #FTTPreviewBtns{float:'+FTT.CSSDirectionR+'}'+
'#FTTPreviewBox.pbDisabled #FTTPreviewBtns{display:none}'+
'.FTTGreenInset{box-shadow:inset 0 0 0 0.1em rgba(102,187,0,0.8)}'+
'.FTTkitteh{max-width:85%}'+
'#FTTMarkupButtonBar>span{max-width:20vw}'+ //up to 5 buttons per line (bold, italic, strike, link, mention)
'#FTTMarkupButtonBar{display:inline}'+
'#FTTMarkupButtonBar *,#FTTButtonBarRight *{vertical-align:middle}'+
'.HLRefDropDown,.HLRefDropDown>.oo-ui-dropdownWidget{max-width:unset !important}'+
'.HLRefDropDownL{display:flex}'+
'.HLRefDropDown .oo-ui-dropdownWidget-handle>.oo-ui-labelElement-label{max-width:15em;overflow:visible}'+
'.HLRefDropDown>div>div{z-index:90}'+
'.FTTBadRef{background:rgba(255,0,0,0.2)}'+
'#FTTUISwitchEditorButton.FTT2010Label{transform:skew(-10deg, 0deg)}'+
'#mw-content-text.FTTGrayScale .FTTtheStranger{border-bottom:0.1em solid rgba(0,0,0,0.3)}'+
'.FTTtheStrangerRLMasq .FTTRLMasq{text-decoration:underline}'+
'#FTTshortcutsMap{margin:auto}'+
'.FTTInvisChar{user-select:none;-ms-user-select:none;-webkit-user-select:none}'+
'#FTTSettingsButtonBar span{margin-bottom:0 !important}'+
'#FTTUIversionDate{display:block}'+
'#FTTFindPrevBtn img{transform:rotate(90deg)}'+
'#FTTFindBtn img{transform:rotate(-90deg)}'+
'#FTTSettings .FTTRisky label .FTTRiskyBlock{color:rgba(255,128,0,0.9)}'+
'#FTTSettings .FTTSuperRisky label .FTTRiskyBlock{color:rgba(255,0,0,0.9)}'+
'#FTTPreviewBox section{display:block !important}'+
'.FTTNinjaBtn{font-size:smaller}'+
'.FTTH2SectContainer,FTTH1SectContainer{clear:both}'+
'.FTTscrollMargin{margin-'+FTT.CSSDirectionL+':1.5em}'+
'.FTTfixed{position:fixed;top:0;left:0}'+
'#FTTNotify{transition:all 0.3s ease-in;font-size:large;position:fixed;z-index:999;top:0;left:0;width:100%;height:100%;background:rgba(255,255,255,0.5);text-align:center;padding-top:50%;}'+
'div.mw-inputbox-centered .FTTIBExt{margin-left:auto;margin-right:auto;width:35em}'+
'.FTTPreviewAfterPost section,#FTTOverlay section,.FTTForm section{display:block !important}'+
'.skin-minerva #FTTUInewLinesRead{display:none}'+
'#FTTUIMarkUpDropDownLabel{line-height:0.9}'+
'#FTTUISwitchEditorButton .oo-ui-labelElement-label{line-height:1.4em !important}'+
'.FTTAutoNum{margin-'+FTT.CSSDirectionR+':0.3em}'+
'.FTTAutoNum.FTTPlain{color:inherit}'+
'.FTTCommentLinks{font-size:'+FTT.normalFontSize+'}'+
'#FTTMarkupButtonBar .FTTSVGRedo{font-size:initial}'+
'#FTTUISwitchEditorButton .cm-mw-accessible-colors,#FTTUISwitchEditorButton .ace-tm,#FTTUISwitchEditorButton .ace-tm span{line-height:1}'+
'.FTTnewLi{display:inline-block;width:100%}'+
'#FTTUglyChevronFix{position:absolute;width:100%;height:100%;top:0;right:0;background-image: linear-gradient(to '+FTT.CSSDirectionR+',rgba(0,0,0,0) 85%,#f00)}'+ //#f8f9fa
'.FTTHeadingLinks,#FTTEditLede{line-height:1 !important}'+
'#FTTSummaHoriLayout label{z-index:0}'+
'body.skin-minerva.FTTWithHeadingLinks .content .mw-parser-output .mw-heading, .content .mw-parser-output h1, .content .mw-parser-output h2, .content .mw-parser-output h3, .content .mw-parser-output h4, .content .mw-parser-output h5, .content .mw-parser-output h6 {display:block !important}' //T353486
//'body.skin-minerva div[data-event-name="talkpage.section"]{display:block !important}' //T355746 (again)
);
FTT.livePreviewToggle = function(trigger) {
	if ( FTT.livePreviewDisabled ) {
//		FTT.debug('livePreviewToggle: enable live preview');
		$('#FTTPreviewBox').addClass('FTTGreenInset');
		FTT.livePreviewDisabled = false;
		FTT.checkIfLivePreviewShouldBeRendered();
		if ( ( FTT.settings.smartLivePreview || FTT.PRMOpened.type == 'editFullPage' ) && ! FTT.textInputLive ) {
			FTT.UITextInput.on('change',function(){FTT.smartLivePreview();});
			$('#FTTReplyForm').on('keyup click',function(e){ //doesn't catch all changes, but allows smartlivepreview to work at least most of the time
				if ( FTT.activeEditor == '2010wikitext' ) {
					FTT.smartLivePreview();
				}
			});
			FTT.textInputLive = true;
		}
		FTT.UILiveToggle.setTitle(FTT.msgs.liveOn);
		FTT.UILiveToggle.$button.attr('aria-label',FTT.msgs.liveOn);
		if ( trigger == 'btn'){
			FTT.notify(FTT.msgs.liveOnNotify);
		}
		$('#FTTliveToggle .FTTSVG').removeClass('FTTSVGLiveOn').addClass('FTTSVGLiveOff');
	} else {
//		FTT.debug('livePreviewToggle: disable live preview');
		$('#FTTPreviewBox').removeClass('FTTGreenInset');
		FTT.livePreviewDisabled = true;
		FTT.UILiveToggle.setTitle(FTT.msgs.liveOff);
		FTT.UILiveToggle.$button.attr('aria-label',FTT.msgs.liveOff);
		if ( trigger == 'btn'){
			FTT.notify(FTT.msgs.liveOffNotify);
		}
		$('#FTTliveToggle .FTTSVG').removeClass('FTTSVGLiveOff').addClass('FTTSVGLiveOn');
	}
};
FTT.movePreview = function(newPlace) {
	if ( $('#FTTTextAndPreview #FTTPreviewBox')[0] ) {
//		FTT.debug('movePreview: move preview away from textarea');
		$('#FTTPreviewBtns .FTTSVG').removeClass('FTTToggleOut').addClass('FTTToggleIn');
		FTT.UIPreviewToggle.setTitle(FTT.msgs.previewSide);
		FTT.UIPreviewToggle.$button.attr('aria-label',FTT.msgs.previewSide);
//		FTT.debug('movePreview: insertafter');
		FTT.insertAfter($('#showNewLines')[0],$('#FTTPreviewBox')[0]);
		FTT.movePreviewAbove();
		$('#FTTUITextInput')[0].style.width = '';
		if ( $('#FTTVisualLight')[0] ) {
			$('#FTTVisualLight')[0].style.width = '';
		}
		$('#FTTUITextInput textarea')[0].style.width = '';
		FTT.sideBySide = false;
		FTT.focusInput();
	} else {
//		FTT.debug('put preview next to textarea');
		$('#FTTUITextInput textarea')[0].style.width = '';
		$('#FTTUITextInput textarea')[0].style.height = '';
		$('#FTTPreviewBtns .FTTSVG').removeClass('FTTToggleIn').addClass('FTTToggleOut');
		FTT.UIPreviewToggle.setTitle(FTT.msgs.previewApart);
		FTT.UIPreviewToggle.$button.attr('aria-label',FTT.msgs.previewApart);
		$('#FTTTextAndPreview').append($('#FTTPreviewBox')[0]);
		$('#FTTUITextInput')[0].style.width = '49%';
		if ( $('#FTTVisualLight')[0] ) {
			$('#FTTVisualLight')[0].style.width = '48%';
		}
		$('#FTTTextAndPreview')[0].scrollIntoView(FTT.smoothScroll);
		FTT.sideBySide = true;
		FTT.previewHeightFix();
		FTT.focusInput();
	}
};
FTT.previewHeightFix = function(A) { //DOM polling is no good, but I see no other way that isn't stupidly complicated
	A = true; //forces the adjustment to activate the first time
	var heightFix = setInterval(function(){ //sometimes null gets registered in FTT.processElementArray despite the element really existing.. If it was just changed to adjust for the real (serverside) timestamp there should still be an element there, even if it would no longer be attached to the DOM. So I guess maybe sometimes the element just isn't available in the DOM fast enough? In that case, this delay should help
		if ( FTT.activeEditor == 'visualLight' ) {
			FTT.newHeightEl = '#FTTVisualLight';
		} else {
			FTT.newHeightEl = '#FTTUITextInput';
		}
		try{FTT.newHeight = $(FTT.newHeightEl)[0].getBoundingClientRect().height+'px';} catch (e) {}
		if ( FTT.sideBySide && (FTT.newHeight != FTT.oldHeight || A ) ) {
			A = false;
//			FTT.debug('adjust height from '+FTT.oldHeight+' to '+FTT.newHeight);
			try{$('#FTTPreviewBox')[0].style.height = FTT.newHeight;} catch (e) {}
		} else if ( ! FTT.sideBySide ) {
			try{$('#FTTPreviewBox')[0].style.height = '';} catch (e) {}
			clearInterval(heightFix);
		}
		FTT.oldHeight = FTT.newHeight;
	},500);
};
FTT.notify = function(str) {
	if ( FTT.activeOverlay ) {
		FTT.notifyDiv = document.createElement('div');
		FTT.notifyDiv.id = 'FTTNotify';
		FTT.notifyDiv.innerHTML = str;
		$('body').append(FTT.notifyDiv);
		var delayNotify = setInterval(function(){
			clearInterval(delayNotify);
			$('#FTTNotify')[0].style.opacity = '0';
		}, 1200);
		var delayNotifyRM = setInterval(function(){
			clearInterval(delayNotifyRM);
			$('#FTTNotify').remove();
		}, 1500);
	} else {
		mw.notify($('<span>'+str+'</span>'));
	}
};
FTT.shiftToOverlay = function(int) { // the overlay is removed by FTT.cancelReply()
	if ( window.innerWidth > FTT.settings.overlayThreshold && ( !FTT.isMobile || !FTT.isDiscussionPage ) ) {
//		FTT.debug('shiftToOverlay: your viewport exceeds the threshold for overlay mode and you\'re either not on the mobile site or not on a talk page, skip overlay');
		return;
	}
	FTT.FFDarkMode();
	FTT.overlayDiv = document.createElement('div');
	FTT.overlayDiv.id = 'FTTOverlay';
	if ( FTT.settings.overlayPad ) {
		FTT.overlayDiv.classList.add('FTTpadded');
	}
	$('body').append(FTT.overlayDiv);
	$('body').addClass('FTTActiveOverlay');
	FTT.origFormDiv = $('#FTTReplyForm')[0].parentElement;
	FTT.UIDiffButton.setLabel(FTT.B1.diff); //shorter label for diff button
	int=0;
	while(FTT.origFormDiv.childNodes[0] && int < 100){
//		FTT.debug('moved element to overlay');
		$('#FTTOverlay').append(FTT.origFormDiv.childNodes[0]);
		int++;
	}
	$('#FTTReplyForm').addClass('FTTOverlay');
	FTT.UITextInput.scrollElementIntoView();
	if ( FTT.PRMOpened.type == 'newsection' ) {
		FTT.scrollAndCall($('#FTTUITextInputTitle')[0],function(){FTT.UITextInputTitle.focus();});
	} else {
		FTT.scrollAndCall($('#FTTTextAndPreview')[0],function(){FTT.focusInput();});
	}
	FTT.activeOverlay = 1;
};
FTT.removeOverlay = function() {
	$('#FTTOverlay').remove();
	$('body').removeClass('FTTActiveOverlay');
	FTT.FFDarkMode(1);
	FTT.activeOverlay = 0;
};
FTT.makeCollapsible = function(element) { //element should end with .mw-collapsible if you want autocollapse to work
	if ( ! element ) {
		element = '.FTTPreviewAfterPost .mw-collapsible,#FTTPreviewBox .mw-collapsible';
	}
	if ( $(element)[0] ) { //don't bother if there's nothing collapsible
		mw.loader.using('jquery.makeCollapsible').then( function () {
			for ( FTT.previewCollapsibleInt=0;FTT.previewCollapsibleInt<$(element).length;FTT.previewCollapsibleInt++){
				$($(element)[FTT.previewCollapsibleInt]).makeCollapsible();
			}
			FTT.autoCollapseButtons = $(element+'.autocollapse .mw-collapsible-toggle'); //not sure why makeCollapsible doesn't take care of this. Maybe another function
			for (FTT.autoCBint=0;FTT.autoCBint<FTT.autoCollapseButtons.length;FTT.autoCBint++) {
				FTT.autoCollapseButtons[FTT.autoCBint].click();
			}
		});
	}
};
FTT.indentWidth = 1.6;
if ( FTT.isMobile ) {
	FTT.indentWidth = 1;
}
FTT.pageRevisionSinceLastCheck = {};
FTT.pageRevisionIDSinceLastCheck = {};
FTT.doAPICall = function( params, mode, callback ) {
//	FTT.debug('called doAPICall with mode ' + mode + ', params:');
//	FTT.debug(params);
//	FTT.apiTimeStart = new Date().getTime();//FTT.debug
	if ( params.action == 'parse' ) {
		FTT.doAPICallMethod = 'post';
	} else {
		FTT.doAPICallMethod = 'postWithEditToken';
	}
	api[FTT.doAPICallMethod]( params ).then( function ( apiResponse ) {
//		FTT.apiTimeEnd = new Date().getTime();//FTT.debug
		if (params.action == 'parse' && mode == 'previewposted' ) {
//			FTT.debug('Got parsed comment:' + apiResponse.parse.text);
			FTT.doPreviewInProgress = 0;
			if ( FTT.PRMOpened.type == 'newsection' && FTT.settings.nSecBottomLink ) {
				$('#mw-content-text').append($('#FTTnSecBottom')[0].parentElement); //move new section link back down
			}
			FTT.postedCommentParsed = apiResponse.parse.text.replace(/^<div class=\"mw-parser-output\">([^]*)<\/div>$/, '$1').replace(/^<p>([^]*)<\/p>[\s]*/,'$1').replace(/<span data\-mw\-comment[^>]*>(<span data\-mw\-comment\-[^>]*><\/span>)*<\/span>/,'');//get rid of P tags that would otherwise break up comment and signature when editing an existing comment
			if ( FTT.PRMOpened.freshindent && FTT.PRMOpened.type == 'edit' ) {
				FTT.FTTFormNegativeMargin = (FTT.PRMOpened.freshindent * FTT.indentWidth);
			} else if ( FTT.PRMOpened.freshcomment && FTT.PRMOpened.type == 'comment' ) {
//				FTT.debug('you\'re replying to your own comment that was rendered with parse-in-place');
				FTT.FTTFormNegativeMargin = 0;
				if ( FTT.commentTextIndent.match(/┌/) ) {
					FTT.FTTFormNegativeMargin = (FTT.cmtIndentHTML * -FTT.indentWidth);
				}
			}
			FTT.previewAfterParseBox = '<div id="PreviewAfterPost" style="margin-' + FTT.CSSDirectionL + ':' + FTT.FTTFormNegativeMargin + 'em" class="FTTPreviewAfterPost FTTPurpleBG">' + FTT.postedCommentParsed;
			FTT.previewHasHeaderRegExp = new RegExp('^(<h1[ >][^]*<\/h1>|<h2[ >][^]*<\/h2>|<h3[ >][^]*<\/h3>|<h4[ >][^]*<\/h4>|<h5[ >][^]*<\/h5>|<h6[ >][^]*<\/h6>)');
			FTT.previewHasHeader = FTT.postedCommentParsed.match(FTT.previewHasHeaderRegExp);
			if ( FTT.previewHasHeader ) {
//				FTT.debug('preview starts with a section header. Moving section header outside the previewAfterParseBox so editing the comment right after posting won\'t destroy the header');
				FTT.previewAfterParseBox = FTT.previewHasHeader[0] + '<div id="PreviewAfterPost" class="FTTPreviewAfterPost FTTPurpleBG mw-parser-output">' + FTT.postedCommentParsed.replace(FTT.previewHasHeaderRegExp,''); //mw-parser-output is added to allow templatestyles to work
			}
			if ( ['comment','FCL','newsection','newheading'].indexOf(FTT.PRMOpened.type) != -1 || ( FTT.PRMOpened.type == 'edit' && document.getElementById('PreviewAfterPost-' + FTT.PRMOpened.id) ) ) {
				FTT.wrongLocatorID = FTT.previewAfterParseBox.match(/span id="([^"]*:[0-9]{13,14}:[^"]*)" class="FTTCmt"/);
				if ( FTT.wrongLocatorID ) {
					FTT.wrongLocatorID = FTT.wrongLocatorID[1];
//					FTT.debug('recorded wrong locator ID for preview: ' + FTT.wrongLocatorID + ', this will be replaced when we get confirmation from the server with the correct timestamp');
				} else {
//					FTT.debug('could not find locator!');
					FTT.wrongLocatorID = '';
				}
			}
			if ( FTT.PRMOpened.type == 'edit' && document.getElementById('PreviewAfterPost-' + FTT.PRMOpened.id) ) {
//				FTT.debug('preview for comment edited from preview');
				document.getElementById('PreviewAfterPost-' + FTT.PRMOpened.id).outerHTML = FTT.previewAfterParseBox + '</div>';
				$('.FTTPreviewAfterPost .ext-discussiontools-init-replylink-buttons,.FTTPreviewAfterPost .ext-discussiontools-init-section-subscribe').remove();//taking out the trash. not like these could have ever worked like this..
			} else if ( FTT.PRMOpened.type == 'edit' ) {
//				FTT.debug('editing a comment, not from the preview after posting');
				FTT.cancelReply('commentedit');
				FTT.locatorInHTMLRegExp = new RegExp('([^]*)(<span id="' + E1(FTT.PRMOpened.id) + '" class="[^"]*">)(([^<]|<(?![\/]?span>|<span([^<]|<(?!\/span>))*)*)*<\/span>)');
				if ( document.getElementById('FTTLink-' + FTT.escapeHTML(FTT.PRMOpened.id)).parentElement.parentElement.innerHTML.match(FTT.locatorInHTMLRegExp) ) {
					document.getElementById('FTTLink-' + FTT.escapeHTML(FTT.PRMOpened.id)).parentElement.parentElement.innerHTML = document.getElementById('FTTLink-' + FTT.escapeHTML(FTT.PRMOpened.id)).parentElement.parentElement.innerHTML.replace(FTT.locatorInHTMLRegExp, FTT.previewAfterParseBox + '$2$3' + '</div>');
					var DelayedAssociateElementParse = setInterval(function(){ //sometimes null gets registered in FTT.processElementArray despite the element really existing.. If it was just changed to adjust for the real (serverside) timestamp there should still be an element there, even if it would no longer be attached to the DOM. So I guess maybe sometimes the element just isn't available in the DOM fast enough? In that case, this delay should help
						clearInterval(DelayedAssociateElementParse);
						$(document).ready(function() {
							FTT.processElementArray[FTT.PRMOpened.int] = document.getElementById('FTTLink-' + FTT.escapeHTML(FTT.PRMOpened.id)).parentElement; //the replacement above means the .FTTCmt from processElementArray is no longer attached to the DOM. We update it in case you want to reply to yourself.
						});
					}, 2000);
				} else {
//					FTT.debug('locator not found in wikitext, you are probably editing some comment without one. This is incompatible with parse-in-place');
					$('#content').addClass('FTTEaseIn FTTPurpleBG'); //can't open new reply forms
					FTT.mustReload = true; //but we can't until we have edit confirmation
				}
			} else if ( document.getElementById('FTTForm-' + FTT.PRMOpened.id) ) {
//				FTT.debug('preview for new comment');
				FTT.previewAfterParseBoxDiv = document.createElement('div');
				document.getElementById('FTTForm-' + FTT.PRMOpened.id).parentElement.insertBefore(FTT.previewAfterParseBoxDiv,document.getElementById('FTTForm-' + FTT.PRMOpened.id));
				FTT.previewAfterParseBoxDiv.outerHTML = FTT.previewAfterParseBox.replace('FTTPreviewAfterPost FTTPurpleBG','FTTPreviewAfterPost FTTPurpleBG').replace(/(<h[1-6][^>]*class=")/,'$1 FTTPreviewHeader') + '</div>';
				$('.FTTPreviewHeader').removeClass('ext-discussiontools-init-section');
				$('.FTTPreviewHeader>a.oo-ui-buttonElement-button').remove(); // DT subscribe button
			}
			if ( FTT.postCommentSuccess == true && FTT.wrongLocatorID != '' && FTT.newCommentID != '#' ) {
//				FTT.debug('preview parsing took longer than editing (this is rare), patch up preview');
				if ( FTT.PRMOpened.type != 'edit' && document.getElementById(FTT.wrongLocatorID) ) {
//					FTT.debug('preview for fresh comment, fixing ID');
					document.getElementById(FTT.wrongLocatorID).id = D2(FTT.newCommentID.slice(1));
					document.getElementById('PreviewAfterPost').id = 'PreviewAfterPost-' + D2(FTT.newCommentID.slice(1)); //keep IDs unique if a user posts multiple comments without reloading
				}
				$('.FTTPurpleBG').removeClass('FTTPurpleBG');
				var DelayAddRL1 = setInterval(function(){clearInterval(DelayAddRL1);
					FTT.addReplyLinkTo(FTT.previewPostedParams); //adds edit link
				}, 2500); //must be higher than the delay for DelayedAssociateElementParse
				$('.FTTLinks').removeClass('FTTNoDisplay');
			}
			if ( apiResponse.parse.text.match('mw-collapsible') ) {
				FTT.makeCollapsible();
			}
		} else if (params.action == 'parse' && (mode == 'preview' || mode == 'livepreview' || mode == 'diff') ) {
//			FTT.debug('Loading preview');
			FTT.smartViewingDiff = 0;
			if ( ! $('#FTTPreviewBox')[0] ) {
//				FTT.debug('form closed? trash preview');
				FTT.doPreviewInProgress = 0;
				FTT.runSmartLivePreview = false;
				return;
			}
			FTT.UIPreviewButton.setDisabled(false);
			FTT.reappliedSmart = FTT.reApplySmart(apiResponse.parse.text); //re-apply smartLivePreview updates since making the parsed preview request
			$('#FTTPreviewBox .mw-parser-output')[0].outerHTML = FTT.reappliedSmart.replace(/<span data\-mw\-comment[^>]*>(<span data\-mw\-comment\-[^>]*><\/span>)*<\/span>/,'');
			$('#FTTPreviewBox .ext-discussiontools-init-section-subscribe').remove();
			$('#FTTPreviewBox .ext-discussiontools-init-section').removeClass('ext-discussiontools-init-section');
			if ( apiResponse.parse.text.match('mw-collapsible') ) {
				FTT.makeCollapsible();
			}
			FTT.doPreviewInProgress = 0;
			if ( mode == 'livepreview' ) {
				if ( FTT.runSmartLivePreview ) {
					FTT.runSmartLivePreview = false;
					FTT.smartLivePreview();
				}
				if ( params.text != '' && !FTT.formChanged && FTT.firstPreview && (( FTT.settings.previewAboveFull && ['editFullPage','heading'].includes(FTT.PRMOpened)) || (FTT.settings.previewAboveOther && !['editFullPage','heading'].includes(FTT.PRMOpened) ) ) ) {
					FTT.firstPreview = 0;
					$(document).ready(function() {
						FTT.UIReplyButton.scrollElementIntoView();
					});
				}
			} else {
				FTT.preBoxGBCR = $('#FTTPreviewBox')[0].getBoundingClientRect();
				if ( (FTT.preBoxGBCR.y > ( window.innerHeight * 0.8 )) || ((FTT.preBoxGBCR.y+FTT.preBoxGBCR.height)<20) ) {
					$('#FTTPreviewBox')[0].scrollIntoView({behavior:'smooth',block:'start',inline:'nearest'});
				}
			}
//			FTT.debug('server answered with preview in ' + ( FTT.apiTimeEnd - FTT.apiTimeStart ) + 'ms');
			if ( ! FTT.formChanged && mode == 'livepreview' && FTT.PRMOpened.highlightRef ) {
//				FTT.debug('preview can cause jump. re-highlight ref');
				FTT.HLSelectRef(FTT.refIndexStart,FTT.refIndexEnd);
			}
		} else if (params.action == 'edit' && apiResponse.edit.result == 'Success' ) {
			FTT.postReplyInProgress[FTT.PRMOpened.id] = false;
			FTT.removeOverlay();
//			FTT.debug('page edit succeeded:');
//			FTT.debug(apiResponse);
			if ( $('#FTTScrewed.FTTSubmitTimeout')[0] ) { //close timeout warning popup
				$('.oo-ui-window-frame .oo-ui-buttonElement-button:eq(0)')[0].click();
				mw.notify(FTT.msgs.nevermind);
			}
			if ( FTT.settings.checkNewComments ) {
				FTT.newSubmitted = (params.appendtext || params.text).split(/\n/);
				for ( FTT.newSubmittedInt=0;FTT.newSubmittedInt<FTT.newSubmitted.length;FTT.newSubmittedInt++) { //FTT.submittedLines keeps a record of all submitted unique lines, so when checking for changed lines Factotum can ignore anything you submitted yourself
					if ( ! FTT.submittedLines.includes(FTT.newSubmitted[FTT.newSubmittedInt]) ) {
						FTT.submittedLines.push(FTT.newSubmitted[FTT.newSubmittedInt]);
					}
				}
			}
			if ( FTT.settings.saveDraft ) {
					FTT.saveDraft(FTT.PRMOpened, 'remove');
			}
			delete FTT.newInitDate;
			if ( apiResponse.edit.newtimestamp ) {
				FTT.newInitDate = new Date(apiResponse.edit.newtimestamp).getTime()+1000; //will only be applied if parse-in-place is used. This is used to prevent false positives due to server and client clocks not being synchronized when when checking for new comments
			}
			if ( FTT.wipeSectionParams ) {
//				FTT.debug('copied a section to another page, wiping section from original page');
				api.postWithEditToken(FTT.wipeSectionParams).then(function(data) {
//					FTT.debug('section was wiped from ' + FTT.PRMOpened.pageTitle);
//					FTT.debug(data);
					FTT.movedSectionLocation = M1('wgArticlePath').replace('$1',D1(FTT.postCommentParams.title + FTT.wipeSectionAnchor));
					if ( ( FTT.settings.redirAfterMove && FTT.settings.debug ) || $('#FTTScrewed')[0] ) {
//						FTT.debug('supressed redirecting to '+FTT.movedSectionLocation);
					} else if ( FTT.settings.redirAfterMove ) {
						FTT.skipWarnExit = true; //prevents "sure you wanna leave the page?" popup
						window.location = FTT.movedSectionLocation;
					} else {
						FTT.parsePageInPlace(FTT.newInitDate);
					}
				}, function ( code, data ) { FTT.APIError(code, data);
				});
			}
			if ( FTT.PRMOpened.redirect ) {
				FTT.skipWarnExit = true;
				window.location = M1('wgArticlePath').replace('$1',M1('wgPageName'))+'#'+FTT.flattenWikiText(FTT.UITextInputTitle.getValue()).replace(/ /g,'_');
				return;
			}
			if ( FTT.PRMOpened.forcepurge || FTT.PRMOpened.pageTitle.replace(/ /g,'_') != M1('wgPageName') ) {
				FTT.purgeParams = {'action':'purge','format':'json','titles':[M1('wgPageName')]};
				api.post(FTT.purgeParams).then(function(data) {
//					FTT.debug('purged cache of ' + FTT.PRMOpened.pageTitle);
//					FTT.debug(data); 
				}, function ( code, data ) {
//					FTT.debug('purging of ' + FTT.PRMOpened.pageTitle + ' failed:' + code);
//					FTT.debug(data);
				});
			}
			if ( FTT.oTT && FTT.oTT.blockMod && FTT.oTT.blockMod.getValue() != '' ) {
				FTT.oTT.blockModSummarySpace = '';
				if ( FTT.UITextInputSummary.getValue().trim() != '' ) {
					FTT.oTT.blockModSummarySpace = ' ';
				}
				FTT.oTT.blockModSummary = FTT.UITextInputSummary.getValue().trim() + FTT.oTT.blockModSummarySpace + '[[' + FTT.B1.Diff + '/' + apiResponse.edit.newrevid + ']]';
				FTT.blockModParams = {format:'json',assert:FTT.assert,user:M1('wgRelevantUserName'),reason:FTT.oTT.blockModSummary,autoblock:true,nocreate:true};
				if ( FTT.oTT.blockMod.getValue() == 'unblock' ) {
					FTT.blockModParams.action = 'unblock';
				} else {
					FTT.blockModParams.action = 'block';
					FTT.blockModParams.expiry = FTT.oTT.blockMod.getValue();
					FTT.blockModParams.noemail = FTT.oTT.blockModmail.isSelected();
					if ( ! FTT.oTT.blockModtalk.isSelected() ) {
						FTT.blockModParams.allowusertalk = true;
					}
					FTT.blockModParams.reblock = true;
				}
				api.postWithEditToken( FTT.blockModParams ).then( function ( apiResponse ) {
					if (apiResponse.block) {
						mw.notify(FTT.B1.blockedtitle,{autoHide:false});
					} else if (apiResponse.unblock) {
						mw.notify($(FTT.B1.unblocked.replace(/\$1/g,D1(FTT.blockModParams.user.replace(/ /g,'_')))+'<span></span>'),{autoHide:false});
					}
				}, function ( code, data ) { FTT.APIError(code, data);});
			}
			FTT.newCommentID = '';
			if ( FTT.settings.useLocator && ['comment','FCL','newsection','newheading'].indexOf(FTT.PRMOpened.type) != -1 ) {
				FTT.newCommentTimestamp = new Date(apiResponse.edit.newtimestamp).getTime().toFixed().slice(0,-3);
				if ( typeof FTT.locatorID != 'undefined' && FTT.locatorID.match(/{{subst:#time:xNU}}/) ) {
					FTT.newCommentID = '#' + FTT.locatorID.replace('{{subst:#time:xNU}}', FTT.newCommentTimestamp);
				}
			} else if ( FTT.settings.useLocator && FTT.PRMOpened.type == 'edit' ) {
				if ( document.getElementById(FTT.PRMOpened.id) ) {
					FTT.newCommentID = '#' + D1(FTT.PRMOpened.id);
				} else if ( document.getElementById('FTTLink-' + FTT.escapeHTML(FTT.PRMOpened.id)) ){
					FTT.newCommentID = '#' + D1('FTTLink-' + FTT.escapeHTML(FTT.PRMOpened.id));
				}
			} else if ( FTT.PRMOpened.type == 'heading' && FTT.PRMOpened.subtype == 'edit' ) {
				FTT.newCommentID = '#' + FTT.PRMOpened.sectionTitle.replace(/ /g,'_');
			} else {
				FTT.newCommentID = '';
			}
			if ( FTT.PRMOpened.freshindent && FTT.commentTextIndent && ! FTT.commentTextIndent.match(/┌/) ) {
				FTT.freshindent = FTT.PRMOpened.freshindent +1;
			} else if ( FTT.commentTextIndent && FTT.commentTextIndent.match(/┌/) ) {
				FTT.freshindent = (1 - FTT.cmtIndentHTML);
			} else {
				FTT.freshindent = 1;
			}
			if ( FTT.highestRLPInt ) {
				FTT.highestRLPInt++;
			} else if ( FTT.PRM ) {
				FTT.highestRLPInt = Number(Object.keys(FTT.PRM)[Object.keys(FTT.PRM).length-1]) +1;
			} else {
				FTT.PRM = {};
				FTT.highestRLPInt = 0;
			}
			if ( FTT.PRMOpened.type == 'edit' ) {
				FTT.newOrigTimestamp = FTT.PRMOpened.origTimestamp;
			} else {
				FTT.newOrigTimestamp = FTT.newCommentTimestamp + FTT.commentMilliseconds;
			}
			FTT.previewPostedParams = {
				'int':FTT.highestRLPInt,
				'type':'comment',
				'subtype':'locator',
				'id':D2(FTT.unescapeHTML(FTT.newCommentID.slice(1))),
				'origid':FTT.PRMOpened.id,
				'pageTitle':FTT.PRMOpened.pageTitle,
				'pageTitleInt':FTT.PRMOpened.pageTitleInt,
				'origReplyTo':FTT.userName,
				'origTimestamp':FTT.newOrigTimestamp,
				'origTimestampTextNode':FTT.newOrigTimestamp,
				'sectionTitle':FTT.PRMOpened.sectionTitle,
				'seq':0,
				'sectionseq':FTT.PRMOpened.sectionseq,
				'freshcomment':true,
				'freshindent':FTT.freshindent
			};
			FTT.PRM[FTT.previewPostedParams.int] = FTT.previewPostedParams; //registering the new comment in the internal administration
			if ( FTT.newCommentID.slice(1) != '' ) {
				var DelayedAssociateElementEdit = setInterval(function(){
					clearInterval(DelayedAssociateElementEdit);
					$(document).ready(function() {
						FTT.processElementArray[FTT.previewPostedParams.int] = document.getElementById(D2(FTT.newCommentID.slice(1))); //associating a DOM element with the new comment
					});
				}, 2000);
			}
			if ( FTT.settings.stalkAutoSub && FTT.signatureAdded && FTT.previewPostedParams.origid != 'FTT-comment-link' ) {
				FTT.stalkSubscribe(FTT.previewPostedParams);
			}
			if ( ['link','reload'].includes(FTT.settings.afterPost) ) {
				document.getElementById('FTTForm-' + FTT.PRMOpened.id).outerHTML = document.getElementById('FTTForm-' + FTT.PRMOpened.id).outerHTML + '<span id="thanksForUsing"><a href="?' + Math.floor(Math.random()*100000) + FTT.newCommentID + '">' + FTT.msgs.commentDone + '</a>' + FTT.msgs.commentPostedThankYou + '</span>'; // the ? forces page reload, # provides anchor to the comment that was commented on
			}
			FTT.cancelReply('editDone'); //closes the reply form
			if (FTT.PRMOpened.subtype == 'InputBox' && FTT.PRMOpened.pageTitle.replace(/_/g,' ') != FTT.PN) {
				var DelayedReload = setInterval(function(){clearInterval(DelayedReload);
					window.location = mw.config.get('wgArticlePath').replace('$1',FTT.PRMOpened.pageTitle.replace(/ /g,'_'))+'#'+params.sectiontitle.replace(/ /g,'_');
				}, 2000);
				return;
			}
			if ( FTT.mustReload == true || M1('wgCurRevisionId') == 0 || ['reload','parsepage'].includes(FTT.settings.afterPost) || (! ['comment','edit','newsection','newheading'].includes(FTT.PRMOpened.type) && FTT.settings.afterPost != 'link' ) || ['newsection','newheading'].includes(FTT.PRMOpened.type) && FTT.settings.afterPost == 'parsecmtonly' ) {
				if ( FTT.newCommentID != '' ) {
					FTT.skipWarnExit = true;
					window.location = FTT.newCommentID;
				}
				if ( FTT.settings.afterPost == 'reload' || M1('wgCurRevisionId') == 0 ) {
//					if(FTT.settings.debug){mw.loader.using(['oojs-ui']).then(function(){OO.ui.confirm('Reload?').done(function(a){if(a){FTT.skipWarnExit = true;location.reload();}});});}else{//FTT.debug
						var DelayedReload2 = setInterval(function(){clearInterval(DelayedReload2);
							FTT.skipWarnExit = true;
							location.reload();
						}, 2000); //if we reload immediately we may not get served the most recent revision with our comment
//					}//FTT.debug
				} else if ( FTT.settings.afterPost != 'link' ) {
					var DelayParse = setInterval(function(){clearInterval(DelayParse);
						FTT.parsePageInPlace(FTT.newInitDate);
					}, 2000);
				}
			} else if ( ['parse','parsecmtonly'].includes(FTT.settings.afterPost) ) {
//				FTT.debug('previewPostedParams:');
//				FTT.debug(FTT.previewPostedParams);
				FTT.postCommentSuccess = true;
				$('.FTTPurpleBG').removeClass('FTTPurpleBG'); //it clears all the purple
				if ( FTT.postedCommentParsed && document.getElementById(FTT.wrongLocatorID) && FTT.newCommentID != '#' ) {
//					FTT.debug('editing took longer than preview parsing (this is common), patch up preview');
					document.getElementById(FTT.wrongLocatorID).id = FTT.escapeHTML(D2(FTT.newCommentID.slice(1)));
					document.getElementById('PreviewAfterPost').id = 'PreviewAfterPost-' + FTT.escapeHTML(D2(FTT.newCommentID.slice(1))); //keep IDs unique if a user posts multiple comments without reloading
					var DelayAddRL2 = setInterval(function(){clearInterval(DelayAddRL2);
						FTT.addReplyLinkTo(FTT.previewPostedParams); //adds edit link
					}, 2500);
				}
			}
			if ( $('.FTTForm')[0] ) {
//				FTT.debug('the form hadn\'t been removed, possibly due to some "awww shit", possibly triggered by a timeout. Removing the form now, including any attached "awww shit" as apparently the edit came through after all.');
				$('.FTTForm')[0].remove();
				mw.notify(FTT.msgs.nevermind);
			}
//			FTT.debug('edit succeeded, empty input fields');
			FTT.setValue('');
			FTT.UITextInputTitle.setValue('');
			FTT.UITextInputSummary.setValue('');
		}
	}, function ( code1, data ) {
		if (params.action == 'parse' ) {
			FTT.doPreviewInProgress = 0;
		}
		if ( data.error && ['abusefilter-disallowed','abusefilter-warning','spamblacklist'].includes(data.error.code) ) {
			if ( data.error.info.match(/^⧼.*⧽$/) || data.error.code == 'spamblacklist' ) {
				FTT.errParams = {action:'parse',page:'MediaWiki:'+data.error.info.slice(1,-1),format:'json',disablelimitreport:true,prop:['text']};
				if ( data.error.code == 'spamblacklist' ) {
					FTT.errParams.title = FTT.PN;
					FTT.errParams.text = data.error.info.replace(/ ([\\*]+) /g,'\n$1 '); //oddly the API serves the error without newlines..
					FTT.errParams.wrapoutputclass = 'FTTSpamPopup';
					delete FTT.errParams.page;
				}
				api.post(FTT.errParams).then(function(AFdata){
						FTT.popup(AFdata.parse.text['*']);
				}, function ( code2, data2 ) {
					data2.error.info = data2.error.info + ' (AF)';
					FTT.APIError(code2+' AF', data2);
				});
			} else {
				FTT.popup(data.error.info);
			}
			FTT.disableForm(false);
			if ( $('#PreviewAfterPost')[0] && $('#PreviewAfterPost')[0].previousElementSibling && ['H1','H2'].includes($('#PreviewAfterPost')[0].previousElementSibling.nodeName) ) {
				$('#PreviewAfterPost')[0].previousElementSibling.remove();
			}
			$('#PreviewAfterPost').remove();
			return;
		}
		if ( ( data.error && data.error.code == 'editconflict' ) || ( typeof data.edit != 'undefined' && data.edit.result == 'Success' && ! data.edit.newtimestamp ) ) { //that nonsense second condition is a workaround for T299809
			FTT.editConflictRetries = FTT.editConflictRetries - 1;
			if ( data.edit && data.edit.result == 'Success' && ! data.edit.newtimestamp && FTT.editConflictRetries > 0 ) {
				FTT.editConflictRetries = 1; //damage control in case there is a legit situation that triggers this, only retry once in case of the nonsense condition
			}
			if ( data.edit && data.edit.result == 'Success' && ! data.edit.newtimestamp ) {
//				FTT.debug('encountered either an edit conflict or FTT is performing null edits. (which it never should) So which is it? Can somebody PLEASE look at T299809? ' + FTT.editConflictRetries + ' tries left.');
			 } else {
//				FTT.debug('encountered edit conflict. ' + FTT.editConflictRetries + ' tries left.');
			 }
			if ( FTT.editConflictRetries >= 1 ) {
				FTT.postReply1(FTT.PRMOpened,'editconflict');
			} else {
//				FTT.debug('giving up');
			}
		}
		if ( ( typeof data == 'undefined' || typeof data.error == 'undefined' ) && FTT.retriedNetworkError < 2 ) {
			FTT.retriedNetworkError++;
			var DelayedAPIRetry = setInterval(function(){
				clearInterval(DelayedAPIRetry);
				FTT.doAPICall(params, mode, callback );
			}, 2000);
			return;
		}
		FTT.APIError(code1, data);
	});
};
FTT.APIError = function(code,data,extra) {
//	FTT.debug(code);
//	FTT.debug(data);
	if ( typeof data != 'undefined' && typeof data.error != 'undefined' ) {
		FTT.addScrewedLink(data.error.code,'API: "' + data.error.info + '"');
	} else {
		FTT.APIErrorExtra = '';
		if(extra){FTT.APIErrorExtra = ' '+extra;}
//		if ( FTT.settings.debug ) { window.alert('no API response: '+extra); }//FTT.debug
		FTT.addScrewedLink('no_response','Received no response from API. Is your internet plugged in?'+FTT.APIErrorExtra); // may need to disable again if this is still popping up when leaving a page
	}
};
E1 = function(text) {
	if ( ['string','number','object'].includes(typeof text) ) {
		return mw.util.escapeRegExp(text.toString()); //in case the input is a number (happens with timestamps)
	} else {
		return '';
	}
};
FTT.escapeRegExp = E1;
FTT.escapeReplacement = function(text) {
	return text.replace(/\$/g, '$$$$');
};
FTT.escapeHTML = function(text) {
//	FTT.debug('escaping HTML for "' + text + '"');
	if ( typeof text == 'undefined' ) {
		return '';
	} else if ( typeof text != 'string' ) {
		text = text.toString();
	}
	return text.replace(/\&/g, '&amp;').replace(/\'/g, '&apos;').replace(/\"/g, '&quot;').replace(/</g, '&lt;').replace(/\>/g, '&gt;');
};
FTT.unescapeHTML = function(text) {
//	FTT.debug('unescaping HTML for "' + text + '"');
	if ( typeof text == 'undefined' ) {
		return '';
	} else if ( typeof text != 'string' ) {
		text = text.toString();
	}
	return text.replace(/\&amp;/g, '&').replace(/\&apos;/g, '\'').replace(/\&quot;/g, '"').replace(/\&lt;/g, '<').replace(/\&gt;/g, '>');
};
FTT.reApplySmart = function(text,intSmart,intEl,intTextNode,testChild) {
	for (intSmart=0;intSmart<FTT.smartOldVals.length;intSmart++) {
//		FTT.debug('reApplySmart: replacement #'+intSmart+' of saved smartLivePreview changes');
		text = text.replace(FTT.smartOldVals[intSmart],FTT.smartNewVals[intSmart]);
	}
	FTT.smartOldVals = [];
	FTT.smartNewVals = [];
	return text;
};
FTT.copyToClipBoard = function(permaLinkInt,mode,text,textEl,toWiki) {
	if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
		try {
			if ( mode == 'nonPerma' ) {
				if ( toWiki ) {
					FTT.nonPermaLinkText[permaLinkInt].setValue(FTT.rewritunUrUrlz(FTT.nonPermaLinkText[permaLinkInt].getValue()));
				}
				FTT.nonPermaLinkText[permaLinkInt].select();
			} else if ( mode == 'perma' ) {
				if ( toWiki ) {
					FTT.permaLinkText[permaLinkInt].setValue(FTT.rewritunUrUrlz(FTT.permaLinkText[permaLinkInt].getValue()));
				}
				FTT.permaLinkText[permaLinkInt].select();
			} else if ( mode == 'export' ) {
				FTT.sEl.exportSettings.select();
			} else if ( mode == 'text' ) {
				textEl = document.createElement('input');
				textEl.value = text;
				textEl.classList.add('FTTTempText');
				textEl.style.opacity = '0';
				$('body').append(textEl);
				textEl.select();
				var copyDone = setInterval(function () {
					clearInterval(copyDone);
					$('.FTTTempText').remove();
				},500);
			}
			document.execCommand("copy");
			mw.notify(FTT.B1['mw-widgets-copytextlayout-copy-success']);
			if ( mode != 'export' && mode != 'text' ) {
				FTT.permaLinkLayout[permaLinkInt].toggle(false);//$('#FTTpermaLink-' + permaLinkInt).remove();
				$('#genLink-' + permaLinkInt).removeClass('FTTNoDisplay');
			}
		} catch (fubar) {
			mw.notify(FTT.B1['mw-widgets-copytextlayout-copy-fail'] + fubar,{type:'error'});
//			FTT.debug('could not copy ' + FTT.permaLink + ' to clipboard: ' + fubar);
		}
	}
};
FTT.permaLinkIDs = {};
FTT.permaLinkText = {};
FTT.permaLinkCutButton = {};
FTT.permaLinkCutButtonWiki = {};
FTT.permaLinkCancel = {};
FTT.permaLinkLayout = {};
FTT.nonPermaLinkText = {};
FTT.nonPermaLinkCutButton = {};
FTT.nonPermaLinkCutButtonWiki = {};
FTT.nonPermaLinkLayout = {};
FTT.genPermaLink = function(PRM,mode) {
//	FTT.debug('genPermaLink: get ID for permalink or to give thanks');
//	FTT.debug(PRM);
	PRM = FTT.addPageAndSectionTitleToRPL(PRM);
	delete FTT.permaLink;
	if ( PRM.type != 'newheading' && mode == 'link' && $('.FTTForm')[0] && FTT.settings.dateLinksIconAlt && PRM.pageTitle == FTT.PRMOpened.pageTitle && PRM.sectionTitle == FTT.PRMOpened.sectionTitle && PRM.sectionseq == FTT.PRMOpened.sectionseq ) {
//		FTT.debug('open reply form in this section found, insert link in form instead');
		FTT.addAnchorTimestamp = FTT.sigDateToMachineReadable(PRM.origTimestamp,true);
		FTT.addAnchorCmtLink = FTT.unixTimeToFlatDate(FTT.addAnchorTimestamp) + ' ' + PRM.origReplyTo.replace(/_/g,' ');
		FTT.insertMarkup('cI','','[[#' + FTT.addAnchorCmtLink + ']]','');
		return;
	}
	if ( PRM.subtype == 'legacy' || mode == 'thanks' ) {
		if ( PRM.subtype == 'legacy' ) {
//			FTT.debug('genPermaLink: timestamp from legacy: ' + PRM.origTimestamp);
			FTT.rvStartPermaLink = new Date(FTT.sigDateToMachineReadable(PRM.origTimestamp)).toISOString();
		} else if ( PRM.subtype == 'locator' ) {//locator permalink is oldid. locator thanks requires, just like legacy, a diff id
//			FTT.debug('genPermaLink: timestamp from locator: ' + PRM.origTimestamp);
			FTT.rvStartPermaLink = new Date(Number(PRM.origTimestamp)).toISOString();
		}
		FTT.currentPageTextParams = {'action':'query','format':'json','titles': PRM.pageTitle,'prop':'revisions','rvstart':FTT.rvStartPermaLink,'rvlimit':1,'rvdir':'newer'};
	} else if ( typeof FTT.permaLinkIDs[PRM.pageTitle] == 'number' ) {
		FTT.permaLinkID = FTT.permaLinkIDs[PRM.pageTitle];
		FTT.insertPermaLinkForm(PRM,mode,FTT.permaLinkID);
		return;
	} else if ( PRM.pageTitle != FTT.PN ) {
		FTT.currentPageTextParams = {'action':'query','format':'json','titles': PRM.pageTitle,'prop':'revisions'};
	} else {
		FTT.permaLinkID = M1('wgCurRevisionId');
		FTT.insertPermaLinkForm(PRM,mode,FTT.permaLinkID);
		return;
	}
	api.get(FTT.currentPageTextParams).then(function(currentPageRevID) {
//		FTT.debug(currentPageRevID);
		if ( ! currentPageRevID.query.pages[-1] ) {
//			FTT.debug('genPermaLink: got revision info to create permalink');
			FTT.permaLinkID = currentPageRevID.query.pages[ Object.keys(currentPageRevID.query.pages)[0] ].revisions[0].revid;
//			FTT.debug('genPermaLink: permaLinkID is '+FTT.permaLinkID);
			FTT.permaLinkIDs[PRM.pageTitle] = FTT.permaLinkID;
			if ( mode == 'thanks' ) {
				FTT.thankParams = {'action':'thank','format':'json','rev':FTT.permaLinkID,'source':'FTT!'};
				api.postWithEditToken(FTT.thankParams).then(function(data) {
					if ( FTT.settings.RLmasq ) {
						$('#thankLink-' + PRM.int)[0].style = 'color:initial';
						$('#thankLink-' + PRM.int + ' span')[0].innerHTML = FTT.thankedLinkInnerHTML;
					} else {
						$('#thankLink-' + PRM.int + ' span').removeClass('FTTSVGHeartIcon').addClass('FTTSVGHeartRedIcon');
						$('#thankLink-' + PRM.int + ' span.FTTScreenReaderLabel')[0].dataset.content = FTT.B1['thanks-button-action-completed'].replace('$1',PRM.origReplyTo);
						$('#thankLink-' + PRM.int + '')[0].title = FTT.B1['thanks-button-action-completed'].replace('$1',PRM.origReplyTo);
					}
					FTT.thankedComments = FTT.testValidJSON(FTT.getItemLS('FTTThanks'));
					if ( ! FTT.thankedComments ) {
//						FTT.debug('genPermaLink: no FTTThanks localStorage object found, will initiate one');
						FTT.thankedComments = {};
					}
					if ( Object.keys(FTT.thankedComments).length > 500 ) {//remove the two oldest thanks given, only the last 500 thanked edits are remembered so localStorage can't overflow
						delete FTT.thankedComments[ Object.keys(FTT.thankedComments).sort()[0] ];
						delete FTT.thankedComments[ Object.keys(FTT.thankedComments).sort()[1] ];
					}
//					FTT.debug('genPermaLink: add ' + PRM.id + ' to localStorage.FTTThanks');
					FTT.thankTimestamp = new Date().getTime();
					FTT.thankedComments[FTT.thankTimestamp] = PRM.id;
//					FTT.debug('genPermaLink: set localStorage item');
					FTT.setItemLS('FTTThanks',FTT.pack(FTT.thankedComments,'UTF16'));
				}, function ( code, data ) {
					if ( data.error && data.error.info ) {
						$('#thankLink-' + PRM.int).removeClass('FTTNoDisplay');
						mw.notify(data.error.info); //it's just thanks, doesn't compromise the reliability of this script if it doesn't work, so no big "awww shit" here
					}
				});
				return;
			} else {
				FTT.insertPermaLinkForm(PRM,mode,FTT.permaLinkID);
			}
		} else {
			mw.notify(FTT.B1.actionfailed,{type:'error'});
		}
	}, function ( code, data ) { FTT.APIError(code, data);
	});
};
FTT.insertPermaLinkForm = function(PRM,mode,permaLinkID) {
//	FTT.debug('insertPermaLinkForm: start');
	if ( FTT.permaLinkLayout[PRM.int] ) {
		FTT.permaLinkLayout[PRM.int].toggle();
		if ( FTT.permaLinkLayout[PRM.int].isVisible() ) { //this check is needed otherwise the selection won't always work properly
			FTT.permaLinkText[PRM.int].select();
		}
		return;
	}
	if ( PRM.subtype == 'locator' ) {
		FTT.permaLinkAnchor = PRM.id.replace(/ /g, '_');
	} else if ( PRM.type == 'newheading' ) {
		FTT.permaLinkAnchor = PRM.sectionTitle;
		if ( PRM.sectionseq > 0 ) {
			FTT.permaLinkAnchor = FTT.permaLinkAnchor + '_' + ( PRM.sectionseq + 1);
		}
	}
	if ( PRM.subtype == 'legacy' ) {
		FTT.permaLink = FTT.serverName + M1('wgArticlePath').replace('$1',FTT.B1.Diff + '/' + permaLinkID+'#mw-diff-ntitle1');
	} else {// section permalinks are "ugly" (not using Special:Permalink) because the "ugly" variant includes the page title which FTT can use while rewriting.
		FTT.permaLink = FTT.serverName + M1('wgScriptPath') + '/index.php?title=' + D1(PRM.pageTitle.replace(/ /g, '_')) + '&oldid=' + permaLinkID + '#' + FTT.permaLinkAnchor.replace(/ /g,'_');
		FTT.nonPermaLink = FTT.serverName + M1('wgArticlePath').replace('$1',D1(PRM.pageTitle.replace(/ /g, '_'))) + '#' + FTT.permaLinkAnchor.replace(/ /g,'_');
	}
	mw.loader.using( [ 'oojs-ui-core' ] ).then( function () {
		FTT.permaLinkText[PRM.int] = new OO.ui.TextInputWidget( {
			value: FTT.permaLink,
			classes: ['FTTpermaLinkText'],
			title: FTT.B1.permalink,
			readOnly: true
		} );
		FTT.permaLinkText[PRM.int].$input.attr('aria-label',FTT.B1.permalink);
		FTT.permaLinkCutButton[PRM.int] = new OO.ui.ButtonWidget( {
			label:FTT.B1['mw-widgets-copytextlayout-copy']+ ' ('+FTT.B1.permalink+')',
			title:FTT.B1['mw-widgets-copytextlayout-copy']+ ' ('+FTT.B1.permalink+')',
			flags:['progressive','primary']
		} );
		FTT.permaLinkCutButton[PRM.int].on('click', function() { FTT.copyToClipBoard(PRM.int,'perma'); });
		FTT.permaLinkCutButtonWiki[PRM.int] = new OO.ui.ButtonWidget( {
			label:FTT.B1['mw-widgets-copytextlayout-copy']+ ' ('+FTT.B1['content-model-wikitext']+')',
			title:FTT.B1['mw-widgets-copytextlayout-copy']+ ' ('+FTT.B1['content-model-wikitext']+')',
			flags:['progressive','primary']
		} );
		FTT.permaLinkCutButtonWiki[PRM.int].on('click', function() { FTT.copyToClipBoard(PRM.int,'perma',null,null,1); });
		FTT.permaLinkCancel[PRM.int] = new OO.ui.ButtonWidget( {
			label:FTT.B1.cancel,
			title:FTT.B1.cancel,
			flags:[FTT.cancelFlag],
		} );
		FTT.permaLinkCancel[PRM.int].on('click', function() { FTT.permaLinkLayout[PRM.int].toggle(false);});
		FTT.permaLinkLayout[PRM.int] = new OO.ui.HorizontalLayout( {
			items: [FTT.permaLinkText[PRM.int],FTT.permaLinkCutButton[PRM.int],FTT.permaLinkCutButtonWiki[PRM.int],FTT.permaLinkCancel[PRM.int]],
			id: 'FTTpermaLink-' + PRM.int,
			classes: ['FTTpermaLink','FTTLinks','FTTLeftRightMargin']
		} );
//		FTT.debug('add field with permalink to processElementArray #' + PRM.int + ' with ID FTTLink-' + FTT.escapeHTML(PRM.id).replace(/([\-\.:%\/\(\)\!\?])/g,'\\$1'));
		if ( PRM.type == 'newheading' ) {
			FTT.insertAfter(FTT.processElementArray[PRM.int].parentElement,FTT.permaLinkLayout[PRM.int].$element[0]);
		} else {
			FTT.appendToFirstBlockParent(document.getElementById('FTTLink-' + FTT.escapeHTML(PRM.id)),FTT.permaLinkLayout[PRM.int].$element[0],'permalink');
		}
		if ( FTT.settings.dateLinksIconSectExtra && PRM.type == 'newheading' ) {
			FTT.nonPermaLinkText[PRM.int] = new OO.ui.TextInputWidget( {
				value: FTT.nonPermaLink,
				classes: ['FTTpermaLinkText'],
				title: FTT.msgs.regularLinkTip,
				readOnly: true
			} );
			FTT.nonPermaLinkText[PRM.int].$input.attr('aria-label',FTT.msgs.regularLinkTip);
			FTT.nonPermaLinkCutButton[PRM.int] = new OO.ui.ButtonWidget( {
				label:FTT.B1['mw-widgets-copytextlayout-copy']+ ' ('+FTT.msgs.regularLinkTip+')',
				title:FTT.B1['mw-widgets-copytextlayout-copy']+ ' ('+FTT.msgs.regularLinkTip+')',
				flags:['progressive','primary']
			} );
			FTT.nonPermaLinkCutButton[PRM.int].on('click', function() { FTT.copyToClipBoard(PRM.int,'nonPerma'); });
			FTT.nonPermaLinkCutButtonWiki[PRM.int] = new OO.ui.ButtonWidget( {
				label:FTT.B1['mw-widgets-copytextlayout-copy']+ ' ('+FTT.B1['content-model-wikitext']+')',
				title:FTT.B1['mw-widgets-copytextlayout-copy']+ ' ('+FTT.B1['content-model-wikitext']+')',
				flags:['progressive','primary']
			} );
			FTT.nonPermaLinkCutButtonWiki[PRM.int].on('click', function() { FTT.copyToClipBoard(PRM.int,'nonPerma',null,null,1); });
			FTT.nonPermaLinkLayout[PRM.int] = new OO.ui.HorizontalLayout( {
				items: [FTT.nonPermaLinkText[PRM.int],FTT.nonPermaLinkCutButton[PRM.int],FTT.nonPermaLinkCutButtonWiki[PRM.int]]
			} );
			$('#FTTpermaLink-' + PRM.int).append('<br />');
			$('#FTTpermaLink-' + PRM.int).append(FTT.nonPermaLinkLayout[PRM.int].$element);
		}
		FTT.permaLinkText[PRM.int].select();
	});
};
FTT.giveThanks = function(PRM) {
	if ( $('.mw-selflink:hover,.FTTSVGHeartRedIcon:hover')[0] ) {
//		FTT.debug('you already thanked this, after you loaded the page');
		return;
	}
//	FTT.debug('popup to ask for thank confirmation'); //probably better for screenreaders anyway
	mw.loader.using(['oojs-ui-core','oojs-ui-windows']).then(function(){
		FTT.FFDarkMode();
		$('#FTTOverlay').addClass('FTTNoDisplay');
		OO.ui.confirm(FTT.B1['flow-thanks-confirmation-special']).done( function(confirmd) {
			FTT.FFDarkMode(1);
			$('#FTTOverlay').removeClass('FTTNoDisplay');
			if (confirmd) {
				FTT.genPermaLink(PRM,'thanks');
			}
		});
	});
};
FTT.initRewritunRegExps = function(projects,urlPre,pre,pretitle,title,anchor,AL) {
	if ( ! FTT.B1 || ! FTT.B1.difflinknamebare ) {
//		FTT.debug('initRewritunRegExps: basicmsgs not loaded yet');
		return false;
	}
	urlPre = '([^\[\}\|"\'\/]|^)http[s]?\\:\\/\\/([a-z\-]{0,10})(\.m)?[\\.]?'; //3 groups
	projects = '(wikipedia\\.org|wikimedia\\.org|mediawiki\\.org|wikidata\\.org|wikibooks\\.org|wiktionary\\.org|wikinews\\.org|wikiquote\\.org|wikisource\\.org|wikiversity\\.org|wikivoyage\\.org|'+E1(M1('wgServerName'))+')'; //1 group
	//wgScript = /w/index.php, wgArticlePath = /wiki/$1
	//if your wgArticlePath would include something *after* the $1 this is going to be a problem
	pre = '('+E1(M1('wgScript'))+'|'+E1(M1('wgArticlePath')).replace('\\$1','')+')'; //1 group
	pretitle = '('+E1(M1('wgScript'))+'\\?title\=|'+E1(M1('wgArticlePath')).replace('\\$1','')+')'; //1 group
	title = '(([^\\n\\.\\,\\?\\|\\& #]|[\\.\\,][^\\n\\& #])*)'; //2 groups
	anchor = '(#[^#\\s\\n]*)?'; //1 group
	AL = '([\\.\\,]( |\n|$)| |\\n|\\?($| |\\n)|$)'; //3 groups, character(s) after link
	FTT.rewritunRegExps = { //these only attempt to rewrite URLs that occur naturally within MediaWiki and may be copy-pasted, **not** every technically valid URL
		'phabpage':[new RegExp('([^\[\}\|"\'\/]|^)https\\:\\/\\/phabricator\.wikimedia\.org\\/'+title+anchor+AL,'g'),'$1[[:phab:$2$4]]$5'],//https://phabricator.wikimedia.org/T306737
		'diffbare':[new RegExp(urlPre+projects+pre+'\\?diff\=([0-9]+)(\\&oldid\=[0-9]*)?'+anchor+AL,'g'),'$1[[INTERWIKI:$2.$4:' + FTT.B1.Diff + '/$6$8|' + FTT.B1.difflinknamebare.replace('diffID','$6') + ']]$9'],//https://en.wikipedia.org/w/index.php?diff=1 (you'd get this from your address bar after following a Special:Diff/1 link)
		'oldidbare':[new RegExp(urlPre+projects+pre+'\\?oldid\=([0-9]*)'+anchor+AL,'g'),'$1[[INTERWIKI:$2.$4:' + FTT.B1.Permalink + '/$6$7|' + FTT.B1.revid.replace('REVID','$6') + ']]$8'],//https://en.wikipedia.org/w/index.php?oldid=1085167190 (you'd get this from your address bar after following a Special:Permalink/1 link)
		'difftitle':[new RegExp(urlPre+projects+pretitle+title+'\\?diff\=([0-9]+)(\\&oldid\=[0-9]*)?'+anchor+AL,'g'),'$1[[INTERWIKI:$2.$4:' + FTT.B1.Diff + '/$8$10|$6 ' + FTT.B1.difflinkname.replace('diffID','$8') + ']]$11'],//https://en.wikipedia.org/wiki/Main_Page?diff=1 (Fandom)
		'oldidtitle':[new RegExp(urlPre+projects+pretitle+title+'\\?oldid\=([0-9]*)'+anchor+AL,'g'),'$1[[INTERWIKI:$2.$4:' + FTT.B1.Permalink + '/$8$9|$6 (' + FTT.B1.revid.replace('REVID','$8') + ')]]$10'],//https://en.wikipedia.org/wiki/Main_Page?oldid=1085167190 (Fandom)
		'diff':[new RegExp(urlPre+projects+pre+'\\?(title\='+title+'\\&)?diff\=([0-9]+)\\&oldid\=[0-9]*'+AL,'g'),'$1[[INTERWIKI:$2.$4:' + FTT.B1.Diff + '/$9|$7' + FTT.B1.difflinkname.replace('diffID','$9') + ']]$10'],//https://en.wikipedia.org/w/index.php?title=Main_Page&diff=1085170884&oldid=1085167190
		'diffprevnext':[new RegExp(urlPre+projects+pre+'\\?(title\='+title+'\\&)?diff\=(next|prev)\\&oldid\=([0-9]*)'+AL,'g'),'$1[[INTERWIKI:$2.$4:' + FTT.B1.Diff + '/$10/$9|$7' + FTT.B1.difflinknameprevnext.replace('diffID','$10') + ']]$11'],//https://en.wikipedia.org/w/index.php?title=Main_Page&diff=next&oldid=1085167190
		'diffprevnextreverse':[new RegExp(urlPre+projects+pre+'\\?(title\='+title+'\\&)?oldid\=(next|prev)\\&diff\=([0-9]*)'+AL,'g'),'$1[[INTERWIKI:$2.$4:' + FTT.B1.Diff + '/$10/$9|$7' + FTT.B1.difflinknameprevnext.replace('diffID','$10') + ']]$11'],//https://en.wikipedia.org/w/index.php?title=Wikipedia_talk:What_Wikipedia_is_not&oldid=prev&diff=1103993080 got there from a notification due to a mention in an edit summary
		'oldid':[new RegExp(urlPre+projects+pretitle+title+'\\&oldid\=([0-9]*)(#([^ \\n,\?\!]|[\.\?\!][^$\s])*)?'+anchor+AL,'g'),'$1[[INTERWIKI:$2.$4:' + FTT.B1.Permalink + '/$8$9$11|$6 (' + FTT.B1.revid.replace('REVID','$8') + ')]]$12'],//https://en.wikipedia.org/w/index.php?title=Main_Page&oldid=1085167190
		'redlink':[new RegExp(urlPre+projects+pretitle+title+'[\\&\\?]action=edit\\&redlink=1'+AL,'g'),'$1[[INTERWIKI:$2.$4:$6]]$8'],//https://en.wikipedia.org/w/index.php?title=Page_does_not_exist_yet&action=edit&redlink=1
		'editPage':[new RegExp(urlPre+projects+pretitle+title+'[\\&\\?]action=edit'+AL,'g'),'$1[[INTERWIKI:$2.$4:' + FTT.B1.EditPage + '/$6]]$8'],//https://en.wikipedia.org/w/index.php?title=Page_does_not_exist_yet&action=edit&redlink=1
		'history':[new RegExp(urlPre+projects+pretitle+title+'[\\&\\?]action=history'+AL,'g'),'$1[[INTERWIKI:$2.$4:' + FTT.B1.PageHistory + '/$6|' + FTT.B1.pageHistoryLinkName.replace('TITLE','$6') + ']]$8'],//https://en.wikipedia.org/w/index.php?title=Main_Page&action=history
		'pagetitle':[new RegExp(urlPre+projects+pretitle+title+anchor+AL,'g'),'$1[[INTERWIKI:$2.$4:$6$8]]$9'],//https://en.wikipedia.org/w/index.php?title=Main_Page
		'nonlocalwikidiff':[new RegExp('(\\[\\[INTERWIKI:[a-z][a-z:\.\-]*(?!Special:Diff)\/)(' + E1(FTT.B1.Diff) + ')','g'),'$1Special:Diff'],//for interwiki links the special page names are rewritten back to the canonical (English) names. Those work everywhere. I could store or request the localized name for diff/permalink/history, but frankly, my dear, I don't give a damn
		'nonlocalwikipermalink':[new RegExp('(\\[\\[INTERWIKI[^\\[][a-z:\.\-]*(?!Special:PermaLink)\/)(' + E1(FTT.B1.Permalink) + ')','g'),'$1Special:PermaLink'],
		'nonlocalwikihistory':[new RegExp('(\\[\\[INTERWIKI[^\\[][[a-z:\.\-]*(?!Special:History)\/)(' + E1(FTT.B1.PageHistory) + ')','g'),'$1Special:PageHistory'],
		'localwiki':[new RegExp('\\[\\[INTERWIKI:[\\.]?' + E1(M1('wgServerName')),'g'),'INTERWIKI[['], //the lone optional period after INTERWIKI: is for wikis without a subdomain (commonly $2 above) like EN.wikipedia.org. Does is always the case for wgServerName matches like example.fandom.com as the "example" is already in wgServerName, so there's no unspecified subdomain
		'testwiki':[new RegExp('\\[\\[INTERWIKI:test\\.wikipedia\\.org','g'),'INTERWIKI[[:testwiki'],
		'commons':[new RegExp('\\[\\[INTERWIKI:commons\\.wikimedia\\.org','g'),'INTERWIKI[[:c'],
		'meta':[new RegExp('\\[\\[INTERWIKI:meta\\.wikimedia\\.org','g'),'INTERWIKI[[:m'],
		'mediawiki':[new RegExp('\\[\\[INTERWIKI:(www)?[\\.]?mediawiki\\.org','g'),'INTERWIKI[[:mw'],
		'foundation':[new RegExp('\\[\\[INTERWIKI:foundation\\.wikimedia\\.org','g'),'INTERWIKI[[:foundation'],
		'species':[new RegExp('\\[\\[INTERWIKI:species\\.wikimedia\\.org','g'),'INTERWIKI[[:species'],
		'wikitech':[new RegExp('\\[\\[INTERWIKI:wikitech\\.wikimedia\\.org','g'),'INTERWIKI[[:wikitech'],
		'wikidata':[new RegExp('\\[\\[INTERWIKI:(www)?[\\.]?wikidata\\.org','g'),'INTERWIKI[[:d'],
		'wikipedia':[new RegExp('\\[\\[INTERWIKI:([a-z\-]{0,10})\\.wikipedia\\.org','g'),'INTERWIKI[[:w:$1'],
		'wikibooks':[new RegExp('\\[\\[INTERWIKI:([a-z\-]{0,10})\\.wikibooks\\.org','g'),'INTERWIKI[[:b:$1'],
		'wiktionary':[new RegExp('\\[\\[INTERWIKI:([a-z\-]{0,10})\\.wiktionary\\.org','g'),'INTERWIKI[[:wikt:$1'],
		'wikinews':[new RegExp('\\[\\[INTERWIKI:([a-z\-]{0,10})\\.wikinews\\.org','g'),'INTERWIKI[[:n:$1'],
		'wikiquote':[new RegExp('\\[\\[INTERWIKI:([a-z\-]{0,10})\\.wikiquote\\.org','g'),'INTERWIKI[[:q:$1'],
		'wikisource':[new RegExp('\\[\\[INTERWIKI:([a-z\-]{0,10})\\.wikisource\\.org','g'),'INTERWIKI[[:s:$1'],
		'wikiversity':[new RegExp('\\[\\[INTERWIKI:([a-z\-]{0,10})\\.wikiversity\\.org','g'),'INTERWIKI[[:v:$1'],
		'wikivoyage':[new RegExp('\\[\\[INTERWIKI:([a-z\-]{0,10})\\.wikivoyage\\.org','g'),'INTERWIKI[[:voy:$1'],
		'parenthesisfix':[new RegExp('(INTERWIKI)?\\[\\[(([^\\]\\(]|\\([^\\) ]+\\))+)\\)\\]\\](?!\\))','g'),'$1[[$2]])']
	};
};
FTT.rewritunUrUrlz = function(text,mode,linkname,RUUInt) {
	if ( M1('wgPageContentModel') != 'wikitext' && mode != 'contentmove' && mode != 'addRewritunToOther' ) {
//		FTT.debug('this ain\'t no wikitext page! write your own urls! mode ' + mode);
		return text;
	}
//	FTT.debug('rewritun ur urlz');
	FTT.rewritunUrUrlz2=text;
//	FTT.debug('rewritun externalLinkRegExp');
	if ( typeof FTT.rewritunRegExps == 'undefined' ) {
		if ( FTT.initRewritunRegExps() == false ) {
			return; //basicmsgs not loaded yet, can\'t rewrite URLs properly yet
		}
	}
	for (RUUInt = 0; RUUInt < Object.keys(FTT.rewritunRegExps).length; RUUInt++) {
		FTT.rewritunRegExp = FTT.rewritunRegExps[Object.keys(FTT.rewritunRegExps)[RUUInt]][0];
		FTT.rewritunReplacement = FTT.rewritunRegExps[Object.keys(FTT.rewritunRegExps)[RUUInt]][1];
//		FTT.debug('Expression #' + RUUInt + ': ' + FTT.rewritunRegExp.source + ', replacement: ' + FTT.rewritunReplacement);
		//We select the preceding character to ensure we're not replacing links that aren't bare. We select the trailing character to ensure we found the end of the URL. If two links that are caught by the same RegExp (e.g. two history links) have only one character between them (like a space or newline) they can't both match, so we run the RegExp twice.
		FTT.rewritunUrUrlz2 = FTT.rewritunUrUrlz2.replace(FTT.rewritunRegExp, FTT.rewritunReplacement).replace(FTT.rewritunRegExp, FTT.rewritunReplacement);
	}
	FTT.rewritunMatchDiffs = FTT.rewritunUrUrlz2.match(/Special:Diff\/([0-9]+)\|([^\]]*) \([^\)]*[0-9]+[^\)]*\)/g);
	if ( FTT.rewritunMatchDiffs ) {
		for (RUUInt = 0; RUUInt < FTT.rewritunMatchDiffs.length; RUUInt++) {
			FTT.rewritunUrUrlz2 = FTT.rewritunUrUrlz2.replace(FTT.rewritunMatchDiffs[RUUInt], D2(FTT.rewritunMatchDiffs[RUUInt].replace(/%(?![0-9A-Fa-f][0-9A-Fa-f])/g,'%25').replace(/_/g, ' ')));
		}
	}
//	FTT.debug('Rewritten urls: ' + FTT.rewritunUrUrlz2);
//	FTT.debug('rewritun youtu.be');
	FTT.rewritunUrUrlz2 = FTT.rewritunUrUrlz2.replace(/\/\/youtu\.be\/([^\?]*)\?\//g, '\/\/www.youtube.com\/watch\?$1&').replace(/\/\/youtu\.be\//g, '\/\/www.youtube.com\/watch\?'); //rewrite youtu.be shortened urls (these are blacklisted)
	if ( FTT.projectIsSULWiki ) {
//		FTT.debug('rewritun forms.gle'); //Google forms URL shortener, converts https://forms.gle/t1M6XLkgiHTL4XQW6 to https://docs.google.com/forms/d/e/1FAIpQLSdLK83vxevNXMPxfDKqZbDBb9VHBGGa6B-mwKIBcHalwSnNTw/closedform so can't automatically convert. Wrap in nowiki instead so at least it doesn't trigger spamblacklist
		FTT.rewritunUrUrlz2 = FTT.rewritunUrUrlz2.replace(/(^|[^>])(https:\/\/forms\.gle\/[A-Za-z0-9]*)/g,'$1<'+FTT.nowiki+'>$2</'+FTT.nowiki+'> ([[m:Spam blacklist]])');
	}
	FTT.rewritunUrUrlz3 = FTT.rewritunUrUrlz2;
	RUUInt=0;
	while ( ( FTT.rewritunUrUrlz2 != FTT.rewritunUrUrlz3 && RUUInt < 1000 ) || RUUInt == 0 ) {
		FTT.rewritunUrUrlz2 = FTT.rewritunUrUrlz3;
		FTT.rewritunUrUrlz3 = FTT.rewritunUrUrlz3.replace(/\[\[([^_\]\n]{0,1000})_(.*\]\])/g, '[[$1 $2'); //replace underscores in internal links with spaces
		RUUInt++;
//		FTT.debug('rewritun #' + RUUInt + ' (' + FTT.rewritunUrUrlz2 + ' to ' + FTT.rewritunUrUrlz3 + ')' );
	}
	FTT.internalLinks = FTT.rewritunUrUrlz3.match(/INTERWIKI\[\[(([^\]]*|\][^\]])*[\]])/g);
	if ( FTT.internalLinks ) {
//		FTT.debug('remove percent encoding from internal links');
		for ( FTT.internalLinksInt = 0;FTT.internalLinksInt < FTT.internalLinks.length;FTT.internalLinksInt++) {
			FTT.rewritunUrUrlz3 = FTT.rewritunUrUrlz3.replace(FTT.internalLinks[FTT.internalLinksInt],D2(FTT.internalLinks[FTT.internalLinksInt].replace(/%(?![0-9A-Fa-f][0-9A-Fa-f])/g,'%25'))).replace(/INTERWIKI\[\[/,'[[');
		}
	}
//	FTT.debug(FTT.internalLinks);
	FTT.rewritunUrUrlz4 = FTT.rewritunUrUrlz3;
	FTT.rewritunUrUrlz4RegExp = new RegExp('(\\[\\[[^\\]\n\%]*\%[^\\]\n]*\\]\\])', 'g');
	FTT.rewritunUrUrlz4match = FTT.rewritunUrUrlz4.match(FTT.rewritunUrUrlz4RegExp);
	if ( FTT.rewritunUrUrlz4match ) {
		for (RUUInt = 0; RUUInt < FTT.rewritunUrUrlz4match.length; RUUInt++) {
//			FTT.debug('rewritun #' + RUUInt + ', '+ FTT.rewritunUrUrlz4match[RUUInt] + ' with ' + D2(FTT.rewritunUrUrlz4match[RUUInt].replace(/%(?![0-9A-Fa-f][0-9A-Fa-f])/g,'%25')));
			FTT.rewritunUrUrlz4 = FTT.rewritunUrUrlz4.replace(FTT.rewritunUrUrlz4match[RUUInt], D2(FTT.rewritunUrUrlz4match[RUUInt].replace(/%(?![0-9A-Fa-f][0-9A-Fa-f])/g,'%25')));
		}
	}
	if ( mode == 'insertlink' && linkname != '' ) {
		FTT.rewritunUrUrlz4 = FTT.rewritunUrUrlz4.replace(/^([a-z0-9]*:\/\/[^ ]*$)/,'[$1 ' + linkname + ']').replace(/(\[[a-z0-9]*:\/\/[^ ]*) .+\]$/,'$1 ' + linkname + ']').replace(/^(\[\[[^\|]*\|)(.*\]\])$/,'$1' + linkname + ']]').replace(/^(\[\[[^\|]*)\]\]$/, '$1|' + linkname + '\]\]');
	}
//	FTT.debug('finished rewritun');
	return FTT.rewritunUrUrlz4;
};
FTT.rewritunOther = function() {
	this.value = FTT.rewritunUrUrlz(this.value,'addRewritunToOther');//"this" is the <input> element that triggered the event that called rewritunOther
};
FTT.addRewritunToOther = function() {
	$(document).ready(function() {
		for(FTT.addRewritunToOtherInt=0;FTT.addRewritunToOtherInt<$(FTT.settings.rewritunOther).length;FTT.addRewritunToOtherInt++){
			if ( ['text','textarea'].includes($(FTT.settings.rewritunOther)[FTT.addRewritunToOtherInt].type) && ( ! $(FTT.settings.rewritunOther)[FTT.addRewritunToOtherInt].classList || ! $(FTT.settings.rewritunOther)[FTT.addRewritunToOtherInt].classList.contains('FTTRewritun') ) ) {
				$(FTT.settings.rewritunOther)[FTT.addRewritunToOtherInt].classList.add('FTTRewritun');
				$(FTT.settings.rewritunOther)[FTT.addRewritunToOtherInt].addEventListener('blur',FTT.rewritunOther);
			}
		}
	});
};
if ( FTT.settings.rewritunOther != '' ) {
	$('body').on('click',function() { FTT.addRewritunToOther(); });//the reason we don't simply do this on load is that input fields that need rewriting may not exist yet, for example if you haven't opened Twinkle yet, so we check on every click.
}
FTT.runCIOther = function() {
	this.value = FTT.runCI(this.value,'runCIOther');
};
FTT.addrunCIToOther = function() {
	$(document).ready(function() {
		for(FTT.addrunCIToOtherInt=0;FTT.addrunCIToOtherInt<$(FTT.settings.runCIOther).length;FTT.addrunCIToOtherInt++){
			if ( ['text','textarea'].includes($(FTT.settings.runCIOther)[FTT.addrunCIToOtherInt].type) && ( ! $(FTT.settings.runCIOther)[FTT.addrunCIToOtherInt].classList || ! $(FTT.settings.runCIOther)[FTT.addrunCIToOtherInt].classList.contains('FTTrunCI') ) ) {
				$(FTT.settings.runCIOther)[FTT.addrunCIToOtherInt].classList.add('FTTrunCI');
				$(FTT.settings.runCIOther)[FTT.addrunCIToOtherInt].addEventListener('blur',FTT.runCIOther);
			}
		}
	});
};
if ( FTT.settings.runCIOther != '' ) {
	$('body').on('click',function() { FTT.addrunCIToOther(); });
}
FTT.AWBtyposOther = function() {
	FTT.RETF(this.value,'AWBtyposOther',this); //we pass the element in question to RETF as RETF will just call itself after downloading the list the first time it runs. It needs to know to which element to apply the stuff when it calls itself
};
FTT.addAWBtyposToOther = function() {
	$(document).ready(function() {
		for(FTT.addrunCIToOtherInt=0;FTT.addrunCIToOtherInt<$(FTT.settings.AWBtyposOther).length;FTT.addrunCIToOtherInt++){
			if ( ['text','textarea'].includes($(FTT.settings.AWBtyposOther)[FTT.addrunCIToOtherInt].type) && ( ! $(FTT.settings.AWBtyposOther)[FTT.addrunCIToOtherInt].classList || ! $(FTT.settings.AWBtyposOther)[FTT.addrunCIToOtherInt].classList.contains('FTTrunCI') ) ) {
				$(FTT.settings.AWBtyposOther)[FTT.addrunCIToOtherInt].classList.add('FTTrunCI');
				$(FTT.settings.AWBtyposOther)[FTT.addrunCIToOtherInt].addEventListener('blur',FTT.AWBtyposOther);
			}
		}
	});
};
if ( FTT.settings.AWBtyposOther != '' ) {
	$('body').on('click',function() { FTT.addAWBtyposToOther(); });//the reason we don't simply do this on load is that input fields that need rewriting may not exist yet, for example if you haven't opened Twinkle yet, so we check on every click.
}
FTT.safeText = function(text,mode,skiplinks) {
	if ( ! text ) {
		return '';
	}
//	FTT.safeTextStart = new Date().getTime();//FTT.debug
	FTT.safedMarker = 'FTTSAFED'+FTT.semiRandom;
	if ( mode == 'unsafe' ) {
//		FTT.debug('safeText: return text to original form');
		//THE ORDER OF THE FOLLOWING MUST BE THE REVERSE OF THE REPLACEMENTS!!
		FTT.safeTextPlaceholders = ['LINK','WIKILINK','HTMLATTR','L6TEMPLATE','L5TEMPLATE','L4TEMPLATE','L3TEMPLATE','L2TEMPLATE','L1TEMPLATE','L0TEMPLATE','CODE','STL','REF','HTMLCOMMENT','PRE','NOWIKI'];
		for (FTT.safeTextPlaceholdersInt=0;FTT.safeTextPlaceholdersInt<FTT.safeTextPlaceholders.length;FTT.safeTextPlaceholdersInt++){
			if ( FTT['RETFEscapeMatched' + FTT.safeTextPlaceholders[FTT.safeTextPlaceholdersInt]] ) {
//				FTT.debug('safeText: replace placeholder "'+FTT.safedMarker+FTT.safeTextPlaceholders[FTT.safeTextPlaceholdersInt] + ' with original content');
				for ( FTT.RETFEscapeInt=0;FTT.RETFEscapeInt<FTT['RETFEscapeMatched' + FTT.safeTextPlaceholders[FTT.safeTextPlaceholdersInt]].length;FTT.RETFEscapeInt++) {
					text = text.replace(new RegExp(FTT.safedMarker + FTT.safeTextPlaceholders[FTT.safeTextPlaceholdersInt]),FTT.escapeReplacement(FTT['RETFEscapeMatched' + FTT.safeTextPlaceholders[FTT.safeTextPlaceholdersInt]][FTT.RETFEscapeInt]));
				}
			}
		}
//		FTT.debug('restored text in ' + (new Date().getTime() - FTT.safeTextStart) + 'ms');
		return text;
	} else if ( ! text.match(new RegExp(FTT.safedMarker)) ) {
//		FTT.debug('safeText: replace tags/links etc with placeholders to stop replacements from matching'); //A note on the order here. It's best to safe items that may contain other items first. So safe ref tags first as they may contain templates. Safe templates before links as they may contain links. A template or link that was already safed as part of something bigger doesn't need to be safed individually, which makes the process faster.
		FTT.RETFEscapeNew = text;
//		FTT.debug('safeText: safing nowiki tags..');
		FTT.RETFEscapeMatchedNOWIKIRegExp = new RegExp('<([Nn]o[Ww]iki|NOWIKI)>(([^\^<]|<(?!\/([Nn]o[Ww]iki>|NOWIKI>)))*)<\/([Nn]o[Ww]iki|NOWIKI)>','g');
		FTT.RETFEscapeMatchedNOWIKI = FTT.RETFEscapeNew.match(FTT.RETFEscapeMatchedNOWIKIRegExp);
		FTT.RETFEscapeNew = FTT.RETFEscapeNew.replace(FTT.RETFEscapeMatchedNOWIKIRegExp,FTT.safedMarker+'NOWIKI');
//		FTT.debug('safeText: safing pre tags..');
		FTT.RETFEscapeMatchedPRERegExp = new RegExp('<([Pp]re|PRE)>(([^\^<]|<(?!\/([Pp]re>|PRE>)))*)<\/([Pp]re|PRE)>','g');
		FTT.RETFEscapeMatchedPRE = FTT.RETFEscapeNew.match(FTT.RETFEscapeMatchedPRERegExp);
		FTT.RETFEscapeNew = FTT.RETFEscapeNew.replace(FTT.RETFEscapeMatchedPRERegExp,FTT.safedMarker+'PRE');
//		FTT.debug('safeText: safing HTML comments..');
		FTT.RETFEscapeMatchedHTMLCOMMENTRegExp = new RegExp('<\!--[ ]*(([^-d]|-(?!->)|d(?!ummy)){0,3000})-->','g');
		FTT.RETFEscapeMatchedHTMLCOMMENT = FTT.RETFEscapeNew.match(FTT.RETFEscapeMatchedHTMLCOMMENTRegExp);
		FTT.RETFEscapeNew = FTT.RETFEscapeNew.replace(FTT.RETFEscapeMatchedHTMLCOMMENTRegExp,FTT.safedMarker+'HTMLCOMMENT');
//		FTT.debug('safeText: safing ref tags..');
		FTT.RETFEscapeMatchedREFRegExp = new RegExp('<([Rr]ef|REF)>(([^\^<]|<(?!\/([Rr]ef>|REF>)))*)<\/([Rr]ef|REF)>','g');
		FTT.RETFEscapeMatchedREF = FTT.RETFEscapeNew.match(FTT.RETFEscapeMatchedREFRegExp);
		FTT.RETFEscapeNew = FTT.RETFEscapeNew.replace(FTT.RETFEscapeMatchedREFRegExp,FTT.safedMarker+'REF');
//		FTT.debug('safeText: safing syntaxhighlight tags..');
		FTT.RETFEscapeMatchedSTLRegExp = new RegExp('<([Ss]yntax[Hh]igh[Ll]ight[^>]*|SYNTAXHIGHLIGHT[^>]*)>(([^<]|<(?!\/([Ss]yntax[Hh]igh[Ll]ight>|SYNTAXHIGHLIGHT>)))*)<\/([Ss]yntax[Hh]igh[Ll]ight|SYNTAXHIGHLIGHT)>','g');
		FTT.RETFEscapeMatchedSTL = FTT.RETFEscapeNew.match(FTT.RETFEscapeMatchedSTLRegExp);
		FTT.RETFEscapeNew = FTT.RETFEscapeNew.replace(FTT.RETFEscapeMatchedSTLRegExp,FTT.safedMarker+'STL');
//		FTT.debug('safeText: safing code tags..');
		FTT.RETFEscapeMatchedCODERegExp = new RegExp('<([Cc]ode|CODE)>(([^\^<]|<(?!\/([Cc]ode>|CODE>)))*)<\/([Cc]ode|CODE)>','g');
		FTT.RETFEscapeMatchedCODE = FTT.RETFEscapeNew.match(FTT.RETFEscapeMatchedCODERegExp);
		FTT.RETFEscapeNew = FTT.RETFEscapeNew.replace(FTT.RETFEscapeMatchedCODERegExp,FTT.safedMarker+'CODE');
//		FTT.debug('safeText: safing templates..');
		FTT.RETFEscapeMatchedSingleTemplateRegExp = new RegExp('\{\{(([^\{\}]|\{[^\{]|\}[^\}])*)\}\}','g');
		for(FTT.RETFTemplateSafeInt=0;FTT.RETFTemplateSafeInt<7;FTT.RETFTemplateSafeInt++){
//			FTT.debug('safeText: safing templates (nesting level '+FTT.RETFTemplateSafeInt);
			FTT['RETFEscapeMatchedL'+FTT.RETFTemplateSafeInt+'TEMPLATE'] = FTT.RETFEscapeNew.match(FTT.RETFEscapeMatchedSingleTemplateRegExp);
			FTT.RETFEscapeNew = FTT.RETFEscapeNew.replace(FTT.RETFEscapeMatchedSingleTemplateRegExp,FTT.safedMarker+'L'+FTT.RETFTemplateSafeInt+'TEMPLATE');
		}
//		FTT.debug('safeText: safing HTML attributes..');
		FTT.RETFEscapeMatchedHTMLATTRRegExp = new RegExp(' [a-z\-]+="(([^"F]|F(?!TTCmt))+)"(?! class="FTTCmt")','g'); //excluding locators
		FTT.RETFEscapeMatchedHTMLATTR = FTT.RETFEscapeNew.match(FTT.RETFEscapeMatchedHTMLATTRRegExp);
		FTT.RETFEscapeNew = FTT.RETFEscapeNew.replace(FTT.RETFEscapeMatchedHTMLATTRRegExp,FTT.safedMarker+'HTMLATTR');
		if ( ! skiplinks ) {
			if ( FTT.settings.rewritun && mode == 'rewritun' ) { //we rewrite URLs here because links in HTML comments etc have already been safed here but links haven't been safed yet.
				FTT.RETFEscapeNew = FTT.rewritunUrUrlz(FTT.RETFEscapeNew);
			}
//			FTT.debug('safeText: safing wikilinks..');
			FTT.RETFEscapeMatchedWIKILINKRegExp = new RegExp('\\[\\[([^\\]\n]|\\](?![^\\]]))*\\]\\]','g');
			FTT.RETFEscapeMatchedWIKILINK = FTT.RETFEscapeNew.match(FTT.RETFEscapeMatchedWIKILINKRegExp);
			FTT.RETFEscapeNew = FTT.RETFEscapeNew.replace(FTT.RETFEscapeMatchedWIKILINKRegExp,FTT.safedMarker+'WIKILINK');
//			FTT.debug('safeText: safing external links..');
			FTT.RETFEscapeMatchedLINKRegExp = new RegExp(':\/\/([^ \n\]\|])*','g');
			FTT.RETFEscapeMatchedLINK = FTT.RETFEscapeNew.match(FTT.RETFEscapeMatchedLINKRegExp);
			FTT.RETFEscapeNew = FTT.RETFEscapeNew.replace(FTT.RETFEscapeMatchedLINKRegExp,FTT.safedMarker+'LINK');
		}
//		FTT.debug('safed text in ' + (new Date().getTime() - FTT.safeTextStart) + 'ms');
		return FTT.RETFEscapeNew;
	} else {
//		FTT.debug('text seems to already have been made safe');
		return text;
	}
};
FTT.RETFBlacklist = []; //e.g. '-XXX(ed/er/ing/ive)'
FTT.RETF = function(text,mode,AWBtyposOtherElement) {
	if ( mode == 'onetime' ) {
		FTT.UITextInput.setReadOnly(true);
	}
	if ( ( FTT.B1.AWBtyposTitle != '' || FTT.settings.AWBtyposCustomTitle != '' ) && M1('wgPageContentModel') == 'wikitext' ) {
//		FTT.debug('AWB RegExTypoFix');
		if ( typeof FTT.RETFTypoRegEx == 'undefined' ) {
			FTT.RETFTypoRegEx = [];
			FTT.RETFTypoReplacement = [];
			FTT.RETFTypoRegExWord = [];
//			FTT.debug('obtain ' + FTT.B1.AWBtyposTitle + ' to extract regular expressions from');
			if ( FTT.settings.AWBtyposCustomTitle != '' ) {
				FTT.AWBtyposTitle = FTT.settings.AWBtyposCustomTitle;
			} else if ( FTT.B1.AWBtyposTitle != '' ) {
				FTT.AWBtyposTitle = FTT.B1.AWBtyposTitle;
			}
			api.get( {'action':'query','format':'json','export':'true','redirects':'1','titles': FTT.AWBtyposTitle} ).then( function ( data ) {
//				FTT.debug(data);
				FTT.RETFRegExpText = FTT.getWikitextFromExport(data.query.export['*']).replace(/&lt;/g,'<').replace(/&gt;/g,'>');
				FTT.RETFRegExpGlobal = new RegExp('<Typo word="(.*)" find="(.*)" replace="(.*)"[ ]?\/>','g');
				FTT.RETFRegExpGroups = new RegExp(FTT.RETFRegExpGlobal.source);
				FTT.RETFRegExpArr = FTT.RETFRegExpText.match(FTT.RETFRegExpGlobal);
				for (FTT.RETFRegExpInt=0;FTT.RETFRegExpInt<FTT.RETFRegExpArr.length;FTT.RETFRegExpInt++) {
					FTT.RETFTypoRegEx.push(FTT.RETFRegExpArr[FTT.RETFRegExpInt].match(FTT.RETFRegExpGroups)[2]);
					FTT.RETFTypoReplacement.push(FTT.RETFRegExpArr[FTT.RETFRegExpInt].match(FTT.RETFRegExpGroups)[3]);
					FTT.RETFTypoRegExWord.push(FTT.RETFRegExpArr[FTT.RETFRegExpInt].match(FTT.RETFRegExpGroups)[1]);
				}
				if ( mode == 'onetime' ) {
//					FTT.debug('running AWB typos just this once');
					FTT.RETF(text,'onetime');
				} else if ( mode == 'AWBtyposOther' ) {
//					FTT.debug('apply RETF to some field');
					FTT.RETF(text,mode,AWBtyposOtherElement);
				}
			}, function ( code, data ) { FTT.RETFRegExp = '';FTT.APIError(code, data);
			});
			return;
		}
		if ( mode != 'init' ) {
//			FTT.debug('Regular expressions are available, applying them now');
			text = FTT.safeText(text);
			if ( mode == 'onetime' ) {
				$('#FTTMatchedRETF')[0].innerHTML = '';
			}
			for (FTT.RETFRegExpInt=0;FTT.RETFRegExpInt<FTT.RETFTypoRegEx.length;FTT.RETFRegExpInt++) {
				FTT.validRETFRegExp = true;
				try {
					RegExp(FTT.RETFTypoRegEx[FTT.RETFRegExpInt]);
				} catch(e) {
					FTT.validRETFRegExp = false;
				}
				if ( FTT.validRETFRegExp && FTT.RETFBlacklist.indexOf(FTT.RETFTypoRegExWord[FTT.RETFRegExpInt]) == -1 ) {
					FTT.RETFnewText = text;
					delete FTT.RETFoldText;
					FTT.RETFescapeInt=0;
					while ( FTT.RETFnewText != FTT.RETFoldText && FTT.RETFescapeInt<10000 ) {
						FTT.RETFoldText = FTT.RETFnewText;
						FTT.RETFnewText = FTT.RETFnewText.replace(new RegExp(FTT.RETFTypoRegEx[FTT.RETFRegExpInt]),FTT.RETFTypoReplacement[FTT.RETFRegExpInt]);
					}
//					if ( FTT.settings.debug && FTT.RETFnewText != text ) { FTT.debug('input text matched ' + FTT.RETFTypoRegEx[FTT.RETFRegExpInt]); }
					if ( FTT.RETFnewText != text && mode == 'onetime' ) {
						$('#FTTMatchedRETF').append(FTT.msgs.matchedRETF.replace('REGEXP',FTT.RETFTypoRegEx[FTT.RETFRegExpInt]).replace('WORD',FTT.RETFTypoRegExWord[FTT.RETFRegExpInt]) + '<br/>');
						if ( ! FTT.RETFsummary.match(new RegExp(', ' + E1(FTT.RETFTypoRegExWord[FTT.RETFRegExpInt]) + '($|,)')) ) {
							FTT.RETFsummary = ', ' + FTT.RETFsummary + FTT.RETFTypoRegExWord[FTT.RETFRegExpInt];
						}
					}
					text = FTT.RETFnewText;
//				} else if ( FTT.RETFBlacklist.indexOf(FTT.RETFTypoRegExWord[FTT.RETFRegExpInt]) != -1 ) { //FTT.debug
//					FTT.debug('the replacement for ' + FTT.RETFTypoRegExWord[FTT.RETFRegExpInt] + ' is blacklisted');
				}
			}
			if ( mode != 'AWBtyposOther' ) {
				FTT.RETFsummary = FTT.RETFsummary.replace(/^, (.*)/,'[[[' + FTT.B1.AWBtyposTitle + '|$1]]]');
			}
			text = FTT.safeText(text,'unsafe');
			if ( mode == 'onetime' ) {
				FTT.setValue(text);
				FTT.UITextInput.setReadOnly(false);
			}
			if ( AWBtyposOtherElement ) {
//				FTT.debug('RETF was loaded, now applying it to some field determined by your custom CSS selector');
				AWBtyposOtherElement.value = text;
				return;
			}
			return text;
		}
	} else if ( FTT.B1.AWBtyposTitle == '' ) {
//		FTT.debug('Wikidata (Q6585066) has no associated page for AWB RETF for this project and you didn\'t configure a page to use either');
		if ( mode == 'onetime' && FTT.UITextInput ) {
			FTT.UITextInput.setReadOnly(false);
		}
		return text;
	} else if ( M1('wgPageContentModel') != 'wikitext' ) {
//		FTT.debug('Do not run AWB RETF on pages that are not wikitext');
		if ( mode == 'onetime' && FTT.UITextInput ) {
			FTT.UITextInput.setReadOnly(false);
		}
		return text;
	}
};
FTT.applyMarkdown = function(text) {
//	FTT.debug('Applying Markdown markup');
	return text.replace(/`([^`]*)`/g, '<code>$1</code>').replace(/(^|\n)---($|\n)/, '$1----$2');
	//todo: lists, links, images, maybe headers.
};
FTT.applyBBCode = function(text,i) {
//	FTT.debug('applying BBCode');
	if ( FTT.B1.smiley != '' ) {
		for (i=0;i<2;i++) { //to apply to multiple smileys in a row run twice
			text = text.replace(/(^| |\n)(:-\)|;-\)\|:-\()([^a-zA-Z0-9]|$)/g, '$1{{' + FTT.B1.smiley + '|$2}}$3');
		}
	}
	return text.replace(/\[\/?[Bb]\]/g, '\'\'\'').replace(/\[\/?[Ii]\]/g, '\'\'').replace(/\[(\/)?[Uu]\]/g, '<$1u>').replace(/\[(\/)?[Ss]\]/g, '<$1s>').replace(/\[url\](http)?(s?)(:\/\/)?([^\/\[]*)([^\[]*)\[\/url\]/gi, '[http$2://$4$5 ' + '$4' + ']').replace(/\[url="?(http)?(s?)(:\/\/)?([^\/\[]*)([^\[]*)"?\]([^\[]*)\[\/url\]/gi, '[http$2://$4$5 ' + '$6' + ']').replace(/\[img\]([^:\[]*:)?([^\[]*)\[\/img\]/g, '[[' + FTT.NS[6] + ':$2|thumb]]').replace(/\[(\/)?[Cc]ode\]/g, '<$1code>').replace(/\[[Ss]tyle size="?([0-9]*)"?\]([^\[]*)\[\/(style)?\]/g, '<span style="font-size: $1pt">$2</span>').replace(/\[[Ss]tyle size="?([^"\]]*)"?\]([^\[]*)\[\/(style)?\]/g, '<span style="font-size: $1">$2</span>').replace(/\[([Ss]tyle )?color="?([0-9A-Fa-z]*)"?\]([^\[]*)\[\/(style|color)?\]/g, '<span style="color: $2">$3</span>').replace(/\[\*\] ?([^\n\[]*)\s*/gm, '<li>$1</li>').replace(/\[[Ll]ist\]\s*([^]*)\[\/[Ll]ist\]/gm, '<ul>$1</ul>').replace(/\s*<\/(ul|li)>/g, '</$1>').replace(/\[[Qq](uote)?\]([^\[]*)\[\/[Qq](uote)?\]/g, '<blockquote><p>$2</p></blockquote>').replace(/\[[Qq](uote)?=?"?([^"\]]*)"?\]([^\[]*)\[\/[Qq](uote)?\]/g, '<blockquote><p>$3</p>—$2</blockquote>').replace(/\s*\[(\/)?(td|tr)\]\s*/g, '<$1$2>').replace(/\[[Tt]able\]/g, '<table class="wikitable">').replace(/\[\/[Tt]able\]/g, '</table>');
};
FTT.insertIsRegExpRegExp = new RegExp('^\/((?:[^\\/]|[\\\\][\/])*)\/((?:[^\\/]|[\\\\][\/])*)\/([gmi]{0,3})(:<<.+>>)?$');
FTT.runCI = function(text,mode,RCINt,RCCINt) {
//	FTT.debug('start to process automatically applied custom inserts that are regular expressions');
	for (RCINt=0;RCINt<FTT.settings.cIThatRun.length;RCINt++) {
		if ( FTT.settings.enableCIThatRun ) { //non-empty insert found
//			FTT.debug('found non-empty regexp to be applied automatically ' + (RCINt + 1) + '/'+FTT.settings.cIThatRun.length);
			if ( D2(FTT.settings.cIThatRun[RCINt]).match(FTT.insertIsRegExpRegExp) ) {
				FTT.customInsertRegExpNowParts = D2(FTT.settings.cIThatRun[RCINt]).match(FTT.insertIsRegExpRegExp);
				FTT.customInsertRegExpNow = new RegExp(FTT.customInsertRegExpNowParts[1], FTT.customInsertRegExpNowParts[3]);
				text = text.replace(FTT.customInsertRegExpNow, FTT.customInsertRegExpNowParts[2].replace(/\\\//g,'/').replace(/[\\]n/g,'\n'));
				if (mode != 'runCIOther') {
					FTT.setValue(text);
				}
//				FTT.debug('replaced ' + FTT.customInsertRegExpNow + ' with ' + FTT.customInsertRegExpNowParts[2]);
			}
		}
	}
	for (RCCINt=0;RCCINt<FTT.settings.cIThatRunCmt.length;RCCINt++) {
		if ( FTT.settings.enableCIThatRunCmt && ! text.match(/NOSIGN$/) && ( ! FTT.UITextInputSummary || ! FTT.UITextInputSummary.getValue().match(/\!nosi(gn|ne)\!/) ) && FTT.PRMOpened && FTT.PRMOpened.type != 'editFullPage' && ! (FTT.PRMOpened.type == 'heading' && FTT.PRMOpened.subtype == 'edit') && mode != 'runCIOther' ) {
//			FTT.debug('found non-empty regexp to be applied automatically and you\'re not editing a full page or section ' + (RCCINt + 1) + '/'+FTT.settings.enableCIThatRunCmt);
			if ( D2(FTT.settings.cIThatRunCmt[RCCINt]).match(FTT.insertIsRegExpRegExp) ) {
				FTT.customInsertRegExpNowParts = D2(FTT.settings.cIThatRunCmt[RCCINt]).match(FTT.insertIsRegExpRegExp);
				FTT.customInsertRegExpNow = new RegExp(FTT.customInsertRegExpNowParts[1], FTT.customInsertRegExpNowParts[3]);
				text = text.replace(FTT.customInsertRegExpNow, FTT.customInsertRegExpNowParts[2].replace(/\\\//g,'/').replace(/[\\]n/g,'\n'));
				if (!['livepreview','runCIOther'].includes(mode)){
					FTT.setValue(text);
				}
//				FTT.debug('replaced ' + FTT.customInsertRegExpNow + ' with ' + FTT.customInsertRegExpNowParts[2]);
			}
		}
	}
	return text;
};
FTT.convertToOneLineCmt = function(text) {
	if ( FTT.PRMOpened.multiline || ( ( FTT.PRMOpened.type == 'newsection' && FTT.PRMOpened.subtype == 'InputBox' ) || ( FTT.B1.newline == '' && ( ( FTT.PRMOpened.type == 'newheading' && FTT.PRMOpened.subtype == 'heading' ) || FTT.PRMOpened.type == 'newsection' ) && ( text.match(/\n(([^<\n]|<(?![Pp]re|PRE))*)<\/([Pp]re|PRE)>/) || text.match(/\n(([^<\n]|<(?![Nn]o[Ww]iki|NOWIKI))*)<\/([Nn]o[Ww]iki|NOWIKI)>/) || text.match(/\n(([^<\n]|<(?![Ss]yntax[Hh]igh[Ll]ight|SYNTAXHIGHLIGHT))*)<\/([Ss]yntax[Hh]igh[Ll]ight|SYNTAXHIGHLIGHT)>/) ) ) ) ) {
//		FTT.debug('a new section input or a newline template is not known AND you\'re posting a new (sub)section which is unindented AND your message appears to contains some multiline pre/nowiki/syntaxhighlight. OR you are editing a multiline new section comment. At the cost being unable to edit your whole comment (you could still edit the whole section of course), we\'ll skip the conversion to a single line for this comment.');
		return text;
	}
//	FTT.debug('converting comment to a single line');
	FTT.multilineText1 = ' \n \n' + text;
	FTT.multilineText2 = '';
	FTT.listInt = 0;
	FTT.multilineText1 = FTT.multilineText1.replace(/<([\/])?([Ss]yntax[Hh]igh[Ll]ight|SYNTAXHIGHLIGHT)([^>\n]*)>/g,'<$1syntaxhighlight$3>').replace(/(<syntaxhighlight[^\n>]*)lang=""/,'$1lang=text').replace(/(<syntaxhighlight[^\n>]*)lang=["]?[Jj][Ss]["]?/,'$1lang=javascript'); //empty lang and "js" are both invalid and result in maintenance cats
	FTT.multilineText1 = FTT.multilineText1.replace(/<([\/])?([Gg]allery|GALLERY)([^>\n]*)>/g,'<$1gallery$3>');
	while ( FTT.multilineText1 != FTT.multilineText2 && FTT.listInt < 1000 ) {
		FTT.listInt++;
		FTT.orderedListItemRegExp = new RegExp('[\n]?FTTORDEREDLISTITEM'+FTT.semiRandom+'(.*)','g');
		FTT.multilineText2 = FTT.multilineText1;
		FTT.multilineText1 = FTT.multilineText1.replace(/(<code><[n]owiki>)(([^<\n]|<(?!\/[n]owiki>))*)<(?!\/[n]owiki><\/code>)(([^<]|<(?!\/[n]owiki><\/code>))*)(<\/[n]owiki><\/code>)/g,'$1$2&lt;$4$6') //tags within a combination of code-nowiki should pretty much exclusively get escaped. Ideally we'd figure out which tag with nowiki effect is the least nested, but that's a major headache
		.replace(/([^\|\=][ ]*)\n([^\|\=])/g,'$1FTTNOTOUCHNEWLINE'+FTT.semiRandom+'$2') //escape newlines that isn't immediately next to a pipe or equal sign, either not part of a template or part of a multiline template parameter, just leave it
		.replace(/\{\{(([^\{\}\n]|\{[^\{\n]|\}[^\}\n]|\{\{[^\{\}\n]*\}\})*)\n(((([^\{\}]|\{[^\{]|\}[^\}]|\{\{[^\{\}]*\}\})*)\n)*)([^\}]*)\}\}/g,'{{$1$3$7}}') //This removes one newline from multiline templates. Only one, so it has to be applied as many times as the number of lines a template takes up. In SOME cases the result is possibly not desirable, but in those cases whatever you were trying to do probably wasn't going to work in an indented comment anyway. Note: you can only nest one level. So {{x(newline)|{{y}}}} would work, but {{x(newline)|{{y|{{z}}}}}} wouldn't.
		.replace(new RegExp('FTTNOTOUCHNEWLINE'+FTT.semiRandom,'g'),'\n') //put escaped newlines back
		.replace(/<([Pp]re|PRE)([^>\n]*)>(([^<\n]|<(?![\/]?[Pp]re))*)\n(([^<]|<(?![\/]?[Pp]re))*)<\/[Pp]re>/g,'<syntaxhighlight lang=text>$5</syntaxhighlight>') //pre with at least one newline in it
		.replace(/<([Nn]o[Ww]iki|NOWIKI)>(([^<\n]|<(?!\/([Nn]o[Ww]iki|NOWIKI)>))*)\n/g,'<' + FTT.nowiki + '>$2</' + FTT.nowiki + '> <' + FTT.nowiki + '>')
		.replace(/<(syntaxhighlight)([^>\n]*)>\n/g,'<$1$2>').replace(/<(syntaxhighlight)([^>\n]*)>(([^<\n]|<(?![\/]?syntaxhighlight))*)(PARSEWIKITAGNEWLINE.*)?\n(([^<]|<(?![\/]?syntaxhighlight))*<\/(syntaxhighlight)>)(\n)?/g,'<$1$2>$3$5PARSEWIKITAGNEWLINE $6')
		.replace(/<(gallery)([^>\n]*)>\n/g,'<$1$2>').replace(/<(gallery)([^>\n]*)>(([^<\n]|<(?![\/]?gallery))*)(PARSEWIKITAGNEWLINE.*)?\n(([^<]|<(?![\/]?gallery))*<\/(gallery)>)/g,'<$1$2>$3$5PARSEWIKITAGNEWLINE $6')
		.replace(/((^|\n)(\*(.*)(\n|$))+)/g,'<ul>$1</ul>')
		.replace(/\n\*\*\*[ ]?(.*)/g,'<li style="margin-left:3em">$1</li>')
		.replace(/\n\*\*[ ]?(.*)/g,'<li style="margin-left:2em">$1</li>')
		.replace(/\n\*[ ]?(.*)/g,'<li>$1</li>')
		.replace(/((^|\n)(\#(.*)(\n|$))+)/g,'<ol>$1</ol>')
		.replace(/(^|\n)\#[ ]?/g,'$1FTTORDEREDLISTITEM'+FTT.semiRandom);
	}
	FTT.multilineText1 = FTT.multilineText1.replace(FTT.orderedListItemRegExp,'<li>$1</li>').replace(/\n<\/([ou])l>/g,'</$1l>');
	if ( FTT.B1.newline == '' ) {
		FTT.multilineText1 = FTT.multilineText1.replace(/PARSEWIKITAGNEWLINE/g,'');
	} else {
		FTT.multilineText1 = FTT.multilineText1.replace(new RegExp('\{\{' + E1(FTT.B1.newline) + '\}\} FTTNEWLINE','g'),'{{'+FTT.B1.newline+'}}').replace(new RegExp('<\/pre>\{\{' + E1(FTT.B1.newline) + '\}\}<pre>','g'), '{{' + FTT.B1.newline + '}}');
		FTT.multilineText1 = FTT.multilineText1.replace(new RegExp('<(syntaxhighlight|gallery)[ ]?([^>\n]*)>((([^<]|<(?!\/[Ss]yntax[Hh]igh[Ll]ight|\/SYNTAXHIGHLIGHT)))*PARSEWIKITAGNEWLINE)(([^<]|<(?!\/syntaxhighlight|\/gallery))*)<\/(syntaxhighlight|gallery)>','g'),'{{#tag:$1|$3$6SYNTAXHLEND|SYNTAXHLPARAMSSTART$2SYNTAXHLPARAMSEND}}');
		for(FTT.tagParamsInt=0;FTT.tagParamsInt<10;FTT.tagParamsInt++){
			FTT.multilineText1 = FTT.multilineText1.replace(/(SYNTAXHLPARAMSSTART[^=]+="[^"]*")[ ](.*SYNTAXHLPARAMSEND)/g,'$1|$2');
		}
		FTT.listInt = 0;
		while ( FTT.multilineText1 != FTT.multilineText2 && FTT.listInt < 1000 ) { //escape curly brackets {} and pipes | within #tag:syntaxhighlight using nowiki
			FTT.listInt++;
			FTT.multilineText2 = FTT.multilineText1;
			FTT.multilineText1 = FTT.multilineText1.replace(/(\{\{#tag:syntaxhighlight\|(([^S]|S(?!YNTAXHLEND))*))(?!SYNTAXHLNOWIKIOPEN)([\{\}\|])(?!SYNTAXHLNOWIKICLOSE)/g,'$1SYNTAXHLNOWIKIOPEN$4SYNTAXHLNOWIKICLOSE');
		}
		if ( FTT.settings.recombineNowiki ) { //to combine (read "nowiki" where it says "lorem?") <lorem?>{</lorem?><lorem?>{</lorem?> into <lorem?>{{</lorem?>, can prevent conversion from {{#tag:syntaxhighlight to <syntaxhighlight or prevent nowiki tags from being filtered out when editing a comment
			FTT.multilineText1 = FTT.multilineText1.replace(/SYNTAXHLNOWIKICLOSESYNTAXHLNOWIKIOPEN/g,'');
		}
		FTT.multilineText1 = FTT.multilineText1.replace(/SYNTAXHLNOWIKIOPEN([\{\}\|]+)SYNTAXHLNOWIKICLOSE/g,'<' + FTT.nowiki + '>$1</' + FTT.nowiki + '>').replace(/PARSEWIKITAGNEWLINE /g,'{{' + FTT.B1.newline + '}}').replace(/SYNTAXHL(PARAMS)?(START|END)/g,'');
	}
	return FTT.multilineText1.replace(/^[\s]*/, '').replace(/\n\n/gm,'<br style="margin-bottom:0.5em"/>').replace( /\n/gm,'<br/>');
};
FTT.addSignature = function(text,mode) {
	if ( !FTT.forceSign && (text.match(/NOSIGN$/) || ( FTT.UITextInputSummary && FTT.UITextInputSummary.getValue().match(/\!nosi(gn|ne)\!/) ) || FTT.PRMOpened.type == 'edit' || ( ! FTT.isDiscussionPage && ! $('.FTTCmt,.LegacyCmt')[0] && ['FCL','comment'].indexOf(FTT.PRMOpened.type) == -1 ) ) ) { //if the type is FCL or comment we could be dealing with a transcluded page like in The Signpost
//		FTT.debug('found NOSIGN or this is an edit of an existing comment or not a talk page, skipping signature');
		FTT.detectedNOSIGN = true;
		return text.replace(/[ ]*NOSIGN$/,'');
	} else {
//		FTT.debug('adding signature');
		FTT.signatureAdded = true;
		FTT.addSigSeparator = ' ';
		if ( text.trim().match(/(&nbsp;|<\/([Pp]re|PRE|[Ss]yntax[Hh]igh[Ll]ight|SYNTAXHIGHLIGHT)>|\}\})$/) || text.match(/\n$/) ) {
			FTT.addSigSeparator = '';
		}
		if ( mode == 'livepreview' ) {
			FTT.addSigSeparator = '<span class="FTTSigSeparator">'+FTT.addSigSeparator+'</span>'; //class to easily filter this node from smart live preview for performance reasons
		}
		if ( FTT.settings.autoDash && ! FTT.flattenWikiText(mw.user.options.get('nickname') || '' ).match(/^[ ]?[\-\—\/]/) ) {
			FTT.addSigSeparator = FTT.addSigSeparator+'—&nbsp;';
//			FTT.debug('your username doesn\'t start with a dash yet');
		}
		if ( FTT.settings.useLocator ) {
			if ( ! FTT.userName ) {
				FTT.userName = '';
			}
			FTT.locatorID = FTT.escapeHTML(FTT.userName) + ':{{subst:#time:xNU}}' + FTT.commentMilliseconds + ':' + FTT.escapeHTML(FTT.PRMOpened.pageTitle.replace(/:/g, 'FTTCLN'));
			FTT.locatorID = FTT.locatorID.replace(/ /g, '_');
//			FTT.debug('adding locator, removing excess signatures and adding newline if the comment ends with a list');
			return text.replace(/[ ]?~~~~$/,'').replace(/(^|\n)([\#\*].*$)/,'$1$2\n') + FTT.endOfCommentLocator.replace(/INNERCONTENT/g, FTT.addSigSeparator+'~~~~').replace(/USERTIME/g, FTT.locatorID);
		} else {
//			FTT.debug('adding legacy signature, removing excess signatures and adding newline if the comment ends with a list');
			return text.replace(/[ ]?~~~~$/,'').replace(/(^|\n)([\#\*].*$)/,'$1$2\n') + FTT.addSigSeparator + '~~~~';
		}
	}
};
FTT.scrollAndCall = function(el,callback,align) {
	FTT.currPageXOffset = window.pageXOffset;
	FTT.currPageYOffset = window.pageYOffset;
	if ( align == 'center' ) {
		FTT.scrollAlign = FTT.smoothScroll;
	} else {
		FTT.scrollAlign = {behavior: 'smooth',block:'nearest',inline: 'nearest'};
	}
	FTT.scrolling = true;
	if ( align == 'magic' ) {
		FTT.magicScroll(el);
	} else {
		el.scrollIntoView(FTT.scrollAlign);
	}
	var scrollDone = setInterval(function () {
		if ( FTT.currPageXOffset == window.pageXOffset && FTT.currPageYOffset == window.pageYOffset ) {
			clearInterval(scrollDone);
			delete FTT.scrolling;
			if ( callback ) {
				callback();
			}
		}
		FTT.currPageXOffset = window.pageXOffset;
		FTT.currPageYOffset = window.pageYOffset;
	},50);
};
FTT.magicScroll = function(el) {
//	FTT.debug('scroll to 3em above the passed element');
//	FTT.debug(el);
	FTT.tempScrollEl = document.createElement('span');
	FTT.tempScrollEl.id = 'tempScrollEl';
	FTT.tempScrollEl.style = 'top:-3em;position:relative';
	el.prepend(FTT.tempScrollEl);
	$(document).ready(function() {
		$('#tempScrollEl')[0].scrollIntoView({behavior:'smooth',block:'start',inline:'nearest'});
		$('#tempScrollEl').remove();
	});
};
FTT.cancelReply = function(trigger) {
	if ( FTT.getValue() == '' ) {
		FTT.formChanged = false;
	}
	FTT.cancelReplyConfirm = function() {
		mw.loader.using(['oojs-ui-core','oojs-ui-windows']).then(function(){
			FTT.FFDarkMode();
			$('#FTTOverlay').addClass('FTTNoDisplay');
			OO.ui.confirm(FTT.B1['mw-widgets-abandonedit']).done( function(confirmd) {
				FTT.FFDarkMode(1);
				$('#FTTOverlay').removeClass('FTTNoDisplay');
				if (confirmd) {
					FTT.formChanged = false; //or you don't care about changes made
					FTT.cancelReply('user'); //either way now the form will be closed for real
				}
			});
		});
	};
	if ( ( FTT.formChanged && FTT.settings.warnCancel ) && trigger == 'esc' ) {
		FTT.scrollAndCall($('#FTTReplyForm')[0],function(){FTT.UITextInput.focus();FTT.cancelReplyConfirm();});
		return;
	} else if ( ( FTT.formChanged && FTT.settings.warnCancel ) && trigger == 'user' ) {
		FTT.cancelReplyConfirm();
		return;
	}
	FTT.openingFormInProgress = new Date().getTime();
//	FTT.debug('check if there\'s a reply form to remove');
	if ( document.getElementById('FTTForm-' + FTT.PRMOpened.id) ) {
		if ( document.getElementById('FTTForm-' + FTT.PRMOpened.id).parentElement.classList.contains('FTTnewLi') && ['user','esc'].includes(trigger) ) {
			document.getElementById('FTTForm-' + FTT.PRMOpened.id).parentElement.remove();
		} else {
			document.getElementById('FTTForm-' + FTT.PRMOpened.id).remove();
		}
		FTT.removeOverlay();
		if ( FTT.settings.floatReturn ) {
			FTT.hidefloatReturn();
		}
		$('.FTTCmtLink.FTTSVGPingIcon').addClass('FTTSVGIcon').removeClass('FTTSVGPingIcon');
		$('.FTTSVGLinkIconArrow').addClass('FTTSVGLinkIcon').removeClass('FTTSVGLinkIconArrow');
		$('.FTTCollapMini.FTTNoDisplay').removeClass('FTTNoDisplay');
//		FTT.debug('there is, removed it');
		if ( FTT.getValue().length < 40 ) {
			FTT.setValue('');
		}
	}
	if ( ['user','esc'].includes(trigger) ) {
		FTT.saveDraft(FTT.PRMOpened, 'remove', 'user');
		FTT.setValue('');
		FTT.UITextInputTitle.setValue('');
		FTT.UITextInputSummary.setValue('');
		if ( M1('wgAction') == 'edit' && M2('section') == 'new' ) {
			$('#editform,#wikiPreview').removeClass('FTTNoDisplay');
			$('#content>.ve-init-target .ve-ui-toolbar,#content>.ve-init-target #bodyContent .ve-init-mw-desktopArticleTarget-uneditableContent,.ve-init-mw-desktopArticleTarget-originalContent #mw-content-text>p:first-child,.ve-init-mw-desktopArticleTarget-originalContent .ve-init-mw-desktopArticleTarget-sectionTitle,.mw-body-content>.ve-ce-surface').removeClass('FTTNoDisplay');
			$('#mw-content-text.VEhidden').addClass('ve-init-mw-desktopArticleTarget-editableContent').removeClass('VEhidden');
		}
		if ( FTT.escPageXOffset || FTT.escPageYOffset ) {
			window.scrollTo({left:FTT.escPageXOffset,top:FTT.escPageYOffset,behavior:'smooth'});
		}
	}
	FTT.UNHLCmt();
	$('#mw-content-text').removeClass('FTTNoDisplay'); //full page editing can hide page content
	$('#FTTnSecBottom').removeClass('FTTNoDisplay');
	$('#mw-content-text .FTTFirstReply').removeClass('FTTNoDisplay'); //re-enable reply-to-section-starter speech balloons that were hidden when opening the form
};
FTT.wikiTableToHtml = function(wikitable){
	FTT.wikitableOld = wikitable.trim().replace(/[ ]?(scope|class|id|style|colspan|rowspan)[ ]?=[ ]?["]?(([^\n"\|scri]|s(?!cope|tyle)|c(?!lass|olspan)|r(?!owspan)|i(?!d=))*)["]?/g,' $1="$2"').replace(/\n /g,'\n')
	.replace(/^\{\|(( (class|id|style)="[^"]*")*)(\n\|\+.*)?(\n\|\-)?([^]*)\|\}$/,'<table$1>$4<tr>$5$6</tr></table>')
	.replace(/^(<table[^>]*>)\n\|\+((([ ](scope|class|id|style)="[^\n"]*")*)[ ]?\|)?[ ]?(.*)<tr>/,'$1<caption$3>$6</caption><tr>');
	FTT.wikitableNew = '';
	FTT.wikitablecellreplace = 0;
	while ( FTT.wikitableOld != FTT.wikitableNew && FTT.wikitablecellreplace < 100 ) {
		if ( FTT.wikitablecellreplace != 0 ) {
			FTT.wikitableOld = FTT.wikitableNew;
		}
		FTT.wikitableNew = FTT.wikitableOld
		.replace(/\n\|\-(( (class|id|style)="[^"]*")*)/g,'</tr><tr$1>')
		.replace(/(\n|\|)\|((([ ](class|id|style|colspan|rowspan)="[^"]*")*)[ ]?\|)?[ ]?(([^\|<\n]|<(?!th>|tr>|td>|\/tr>|\/td|\/th))*)/g,'<td$3>$6</td>') // \n| is the first cell, || are following cells
		.replace(/(\n|\!)\!((([ ](scope|class|id|style)="[^\n"]*")*)[ ]?\|)?[ ]?(([^\!<\n]|<(?!th>|tr>|td>|\/tr>|\/td|\/th))*)/g,'<th$3>$6</th>')
		.replace(/<tr><\/tr><tr>/g,'<tr>');
		FTT.wikitablecellreplace++;
	}
	return FTT.wikitableNew;
};
FTT.htmlToWikiTable = function(htmltable){
	FTT.htmlTableString = htmltable.trim()
	.replace(/^<table([^>]*)>([^]*)<\/table>/,'{|$1\n$2\n|}') //table
	.replace(/<[\/]?(tbody|thead)>/g,'') //don't really need those me thinks
	.replace(/<\/th><td/g,'</th>\n<td') // force table cells after a TH onto a new line, otherwise MediaWiki converts those td elements to th because fuck if I know
	.replace(/^\{\|(.*)\n<caption((([ ](class|id|style)="[^"]*")*)[ ]?)>(([^<]|<(?!\/caption))*)<\/caption>/,'{|$1\n|+ $2 | $6') //caption with class etc
	.replace(/^\{\|\n<caption>(([^<]|<(?!\/caption))*)<\/caption>/,'\n|+ $1') //bare caption
	.replace(/(\n|<tr((([ ](class|id|style)="[^"]*")*)[ ]?)?>)<td((([ ](class|id|style|colspan|rowspan)="[^"]*")+)[ ]?)>(([^\n<]|<(?!\/td>))*)<\/td>/g,'$1\n| $6 | $10') //td with class etc, first of row
	.replace(/(\n|<tr((([ ](class|id|style)="[^"]*")*)[ ]?)?>)<td>(([^\n<]|<(?!\/td>))*)<\/td>/g,'$1\n| $6') //bare td, first of row
	.replace(/[\n]{2,9}/g,'\n')
	.replace(/<td((([ ](class|id|style|colspan|rowspan|data\-sort\-value)="[^"]*")+)[ ]?)>(([^\n<]|<(?!\/td>))*)<\/td>/g,' || $1 | $5') //td with class/etc, beyond the first
	.replace(/<td>(([^\n<]|<(?!\/td>))*)<\/td>/g,' || $1') //bare td beyond the first
	.replace(/<tr((([ ](class|id|style)="[^"]*")*)[ ]?)?><th((([ ](class|id|style|scope)="[^"]*")+)[ ]?)>(([^\n<]|<(?!\/th>))*)<\/th>/g,'<tr$1>\n! $5 | $9') //th with class etc, first of row
	.replace(/<tr((([ ](class|id|style|scope)="[^"]*")*)[ ]?)?><th>(([^\n<]|<(?!\/th>))*)<\/th>/g,'<tr$1>\n! $5') //bare th, first of row
	.replace(/<th((([ ](class|id|style|scope|data\-sort\-value|data\-sort\-type)="[^"]*")+)[ ]?)>(([^\n<]|<(?!\/th>))*)<\/th>/g,' !! $1 | $5') //th with class/etc, beyond the first
	.replace(/<th>(([^\n<]|<(?!\/th>))*)<\/th>/g,' !! $1') //bare th beyond the first
	.replace(/<tr((([ ](class|id|style)="[^">]*")*)[ ]?)?>(([^<]|<(?!\/tr>))*)<\/tr>/g,'\n|-$1\n$5') //new row
	.replace(/[\n]{2,9}/g,'\n')
	.replace(/[ ]{2,9}/g,' ')
	.replace(/=[ ]?"[ ]?([^"])[ ]?"/g,'="$1"');
	return FTT.htmlTableString.replace(/(<br\/>|\n){2,}/g,'\n');
};
FTT.processComment = function(text, mode) {
//	FTT.processCommentStart = new Date().getTime();//FTT.debug
	if ( mode != 'toVisual' && FTT.activeEditor == 'visualLight' ) {
//		FTT.debug('sync to source before processing comment');
		FTT.syncToSource();
	}
	if ( FTT.settings.filterDirMarks ) {
		if ( FTT.CSSContentDir == 'ltr' && text.match(/\u200e/) && ! text.match(/\u200f/) ) {
//			FTT.debug('filtering left-to-right marks from text as this is a left-to-right wiki already and there are no right-to-left marks in the input. The marks probably result from copy-pasting.');
			text = text.replace(/\u200e/g,'');
		} else if ( FTT.CSSContentDir == 'rtl' && text.match(/\u200f/) && ! text.match(/\u200e/) ) {
//			FTT.debug('filtering right-to-left marks from text as this is a right-to-left wiki already and there are no left-to-right marks in the input. The marks probably result from copy-pasting.');
			text = text.replace(/\u200f/g,'');
		}
	}
	text = text.replace(/(<[Ss]yntax[Hh]igh[Ll]ight)(>[^]*<\/[Ss]yntax[Hh]igh[Ll]ight>)/g,'$1 lang=text$2');
	if ( !['preview','livepreview','toVisual'].includes(mode) ) {
		text = text.trim(); //replace(/[\s]*$/, '');
	}
	text = FTT.applyModules('processComment',text);
	if ( FTT.settings.enableCIThatRun || FTT.settings.enableCIThatRunCmt ) {
//		FTT.debug('applying your regular expressions');
		try{text = FTT.runCI(text,mode);} catch (e) {FTT.notify(FTT.B1['wikieditor-toolbar-tool-replace-invalidregex'].replace(/\$1/,FTT.basicmsgs.actionfailed));}
	}
	if ( M1('wgPageContentModel') == 'wikitext' ) {
		text = FTT.safeText(text,'rewritun'); //rewritun is done inside safing as rewritun can't be done after safing (when the URLs are safed) but is preferably done after HTML comments etc have been safed
		if ( FTT.settings.AWBtypos && ( !FTT.isMobile || !FTT.settings.MFAdjAWBtypos) && mode != 'toVisual' && (mode != 'preview' || FTT.settings.AWBtypoPreview) && mode != 'livepreview' ) { //in preview mode doPreview has already applied RETF
			text = FTT.RETF(text);
		}
		if ( FTT.settings.bbcode ) {
			text = FTT.applyBBCode(text);
		}
		if ( FTT.settings.markdown ) {
			text = FTT.applyMarkdown(text);
		}
		text = text.replace(/\*\*(([^\*\n]|\*[^\*])+)\*\*/g, 'FTTBOLDTAGOPEN$1FTTBOLDTAGCLOSE').replace(/(^|[^:\[])\/\/(([^\/\:]|\:..|\/[^\/])+)(?![:\[])\/\/($|[^\/])/g, '$1FTTITALICTAGOPEN$2FTTITALICTAGCLOSE$4');//the :.. allows a URL within italicized text
		text = FTT.safeText(text,'unsafe');
		if(mode!='livepreview'){
			FTT.setValue(text.replace(/FTTBOLDTAGOPEN(([^F]|F(?!TTBOLDTAGCLOSE))*)FTTBOLDTAGCLOSE/g,'**$1**').replace(/FTTITALICTAGOPEN(([^F]|F(?!TTITALICTAGCLOSE))*)FTTITALICTAGCLOSE/g,'//$1//'));
		}
		text = text.replace(/FTTBOLDTAGOPEN(([^F]|F(?!TTBOLDTAGCLOSE))+)FTTBOLDTAGCLOSE/g,'<b>$1</b>').replace(/FTTITALICTAGOPEN(([^F]|F(?!TTITALICTAGCLOSE))*)FTTITALICTAGCLOSE/g,'<i>$1</i>');
		if ( text.match(/(^|\n)\{\|/) ) {
			text = FTT.safeText(text);
			FTT.tablesInText = text.match(/(^|\n)\{\|.*(\n[ ]?[\|\!][^\}].*)*\n\|\}/g);
			if ( FTT.tablesInText && ['comment','edit','newsection'].includes(FTT.PRMOpened.type) ) {
				for (FTT.tablesInTextInt=0;FTT.tablesInTextInt<FTT.tablesInText.length;FTT.tablesInText++){
					text = text.replace(FTT.tablesInText[FTT.tablesInTextInt].trim(),FTT.wikiTableToHtml(FTT.tablesInText[FTT.tablesInTextInt]));
				}
			}
			text = FTT.safeText(text,'unsafe');
		}
	}
	if ( FTT.settings.runCIAgain && mode != 'livepreview' ) {
		try{text = FTT.runCI(text,mode);} catch (e) {FTT.notify(FTT.B1['wikieditor-toolbar-tool-replace-invalidregex'].replace(/\$1/,FTT.basicmsgs.actionfailed));}
	}
	if ( FTT.settings.runRewritunAgain && mode != 'livepreview' ) {
		text = FTT.safeText(text,'rewritun');
		text = FTT.safeText(text,'unsafe');
	}
	FTT.signatureAdded = false;
	if ( FTT.PRMOpened.type == 'editFullPage' || ( FTT.PRMOpened.type == 'heading' && FTT.PRMOpened.subtype == 'edit' ) ) {
//		FTT.debug('return processed text for full page/section edit');
//		FTT.processCommentEnd = new Date().getTime();//FTT.debug
//		FTT.debug('processed comment in ' + (FTT.processCommentEnd - FTT.processCommentStart)+'ms');
		return text.replace(/[ ]*NOSIGN$/,'');//if there's NOSIGN here the user added it erroneously
	} else if ( mode == 'toVisual' ) {
//		FTT.debug('return processed text for toVisual');
		text = FTT.convertToOneLineCmt(text);
//		FTT.processCommentEnd = new Date().getTime();//FTT.debug
//		FTT.debug('processed comment in ' + (FTT.processCommentEnd - FTT.processCommentStart)+'ms');
		return text;
	} else {
		FTT.detectedNOSIGN = false;
		text = FTT.addSignature(text,mode);
		if ( FTT.PRMOpened.type == 'edit' ) {
//			FTT.debug('return processed text for comment edit');
			text = FTT.convertToOneLineCmt(text).replace(/[\s]*$/, '');
//			FTT.processCommentEnd = new Date().getTime();//FTT.debug
//			FTT.debug('processed comment in ' + (FTT.processCommentEnd - FTT.processCommentStart)+'ms');
			return text;
		} else if ( FTT.detectedNOSIGN ) {
//			FTT.debug('found NOSIGN, no signature added, will not convert comment to one line');
//			FTT.processCommentEnd = new Date().getTime();//FTT.debug
//			FTT.debug('processed comment in ' + (FTT.processCommentEnd - FTT.processCommentStart)+'ms');
			return text;
		} else {
//			FTT.debug('return processed and signed text');
			text = FTT.convertToOneLineCmt(text);
//			FTT.processCommentEnd = new Date().getTime();//FTT.debug
//			FTT.debug('processed comment in ' + (FTT.processCommentEnd - FTT.processCommentStart)+'ms');
			return text;
		}
	}
};
FTT.parsePageInPlace = function(newInitTime,storedFormData,force){ // learned a great deal from w:enUser:BrandonXLF/QuickEdit.js for this, thanks BrandonXLF!
	if ( $('.FTTNewCmtSubscribed,.FTTNewCmt')[0] && ! force && FTT.settings.reparseConfirm ) {
		mw.loader.using(['oojs-ui']).then(function(){
			OO.ui.confirm(FTT.msgs.reparseConfirmPopup).done(function(a){
				if(a){
					FTT.parsePageInPlace(newInitTime,storedFormData,1);
				}
			});
		});
		return;
	}
	if ( (FTT.isMobile && FTT.PRMOpened.type != 'comment') || FTT.settings.afterPostReload || ! $('#mw-content-text>.mw-parser-output')[0] ) {
//		FTT.debug('parsePageInPlace: you\'d rather just have the page reloaded than reparsed, so let\'s do that. (or there is no #mw-content-text>.mw-parser-output on this page)');
//		if(FTT.settings.debug){mw.loader.using(['oojs-ui']).then(function(){OO.ui.confirm('Reload?').done(function(a){if(a){FTT.skipWarnExit = true;location.reload();}});});}else{//FTT.debug
			var DelayedReload = setInterval(function () {
				clearInterval(DelayedReload);
				FTT.skipWarnExit = true;
				location.reload(); //if we reload immediately we may not get served the most recent revision with our comment
			}, 2000);
//		}//FTT.debug
		return;
	}
//	FTT.debug('parsePageInPlace: obtain parsed version of this page and replace current contents');
	api.get({action:'parse',page:M1('wgPageName'),format:'json',disablelimitreport:true,prop:['text','categorieshtml']}).then(function(data){
//		FTT.debug('parsePageInPlace: got parsed page:');
//		FTT.debug(data);
		FTT.timestampInit = ( newInitTime || new Date().getTime() );
		$('.FTTFirstReply').remove();
		FTT.pageCategoriesElement = $('#catlinks');
		$('#mw-content-text>.mw-parser-output')[0].outerHTML = data.parse.text['*'];
		FTT.pageCategoriesElement[0].outerHTML = data.parse.categorieshtml['*'];
		mw.hook('wikipage.content').fire($('#mw-content-text'));
		mw.hook('wikipage.categories').fire(FTT.pageCategoriesElement);
		if ( FTT.settings.floatingToC ) {
//			FTT.debug('parsePageInPlace: refloat the ToC');
			$('.FTTFloatingToC').remove();
			FTT.floatTheTOC();
		}
		delete FTT.legacyCommentCount;
		delete FTT.mwHeadLineArray;
		delete FTT.firstReplyInSectionLastElement;
		FTT.PRM = {};
		FTT.finishedAddingLinks = false;
		$('.FTTCycleBtnsFixed').remove();
//		FTT.debug('run searchNodeContentsLoop (after parsePageInPlace)');
		FTT.searchNodeContentsLoop('reparse');
		$('.FTTPurpleBG').removeClass('FTTPurpleBG'); //almost everything that could cause problems would be reset at this point anyway
		if ( storedFormData ) {
//			FTT.debug('parsePageInPlace: search PRM for the params equal to those of the form that was opened previously');
			delete FTT.searchRPL;
			if ( storedFormData.type == 'heading' && storedFormData.subtype == 'edit' && storedFormData.section == 0 ) {
				FTT.openReplyForm(FTT.PRMLede);
			} else if ( storedFormData.type == 'heading' && storedFormData.subtype == 'edit' ) {
				FTT.searchRPL = FTT.PRMHeadingEdit;
			} else if ( storedFormData.type == 'edit' ) {
				FTT.searchRPL = FTT.PRMEdit;
			} else if ( storedFormData.type == 'editFullPage' ) {
				FTT.openReplyForm(FTT.PRMFP);
			} else if ( storedFormData.type == 'newsection' ) {
				FTT.openReplyForm(FTT.PRMnSec);
			} else {
				FTT.searchRPL = FTT.PRM;
			}
			if ( FTT.UITextInput ) {
				FTT.setValue(storedFormData.text);
			}
			if ( FTT.searchRPL ) {
				for(FTT.rPLSearchInt=0;FTT.rPLSearchInt<Object.keys(FTT.searchRPL).length;FTT.rPLSearchInt++) {
					FTT.searchRPLEntry = FTT.searchRPL[Object.keys(FTT.searchRPL)[FTT.rPLSearchInt]];
					if ( ! FTT.searchRPLEntry.sectionTitle && ( FTT.searchRPLEntry.type == 'heading' || FTT.searchRPLEntry.subtype == 'heading' ) ) {
						FTT.searchRPLEntry = FTT.addPageAndSectionTitleToRPL(FTT.searchRPLEntry);
					}
					if ( ((storedFormData.type != 'heading' && storedFormData.subtype != 'heading') || FTT.searchRPLEntry.sectionTitle == storedFormData.sectionTitle ) && FTT.searchRPLEntry.origReplyTo == storedFormData.origReplyTo && FTT.searchRPLEntry.origTimestamp == storedFormData.origTimestamp && FTT.searchRPLEntry.type == storedFormData.type && FTT.searchRPLEntry.subtype == storedFormData.subtype && FTT.searchRPLEntry.seq == storedFormData.seq ) {
						FTT.openReplyForm(FTT.searchRPLEntry);
						FTT.setValue(storedFormData.text);
						if ( FTT.UITextInputTitle.isVisible() ) {
							FTT.UITextInputTitle.setValue(storedFormData.title);
						}
					}
					if ( storedFormData.summary && FTT.UITextInputSummary ) {
						FTT.UITextInputSummary.setValue(storedFormData.summary);
					}
				}
			}
		}
	}, function ( code, data ) { mw.notify(code,{type:'error'});
	});
};
FTT.invisibleChars = [ //regexp, link name, link title, enwiki article
	[new RegExp('(\u200e)','g'),'U+200E','LEFT-TO-RIGHT MARK','Left-to-right_mark'],
	[new RegExp('(\u200f)','g'),'U+200F','RIGHT-TO-LEFT MARK','Right-to-left_mark'],
	[new RegExp('(\u200b)','g'),'U+200B','ZERO WIDTH SPACE','Zero-width space'],
	[new RegExp('(\u200c)','g'),'U+200C','ZERO WIDTH NON-JOINER','Zero-width non-joiner'],
	[new RegExp('(\u200d)','g'),'U+200D','ZERO WIDTH JOINER','Zero-width joiner'],
	[new RegExp('(\u2060)','g'),'U+2060','WORD JOINER','Word joiner']
];
FTT.doDiff = function(PRM) {
	FTT.UIDiffButton.setDisabled(true);
	mw.loader.load('mediawiki.diff.styles');
	if ( FTT.activeEditor == 'visualLight' ) {
		FTT.syncToSource();
	}
	FTT.processedComment = FTT.processComment(FTT.getValue(),'preview');
	FTT.diffParams = { action:'compare',toslots:'main',prop:'diff|size','fromcontentmodel-main':'wikitext','tocontentmodel-main':'wikitext' };
	if ( PRM.type == 'editFullPage' ) {
		FTT.diffParams.fromtitle = PRM.pageTitle;
		FTT.diffParams['totext-main'] = FTT.processedComment;
	} else if ( PRM.type == 'heading' && FTT.PRMOpened.subtype == 'edit' ) {
		FTT.diffParams.fromslots = 'main';
		if ( typeof FTT.sectionTextForPreload == 'undefined' ) {
			api.get( {action: 'query', export: 'true', format: 'json', titles: PRM.pageTitle} ).then( function ( data ) {
//				FTT.debug(data);
				FTT.wikiTextForEdit = FTT.getWikitextFromExport(data.query.export["*"]);
//				FTT.debug('preload section wikitext');
				FTT.sectionTextForPreload = FTT.getInsertionPointSection(FTT.PRMOpened, FTT.wikiTextForEdit).sectiontext;
				if ( FTT.sectionTextForPreload == null ) {
//					FTT.debug('section not found, try preloading text by section number instead'); //this happens on sections like "== {{int:license-header}} ==" as they don't contain the innerText "Licensing"
					FTT.sectionNumFromLink = -1;
					try{FTT.sectionNumFromLink = Number(FTT.processElementArray[FTT.PRMOpened.int].querySelectorAll('A')[0].href.match(/section=(T\-)?([0-9]*)/)[2]);} catch (e) {}
					if ( FTT.sectionNumFromLink > -1 ) {
						FTT.sectionTextForPreload = FTT.getSectionByNum(FTT.wikiTextForEdit,FTT.sectionNumFromLink);
					} else {
						FTT.sectionTextForPreload = '';
					}
				}
				FTT.doDiff(PRM);
				return;
			}, function ( code, data ) { FTT.APIError(code, data);
			});
			return;
		}
		FTT.diffParams['fromtext-main'] = FTT.sectionTextForPreload;
		FTT.diffParams['totext-main'] = FTT.processedComment;
	} else if ( PRM.type == 'edit' ) {
		FTT.diffParams.fromslots = 'main';
		FTT.diffParams['fromtext-main'] = FTT.wikiTextForEditCommentStripped;
		FTT.diffParams['totext-main'] = FTT.processedComment;
	}
	api.post( FTT.diffParams ).then( function ( data ) {
		FTT.UIDiffButton.setDisabled(false);
		$('#FTTPreviewBox .mw-parser-output')[0].outerHTML = '';
		$('#FTTPreviewBox').removeClass('FTTNoDisplay');
//		FTT.debug('got diff HTML:');
//		FTT.debug(data.compare['*']);
		FTT.diffCompareHTML = data.compare['*'];
		FTT.diffCompareHTML = FTT.diffCompareHTML.replace(/foobar/g,'fooTESTbar');
		for (FTT.invisInt=0;FTT.invisInt<FTT.invisibleChars.length;FTT.invisInt++) {
			FTT.diffCompareHTML = FTT.diffCompareHTML.replace(FTT.invisibleChars[FTT.invisInt][0],'$1<a href="https://en.wikipedia.org/wiki/'+FTT.invisibleChars[FTT.invisInt][3]+'" title="'+FTT.invisibleChars[FTT.invisInt][2]+'" class="FTTRedBG FTTInvisChar">'+FTT.invisibleChars[FTT.invisInt][1]+'</a>');
		}
		if ( ! data.compare['*'] ) {
			FTT.diffHTML = '<div class="mw-parser-output" style="width:100%;text-align:center">' + FTT.B1['diff-empty'] + '</div>';
		} else {
			FTT.diffHTML = '<table class="mw-parser-output diff diff-contentalign-left diff-editfont-monospace"><colgroup><col class="diff-marker"><col class="diff-content"><col class="diff-marker"><col class="diff-content"></colgroup>' + FTT.diffCompareHTML + '</table>';
		}
		$('#FTTPreviewBox').append(FTT.diffHTML);
		if ( data.compare.fromsize ) {
			FTT.diffOldSize = data.compare.fromsize;
		} else if ( PRM.type == 'heading' ) {
			FTT.diffOldSize = new Blob([FTT.sectionTextForPreload]).size;
		} else if ( PRM.type == 'edit' ) {
			FTT.diffOldSize = new Blob([FTT.wikiTextForEditSTLNew.trim()]).size;
		}
		FTT.diffNewSize = new Blob([FTT.getValue()]).size;
		if ( FTT.diffNewSize >= FTT.diffOldSize ) {
			FTT.diffSize = FTT.diffNewSize - FTT.diffOldSize;
			FTT.diffPlusMin = '+';
			if ( FTT.diffSize > 200 ) {
				FTT.diffSizeClass = "FTTDiffPlusBig";
			} else {
				FTT.diffSizeClass = "FTTDiffPlus";
			}
		} else {
			FTT.diffSize = FTT.diffOldSize - FTT.diffNewSize;
			FTT.diffPlusMin = '-';
			if ( FTT.diffSize > 200 ) {
				FTT.diffSizeClass = "FTTDiffMinBig";
			} else {
				FTT.diffSizeClass = "FTTDiffMin";
			}
		}
		FTT.UIdiffSize.toggle(1);
		FTT.UIdiffSize.setLabel(new OO.ui.HtmlSnippet('<span class="' + FTT.diffSizeClass + '">' + FTT.diffPlusMin + FTT.diffSize + '</span>'));
	}, function ( code, data ) { FTT.UIDiffButton.setDisabled(false);FTT.APIError(code, data);
	});
};
FTT.hidefloatReturn = function() {
	$('#FTTFloatReturn')[0].style.opacity = 0;
	var DelayFRToggle = setInterval(function () { //just in case the preview parsing times out or something
		clearInterval(DelayFRToggle);
		FTT.floatReturnLink.toggle(false);
	},520);
};
FTT.unhidefloatReturn = function() {
	FTT.floatReturnLink.toggle(true);
	var DelayOpacity = setInterval(function () { //just in case the preview parsing times out or something
		clearInterval(DelayOpacity);
		$('#FTTFloatReturn')[0].style.opacity = 1;
	},20);
};
FTT.throttle = 0;
FTT.togglefloatReturn = function(throttle) {
	FTT.throttle++;
	throttle = FTT.throttle;
	var DelayThrottle = setInterval(function () { //just in case the preview parsing times out or something
		clearInterval(DelayThrottle);
		if ( throttle == FTT.throttle && FTT.UITextInput && FTT.UITextInput.isElementAttached() ) { //if this function was called again within 500ms the number won't match and it'll skip (performance, scroll event fires way too much)
			FTT.formLocation = $('.FTTForm:eq(0)')[0].getBoundingClientRect();
			if ( FTT.formLocation.y > window.innerHeight ) {
//				FTT.debug('togglefloatReturn: you are above the form. Scroll events fired so far: '+throttle);
				$('#FTTFloatReturn').removeClass('FTTFloatReturnTop');
				FTT.unhidefloatReturn();
			} else if ( FTT.formLocation.y + FTT.formLocation.height < 0 ) {
//				FTT.debug('togglefloatReturn: you are below the form. Scroll events fired so far: '+throttle);
				$('#FTTFloatReturn').addClass('FTTFloatReturnTop');
				FTT.unhidefloatReturn();
			} else {
//				FTT.debug('togglefloatReturn: you are near the form or it\'s in view. Scroll events fired so far: '+throttle);
				FTT.hidefloatReturn();
			}
		}
	},300);
};
FTT.addfloatReturn = function() {
	FTT.floatReturnLink = new OO.ui.ButtonWidget( {
						label: FTT.B1.returnto.replace('$1',FTT.SN),
						id: 'FTTFloatReturn'
	});
	FTT.floatReturnLink.on('click',function(){
		if ( FTT.PRMOpened.justSettings ) {
			FTT.magicScroll($('.FTTForm')[0]);
		} else {
			FTT.scrollAndCall($('#FTTReplyForm')[0],function(){FTT.UITextInput.focus();},'center');
		}
	});
	FTT.floatReturnLink.toggle(false);
	$('body').append(FTT.floatReturnLink.$element);
	$('#FTTFloatReturn')[0].style = 'opacity:0';
};
FTT.doPreview = function(mode, PRM) {
//	FTT.debug('doPreview: mode: '+mode);
	if ( FTT.doPreviewInProgress && FTT.doPreviewInProgress > (new Date().getTime() -12000 ) ) {
//		FTT.debug('doPreview: there\'s already a doPreview in progress that launched less than 12 seconds ago, wait for it to complete or timeout');
		return;
	}
	if ( FTT.queuePreviewTime ) {
//		FTT.debug('doPreview: a scheduled preview request is about to be made, cancel the current instance');
		return;
	}
	FTT.doPreviewInProgress = new Date().getTime();
	if ( mode == 'preview' ) {
		FTT.UIPreviewButton.setDisabled(true);
		var DelayMarkUnlockPreviewButton = setInterval(function () { //just in case the preview parsing times out or something
			clearInterval(DelayMarkUnlockPreviewButton);
			FTT.UIPreviewButton.setDisabled(false);
		},12000);
	}
	if ( FTT.getValue().match(/<\/syntaxhighlight>/i) || M1('wgPageContentModel') == 'javascript' ) {
		mw.loader.load('ext.pygments');
	}
	if ( M1('wgPageContentModel') == 'json' ) {
		mw.loader.load('mediawiki.content.json');
	}
	if ( FTT.getValue().match(/<\/gallery>/i) ) {
		mw.loader.load('mediawiki.page.gallery.styles');
	}
	if ( mode != 'previewposted' ) {
		$('#FTTPreviewBox').removeClass('FTTNoDisplay');
		if ( FTT.settings.checkNewComments && M1('wgArticleId') != 0 ) {
			FTT.checkForNewComments(FTT.PRMOpened, 'preview');
		}
	}
	if ( mode != 'livepreview' && (FTT.settings.rewritun || (FTT.settings.AWBtypos && ( !FTT.isMobile || !FTT.settings.MFAdjAWBtypos)) ) ) {
		FTT.UITextInput.setReadOnly(true);
		if ( FTT.settings.rewritun ) {
			FTT.safeText(FTT.safeText(FTT.getValue(),'rewritun'),'unsafe');
		}
		if ( (!FTT.isMobile || !FTT.settings.MFAdjAWBtypos) && FTT.settings.AWBtypoPreview && FTT.settings.AWBtypos ) {
			FTT.setValue(FTT.RETF(FTT.getValue()));
		}
		FTT.UITextInput.setReadOnly(false);
	}
	if ( FTT.activeEditor == 'visualLight' ) {
		FTT.syncToSource();
	}
	FTT.smartOldVals = [];
	FTT.smartNewVals = [];
	FTT.processedComment = FTT.processComment(FTT.getValue(),mode);
	if ( mode == 'previewposted' && FTT.commentTextIndent && ( PRM.type != 'edit' || ( PRM.type == 'edit' && PRM.freshcomment == true ) ) ) {
//			FTT.debug('doPreview: add the last character+space from the indentation so preview renders properly with its indentation');
			if ( ! FTT.commentTextIndent.match('┌') ) {
				FTT.processedComment = FTT.commentTextIndent.slice(-1) + FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment);
			} else {
//				FTT.debug('doPreview: adjust preview for automatic outdent');
				FTT.processedComment = FTT.commentTextIndent + FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment);
				FTT.FTTFormNegativeMargin = FTT.origCmtIndent * -FTT.indentWidth;
			}
	}
	if ( PRM.type == 'newsection' && FTT.UITextInputTitle.getValue() != '' ) { //this and the one below were restricted to mode == 'previewposted', I probably had a good reason for that..? no idea
		FTT.processedComment = '==' + FTT.UITextInputTitle.getValue() + '==\n' + FTT.processedComment;
	} else if ( PRM.type == 'newheading' && FTT.UITextInputTitle.getValue() != '' ) {
		if ( ! PRM.sectLevel ) {
			PRM.sectLevel = FTT.processElementArray[PRM.pageTitleInt].parentElement.nodeName.slice(1);
		}
		FTT.newSectLevel = '';
		for(FTT.sectLevelInt=0;FTT.sectLevelInt<(Number(PRM.sectLevel)+1);FTT.sectLevelInt++) {
			if ( FTT.newSectLevel.length < 6 ) {
				FTT.newSectLevel = FTT.newSectLevel + '=';
			}
		}
		FTT.processedComment = ( FTT.newSectionLevel || FTT.newSectLevel ) + FTT.UITextInputTitle.getValue() + ( FTT.newSectionLevel || FTT.newSectLevel ) + '\n' + FTT.processedComment;
	//} else if ( PRM.type == 'newheading' ) {
	//	FTT.processedComment = FTT.newTitlelessSubSecIndent + FTT.processedComment;
	} else if ( PRM.type == 'edit' ) {
		FTT.processedComment = FTT.processedComment + FTT.wikiTextForEditSigForPreview;
		FTT.processedComment = FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment);
	}
	FTT.previewCommentParams = {
		format: 'json',
		action: 'parse',
		disablelimitreport: true,
		title: FTT.PRMOpened.pageTitle,
		pst: '1',
		prop: 'text',
		formatversion: '2',
		text: FTT.processedComment,
	};
	if ( FTT.PRMOpened.type != 'editFullPage' ) {
		FTT.previewCommentParams.disableeditsection = true;
	}
	FTT.doAPICall(FTT.previewCommentParams, mode);
};
FTT.compareRevisionsToCheckForComments = function(PRM,mode,wikitextFromPostReply,revidFromPostReply,getWikiText) {
	if ( mode == 'postreply' && FTT.killNewReplyCheck == 1 ) {
		FTT.postReply2(PRM, wikitextFromPostReply, revidFromPostReply);
		return;
	}
	if ( getWikiText != true && mode != 'postreply' ) {
		api.get( {
			action: 'query', prop: 'revisions', format: 'json', titles: PRM.pageTitle, rvlimit: 1, rvprop: 'ids', rvslots: '*',
		} ).then( function ( data ) {
			if ( ! data.query.pages[-1] && data.query.pages[ Object.keys(data.query.pages)[0] ].revisions[0].revid != FTT.pageRevisionIDSinceLastCheck[PRM.pageTitle] ) {
//				FTT.debug('the current revision is different from the last one we checked');
				FTT.pageRevisionIDSinceLastCheck[PRM.pageTitle] = data.query.pages[ Object.keys(data.query.pages)[0] ].revisions[0].revid;
				FTT.compareRevisionsToCheckForComments(PRM,mode,wikitextFromPostReply,revidFromPostReply,true);
//			} else { FTT.debug('no new revisions since the last time we checked');
			}
		}, function ( code, data ) { FTT.APIError(code, data);
		});
	} else {
//		FTT.debug('compare wikitext of current revision to revision since last check');
		FTT.compareRevisionsToCheckForCommentsProcessing = function(PRM,mode,wikitextFromPostReply,revidFromPostReply) {
			if ( PRM.type == 'comment' ) {
				FTT.insertionPointThrowAway = FTT.getInsertionPointComment(PRM, FTT.pageRevisionCurrentText); //we don't do anything with this, but it triggers an error if the comment has vanished
				if ( FTT.insertionPointThrowAway == 'retry' ) {
					FTT.compareRevisionsToCheckForComments(FTT.PRMOpened,mode,wikitextFromPostReply,revidFromPostReply,getWikiText);
					return;
				}
			}
			FTT.pageRevisionCurrentText = FTT.getInsertionPointSection(PRM, FTT.pageRevisionCurrentText).sectiontext;
			FTT.pageRevisionSinceLastCheckText = FTT.getInsertionPointSection(PRM, FTT.pageRevisionSinceLastCheck[PRM.pageTitle]).sectiontext;
			if ( ! FTT.pageRevisionSinceLastCheckText ) { //could happen with a locator if the page was moved. getInsertionPointSection should probably get the same fallback getInsertionPointComment has but I'm too tired
				return;
			}
			if ( FTT.pageRevisionCurrentText != FTT.pageRevisionSinceLastCheckText ){
				FTT.pageRevisionCurrentSplit = FTT.pageRevisionCurrentText.split('\n');
				FTT.linesSinceLastCheck = '';
				for (FTT.commentCheckInt = 0; FTT.commentCheckInt < FTT.pageRevisionCurrentSplit.length; FTT.commentCheckInt++) {
					if ( FTT.pageRevisionSinceLastCheckText.split('\n').indexOf(FTT.pageRevisionCurrentSplit[FTT.commentCheckInt]) == -1 ) {
//						FTT.debug('line not found in the old revision of this page: ' + FTT.pageRevisionCurrentSplit[FTT.commentCheckInt]);
						FTT.newLineUserLinks = FTT.pageRevisionCurrentSplit[FTT.commentCheckInt].match(new RegExp('(?:' + FTT.userNSWikitextRegExpPart + ')([^\\|\\]]*)','g'));
						FTT.newLineLastUser = '';
						if ( FTT.newLineUserLinks ) {
							FTT.newLineLastUser = FTT.newLineUserLinks[FTT.newLineUserLinks.length -1].replace(new RegExp('(' + FTT.userNSWikitextRegExpPart + ')'),'').replace(/_/g,' ');
						}
						if ( ! FTT.submittedLines.includes(FTT.pageRevisionCurrentSplit[FTT.commentCheckInt]) && ! FTT.pageRevisionCurrentSplit[FTT.commentCheckInt].match(/^=.*=($|<!\-\-.*-->$)/) && ( ! ( FTT.newLineLastUser == M1('wgUserName') && FTT.cleanTimestamp(FTT.pageRevisionCurrentSplit[FTT.commentCheckInt]).match(FTT.signDateRegExpLocalMonths) ) ) ) {
							FTT.linesSinceLastCheck = FTT.linesSinceLastCheck + FTT.pageRevisionCurrentSplit[FTT.commentCheckInt] + '\n';
						}
//						else { FTT.debug('changed line appears to be a comment by you, presumably you already know about that'); }
					}
				}
				if ( FTT.linesSinceLastCheck.trim() == '' ) {
//					FTT.debug('there\'s a new revision but no new lines you need to know of');
					FTT.disableForm(false); //re-enable form
					if ( mode == 'postreply' ) {
						FTT.postReply2(PRM, wikitextFromPostReply, revidFromPostReply);
					}
					return;
				}
				api.post( {
					action: 'parse', format: 'json', disablelimitreport: true, disableeditsection: true, title: M1('wgPageName'), pst: '1', prop: 'text', formatversion: '2', text: FTT.linesSinceLastCheck
				} ).then( function ( data ) {
					FTT.UInewLinesRead = new OO.ui.ButtonWidget( {
						label: FTT.msgs.refreshDiscussion,
						flags: [ 'progressive' ],
						id: 'FTTUInewLinesRead'
					} );
					FTT.UInewLinesRead.on('click',function() {
						FTT.storedFormData = {
							title:FTT.UITextInputTitle.getValue(),
							text:FTT.getValue(),
							summary:FTT.UITextInputSummary.getValue(),
							origReplyTo:FTT.PRMOpened.origReplyTo,
							origTimestamp:FTT.PRMOpened.origTimestamp,
							type:FTT.PRMOpened.type,
							subtype:FTT.PRMOpened.subtype,
							seq:FTT.PRMOpened.seq,
							sectionTitle:FTT.PRMOpened.sectionTitle,
							section:FTT.PRMOpened.section,
						};
						FTT.parsePageInPlace(undefined,FTT.storedFormData);
						delete FTT.storedFormData;
					});
					$('#showNewLines')[0].innerHTML = '<b>' + FTT.msgs.newLines + '</b><br />' + data.parse.text;
					$('#showNewLines').append(FTT.UInewLinesRead.$element);
					$('#FTTUInewLinesRead')[0].style = 'width:100%';
					$('#FTTUInewLinesRead a')[0].style = 'width:100%';
					$('#showNewLines').removeClass('FTTNoDisplay');
					$('#showNewLines').addClass('FTTEaseIn FTTPurpleBG');
					$('#showNewLines')[0].scrollIntoView(FTT.smoothScroll);
					var DelayMarkNewLinesAsReadButton = setInterval(function () {
						clearInterval(DelayMarkNewLinesAsReadButton);
						$('#showNewLines').removeClass('FTTPurpleBG');
						FTT.disableForm(false); //re-enable form
					},500);
				}, function ( code, data ) { FTT.APIError(code, data);
				});
			} else if ( mode == 'postreply' ) {
//				FTT.debug('current revision and revision as of the last check are identical (for this section, at least)');
				FTT.postReply2(PRM,wikitextFromPostReply,revidFromPostReply);
			}
		};
		if ( mode == 'postreply' ) {
//			FTT.debug('already have the current wikitext from postReply1');
			FTT.pageRevisionCurrentText = wikitextFromPostReply;
			FTT.compareRevisionsToCheckForCommentsProcessing(PRM,mode,wikitextFromPostReply,revidFromPostReply);
			FTT.pageRevisionSinceLastCheck[PRM.pageTitle] = wikitextFromPostReply;
			FTT.pageRevisionIDSinceLastCheck[PRM.pageTitle] = revidFromPostReply;
		}	else {
			api.get( {
				action: 'query', prop: 'revisions', format: 'json', titles: PRM.pageTitle, rvlimit: 1, rvprop: 'timestamp|content|ids', rvslots: '*',
			} ).then( function ( data ) {
//				FTT.debug('page content now:');
//				FTT.debug(data);
				if ( data.query && data.query.pages && data.query.pages[ Object.keys(data.query.pages)[0] ] && data.query.pages[ Object.keys(data.query.pages)[0] ].revisions && data.query.pages[ Object.keys(data.query.pages)[0] ].revisions[0] ) {
					FTT.pageRevisionCurrentText = data.query.pages[ Object.keys(data.query.pages)[0] ].revisions[0].slots.main['*'];
				} else {
					FTT.pageRevisionCurrentText = '';
				}
				FTT.compareRevisionsToCheckForCommentsProcessing(PRM,mode,FTT.pageRevisionCurrentText,revidFromPostReply);
				if ( data.query && data.query.pages && data.query.pages[ Object.keys(data.query.pages)[0] ] && data.query.pages[ Object.keys(data.query.pages)[0] ].revisions && data.query.pages[ Object.keys(data.query.pages)[0] ].revisions[0] ) {
					FTT.pageRevisionSinceLastCheck[PRM.pageTitle] = data.query.pages[ Object.keys(data.query.pages)[0] ].revisions[0].slots.main['*'];
					FTT.pageRevisionIDSinceLastCheck[PRM.pageTitle] = data.query.pages[ Object.keys(data.query.pages)[0] ].revisions[0].revid;
				} else {
					FTT.addScrewedLink('no revision API','No revision data found in API response.');
				}
			}, function ( code, data ) { FTT.APIError(code, data);
			});
		}
	}
};
FTT.checkForNewComments = function(PRM, mode, wikitextFromPostReply, revidFromPostReply) {
	if ( PRM.type == 'edit' || PRM.justSettings ) {
//		FTT.debug('editing comment or just opening settings, skipping new comment check (unlikely to be interesting for comment edits, might lead to false positives otherwise)');
		return;
	}
	if ( ! PRM.pageTitle ) {
		FTT.addPageAndSectionTitleToRPL(PRM);
	}
	if ( mode != 'postreply' && ( (FTT.lastCheckedForNewComments||0) + 30000 ) > new Date().getTime() && FTT.lastCheckedID == PRM.id ) {
//		FTT.debug('checkForNewComments, already checked for new comments in the past 30 seconds, skipping');
	} else {
//		FTT.debug('checkForNewComments, check for new comments');
		FTT.lastCheckedForNewComments = new Date().getTime();
		FTT.lastCheckedID = PRM.id;
		if ( FTT.pageRevisionSinceLastCheck[PRM.pageTitle] ) {
//			FTT.debug('already have a version that was checked against');
			FTT.compareRevisionsToCheckForComments(PRM,mode,wikitextFromPostReply,revidFromPostReply);
		} else {
//			FTT.debug('first time we check, obtain wikitext of the page as it was when the page was loaded');
			FTT.initialRevisionCheckParams = { 'action': 'query', 'prop': 'revisions|info', 'format': 'json', 'titles': PRM.pageTitle, 'rvlimit': 1, 'rvprop': 'timestamp|content|ids', 'rvslots': '*', 'rvstart': FTT.pageLoadTimeStamp, 'inprop':'watched', 'intestactions':'edit'};
			api.get( FTT.initialRevisionCheckParams ).then( function ( data ) {
//				FTT.debug('page content as it was when loading the page:');
//				FTT.debug(data);
				if ( typeof data.query.pages[ Object.keys(data.query.pages)[0] ].actions.edit != 'string' ) {
					FTT.showProtectedWarning(PRM.pageTitle);
				}
				if ( data.query.pages[-1] ) {
//					FTT.debug('page doesn\'t seem to exist.');
					if ( mode == 'postreply' ) {
						FTT.postReplyInProgress[PRM.id] = true;
						FTT.postReply2(PRM,wikitextFromPostReply,revidFromPostReply);
					}
					return;
				}
				if ( typeof data.query.pages[ Object.keys(data.query.pages)[0] ].watched == 'string' ) {
					if ( typeof data.query.pages[ Object.keys(data.query.pages)[0] ].watchlistexpiry == 'string' ) {
						FTT.watchlistexpiry = new Date(data.query.pages[ Object.keys(data.query.pages)[0] ].watchlistexpiry).getTime();
					}
				}
				if ( data.query && data.query.pages && data.query.pages[ Object.keys(data.query.pages)[0] ] && data.query.pages[ Object.keys(data.query.pages)[0] ].revisions && data.query.pages[ Object.keys(data.query.pages)[0] ].revisions[0] ) {
					FTT.pageRevisionSinceLastCheck[PRM.pageTitle] = data.query.pages[ Object.keys(data.query.pages)[0] ].revisions[0].slots.main['*'];
					FTT.pageRevisionIDSinceLastCheck[PRM.pageTitle] = data.query.pages[ Object.keys(data.query.pages)[0] ].revisions[0].revid;
				} else {
					FTT.pageRevisionSinceLastCheck[PRM.pageTitle] = '';
					FTT.pageRevisionIDSinceLastCheck[PRM.pageTitle] = 0;
				}
				FTT.compareRevisionsToCheckForComments(PRM,mode,wikitextFromPostReply,revidFromPostReply);
			}, function ( code, data ) { FTT.APIError(code, data);
			});
		}
	}
};
FTT.saveDraftEvery = function() {
	var SaveDraftEvery = setInterval(function () {
		if ( $('#FTTReplyForm')[0] ) {
			FTT.saveDraft(FTT.PRMOpened,false,'scheduled');
			FTT.saveDraftEvery();
		}
	clearInterval(SaveDraftEvery);
	}, 20000);
};
FTT.saveDraft = function(PRM, removeDraft, trigger) {
//	if ( trigger != 'scheduled' ) { FTT.debug('should we save a draft? trigger: ' + trigger); }
	if ( PRM.preload ) {
//		FTT.debug('not saving draft as this form requested preloaded content');
		return;
	}
	if ( ! $('#FTTReplyForm')[0] && $('.FTTPendingBlink')[0] && trigger != 'user' && removeDraft != 'remove' ) { //if there's a FTTPendingBlink posting is in progress
//		FTT.debug('there is no open reply form and you didn\'t press cancel');
	} else {
		FTT.savedDrafts = FTT.testValidJSON(FTT.getItemLS('FTTDrafts',1));
		if ( PRM.type != 'heading' ) {
			FTT.draftID = D1(PRM.id.replace(/:[0-9]+$/,'')); //element number can vary between page loads for a million reasons so strip it
		} else {
			FTT.draftID = D1(PRM.pageTitle+PRM.sectionTitle+PRM.sectionseq.toString()); //section titles aren't very unique so include sectionseq
		}
		if ( FTT.savedDrafts ) {
//			if ( trigger != 'scheduled' ) { FTT.debug('found existing drafts in localStorage'); }
			if ( removeDraft != 'remove' && FTT.savedDrafts[FTT.draftID] && ( FTT.savedDrafts[FTT.draftID].text == FTT.getValue() ) ) {
				FTT.draftUnchanged = true;
			} else {
				delete FTT.draftUnchanged;
			}
		} else {
//			FTT.debug('no existing drafts found');
			FTT.savedDrafts = {};
			delete FTT.draftUnchanged;
		}
		FTT.timestampNowDraft = new Date().getTime();
		FTT.timestampNowDraftLastWeek = FTT.timestampNowDraft - 604800000;
		for (FTT.FTTDraftInt = 0; FTT.FTTDraftInt < Object.keys(FTT.savedDrafts).length; FTT.FTTDraftInt++) {
			FTT.processDraftKey = Object.keys(FTT.savedDrafts)[FTT.FTTDraftInt];
//			FTT.debug('saveDraft: processDraftKey '+FTT.processDraftKey);
			if ( FTT.savedDrafts[FTT.processDraftKey].time < FTT.timestampNowDraftLastWeek ) {
//				FTT.debug('draft ' + FTT.processDraftKey + ' is over a week old, removing it');
				delete FTT.savedDrafts[FTT.processDraftKey];
			}
		}
		if ( FTT.getValue().length > 100 && ! removeDraft ) {
			FTT.savedDrafts[FTT.draftID] = {'time':FTT.timestampNowDraft,'text':FTT.getValue()};
		} else if ( FTT.savedDrafts[FTT.draftID] ) {
//			FTT.debug('too short or deletion requested, removing draft ' + FTT.draftID + ', trigger: ' + trigger);
			if ( trigger == 'user' && FTT.getValue() > 100 ) {
				mw.notify(FTT.msgs.removeDraft);
			}
			delete FTT.savedDrafts[FTT.draftID];
		} else {
//			FTT.debug('no change to drafts found');
			FTT.draftUnchanged = true;
		}
		if ( ! FTT.draftUnchanged || trigger == 'user' ) {
//			FTT.debug('save drafts to localStorage, trigger: ' + trigger);
			FTT.setItemLS('FTTDrafts',FTT.pack(FTT.savedDrafts,'UTF16'));
		} else {
//			if ( trigger != 'scheduled' ) { FTT.debug('no change, skipped saving'); }
		}
	}
};
FTT.closeSettings = function() {
	if ( FTT.PRMOpened.justSettings ) {
		FTT.setValue('');FTT.UITextInputTitle.setValue(''); //generally nothing could be entered here, but to be absolutely sure
		FTT.cancelReply();
		delete FTT.PRMOpened;
	} else {
		$('.FTTSettings').addClass('FTTNoDisplay');
	}
};
FTT.unixTimeToFlatDate = function(timestamp) { //in: 1649799410000 out: 202204132136
	FTT.unixTimeToFlatDateYear = new Date(new Date(timestamp).toLocaleString('en',{timeZone:FTT.wikiTimezone})).getFullYear();
	FTT.unixTimeToFlatDateMonth = (new Date(new Date(timestamp).toLocaleString('en',{timeZone:FTT.wikiTimezone})).getMonth()) + 1;
	if ( FTT.unixTimeToFlatDateMonth < 10 ) {
		FTT.unixTimeToFlatDateMonth = '0' + FTT.unixTimeToFlatDateMonth;
	}
	FTT.unixTimeToFlatDateDay = (new Date(new Date(timestamp).toLocaleString('en',{timeZone:FTT.wikiTimezone})).getDate());
	if ( FTT.unixTimeToFlatDateDay < 10 ) {
		FTT.unixTimeToFlatDateDay = '0' + FTT.unixTimeToFlatDateDay;
	}
	FTT.unixTimeToFlatDateHours = new Date(new Date(timestamp).toLocaleString('en',{timeZone:FTT.wikiTimezone})).getHours();
	if ( FTT.unixTimeToFlatDateHours < 10 ) {
		FTT.unixTimeToFlatDateHours = '0' + FTT.unixTimeToFlatDateHours;
	}
	FTT.unixTimeToFlatDateMinutes = new Date(new Date(timestamp).toLocaleString('en',{timeZone:FTT.wikiTimezone})).getMinutes();
	if ( FTT.unixTimeToFlatDateMinutes < 10 ) {
		FTT.unixTimeToFlatDateMinutes = '0' + FTT.unixTimeToFlatDateMinutes;
	}
	return FTT.unixTimeToFlatDateYear + FTT.unixTimeToFlatDateMonth + FTT.unixTimeToFlatDateDay + FTT.unixTimeToFlatDateHours + FTT.unixTimeToFlatDateMinutes;
};
FTT.createPingText = function(PRM,mode,trigger) {
//	FTT.debug('createPingText: create ping text');
	if ( PRM.origReplyTo == '' ) {
		return;
	}
	FTT.recipientIsAnon = PRM.origReplyTo.match(/(^anon$|^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$|\:)/); //IPv4 or contains a colon as in IPv6 which is disallowed in usernames. Will need adjustment after IP anonimization
	FTT.checkHtmlForNoping = FTT.processElementArray[PRM.int].parentElement.innerHTML;
	FTT.checkLessHtmlForNopingRegExp = new RegExp('E1(PRM.origReplyTo).*NOPING.*' + E1(PRM.origTimestamp));
	FTT.checkLessHtmlForNoping = '';
	if ( FTT.checkHtmlForNoping.match(FTT.checkLessHtmlForNopingRegExp) ) {
		FTT.checkLessHtmlForNoping = FTT.checkHtmlForNoping.match(FTT.checkLessHtmlForNopingRegExp)[0];
	}
	FTT.userHatesPingRegExp = new RegExp('(' + E1(PRM.origReplyTo) + '.*)NOPING(\:[^\:<\>\"]{0,120})?(\:[^\:<\>\"]{0,120})?(\:[^\:<\>\"]{0,120})?(\:[^\:<\>\"]{0,120})?(\:[^\:<\>\"]{0,120})?(.{0,100}' + E1(PRM.origTimestamp) + '.*)');
	FTT.userHatesPing = FTT.checkLessHtmlForNoping.match(FTT.userHatesPingRegExp);
	FTT.userHatesPingCauseISaidSo = 0;
	FTT.neverPingArr = FTT.settings.neverPing.split('|');
	if ( FTT.neverPingArr.includes(E1(PRM.origReplyTo).replace(/_/g,' ')) ) {
		FTT.userHatesPing = true;
		FTT.userHatesPingCauseISaidSo = 1;
	}
	PRM = FTT.addPageAndSectionTitleToRPL(PRM);
	if ( mode == 'addping' && PRM.pageTitle.replace(/_/g,' ') == FTT.PRMOpened.pageTitle.replace(/_/g,' ') && PRM.sectionTitle.replace(/_/g,' ') == FTT.PRMOpened.sectionTitle.replace(/_/g,' ') && PRM.sectionseq == FTT.PRMOpened.sectionseq ) {
//		FTT.debug('createPingText: you pressed a speech balloon in the same section where you are adding a comment');
		FTT.createdPingText = FTT.settings.pingTextInSection.replace(/PINGUSER/g, FTT.escapeReplacement(PRM.origReplyTo.replace(/_/g,' ')));
		if ( PRM.subtype == 'locator' ) {
			FTT.createdPingText = FTT.createdPingText.replace(/CMTLINK/g, '#' + FTT.escapeReplacement(PRM.id));
		} else if ( PRM.subtype == 'legacy' ) {
			FTT.addAnchorTimestamp = FTT.sigDateToMachineReadable(PRM.origTimestamp);
			FTT.addAnchorCmtLink = FTT.unixTimeToFlatDate(FTT.addAnchorTimestamp) + '_' + PRM.origReplyTo.replace(/ /g,'_');
			FTT.createdPingText = FTT.createdPingText.replace(/CMTLINK/g, '#' + FTT.escapeReplacement(FTT.addAnchorCmtLink));
		}
	} else {
		FTT.createdPingText = FTT.settings.pingText.replace(/PINGUSER/g, FTT.escapeReplacement(PRM.origReplyTo.replace(/_/g,' ')));
	}
	if ( trigger != 'doubleclick' && FTT.settings.quoteSelect && FTT.selectedText && FTT.selectedText.trim() != '' ) {
		if ( FTT.B1.tq != '' ) {
			FTT.quoteSelect = '{{'+FTT.B1.tq+'|1='+FTT.selectedText+'}} ';
		} else {
			FTT.quoteSelect = FTT.wikiMsgs.quoteOpen+FTT.selectedText+FTT.wikiMsgs.quoteClose+' ';
		}
		FTT.createdPingText = FTT.createdPingText + FTT.quoteSelect;
		FTT.pingTextAnon = FTT.pingTextAnon + FTT.quoteSelect;
	} else {
		FTT.quoteSelect = '';
	}
	if ( FTT.recipientIsAnon != null ) {
//		FTT.debug('createPingText: anon user found');
		return FTT.pingTextAnon.replace(/PINGUSER/g, FTT.escapeReplacement(PRM.origReplyTo));
	} else if ( FTT.userHatesPing && FTT.userName ) {
//		FTT.debug('createPingText: user hates pings and you are logged in');
		FTT.userNemesis = FTT.checkLessHtmlForNoping.replace(FTT.userHatesPingRegExp, '$2$3$4$5$6').replace(/_/g, ' ');
		if ( FTT.userNemesis == '' || FTT.userNemesis.match(':' + FTT.userName + ':') ) {
//			FTT.debug(PRM.origReplyTo + ' doesn\'t want to be pinged');
			if ( FTT.userNemesis.match(':' + FTT.userName + ':') ) {
//				FTT.debug('createPingText: BY YOU.');
			}
			if ( !FTT.userHatesPingCauseISaidSo ) {
				mw.notify(FTT.msgs.nopingNotify,{type:'error'});
			}
			return PRM.origReplyTo + ', '+FTT.quoteSelect;
		} else {
//			FTT.debug('createPingText: this user has enemies, but you aren\'t one of them. adding ping');
			return FTT.createdPingText;
		}
	} else if ( PRM.origReplyTo.replace(/_/g,' ') == FTT.userName ) {
//		FTT.debug('createPingText: you talking to yourself?');
	} else if ( PRM.origReplyTo.replace(/_/g,' ') == M1('wgRelevantUserName') && ! M1('wgRelevantPageName').match('/') ) {
//		FTT.debug('createPingText: not pinging user on their own talk page');
	} else {
		return FTT.createdPingText;
	}
};
FTT.listToRaw = function(text) {
	FTT.textWithListNew = text;
	FTT.textWithList = '';
	FTT.rawListInt = 0;
	while ( FTT.textWithListNew != FTT.textWithList && FTT.rawListInt < 1000 ) {
		FTT.rawListInt++;
		FTT.textWithList = FTT.textWithListNew;
		FTT.textWithListNew = FTT.textWithListNew.replace(/<ol>(((<li>([^<]|<(?!\/li>))*)|\n\# .*)*)<li>(([^<]|<(?!\/li>))*)<\/li>/g,'<ol>$1\n# $5');
	}
	return FTT.textWithListNew
	.replace(/<\/ol>/g, '\n')
	.replace(/<[\/]?[ou]l>/g, '')
	.replace(/<\/li>([^<])/g, '</li>\n$1')
	.replace(/<li>(([^<]|<(?!\/li>))*)<\/li>/g, '\n*$1')
	.replace(/<li style="margin\-left:2em;">(([^<]|<(?!\/li>))*)<\/li>/g, '\n**$1')
	.replace(/<li style="margin\-left:3em;">(([^<]|<(?!\/li>))*)<\/li>/g, '\n***$1');
};
FTT.syncToVisual = function() {
	if ( FTT.syncBackToVisual == false ) {
//		FTT.debug('skip syncing back to visual');
		FTT.syncBackToVisual = true;
	} else if ( FTT.activeEditor == 'visualLight' ) {
//		FTT.debug('sync to visual');
		FTT.visualComment = FTT.processComment(FTT.UITextInput.getValue(), 'toVisual');
		FTT.visualComment = FTT.visualComment.replace(/\*\*([^\*\n]+)\*\*/g, '<b>$1</b>').replace(/(^|[^:"'])\/\/(([^\/]|:\/|\/[^\/])*[^:"'])\/\/($|[^\/])/g, '$1<i>$2</i>$4').replace(/(^|[^'])'''(([^']|'[^']|''[^'])*)'''([^'])/g, '$1<b>$2</b>$4').replace(/(^|[^'])''([^']([^']|'''|[^']'[^'])*)''([^'])/g, '$1<i>$2</i>$4').replace(/\[([A-Za-z0-9]*):\/\/([^ \]]*)\]/g, '<a class="external" href="$1://$2">[1]</a>').replace(/\[([A-Za-z0-9]*):\/\/([^ \]]*) ([^\]]*)\]/g, '<a class="external" href="$1://$2">$3</a>').replace(/\[\[(([^\]\|]|\][^\]])*)\|\]\]/g, '<a class="FTTUnnamedPipeTrickInternalLink" href="/wiki/$1">$1</a>').replace(/\[\[(([^\]\|]|\][^\]])*)\]\]/g, '<a class="FTTUnnamedInternalLink" href="/wiki/$1">$1</a>').replace(/\[\[([^\|]*)\|(([^\]]|\][^\]])*)\]\]/g, '<a class="FTTNamedInternalLink" href="/wiki/$1">$2</a>').replace(/(<a class="FTTUnnamedInternalLink" href="[^"]*">):/, '$1');
		FTT.UIVisual.innerHTML = FTT.visualComment;
	}
};
FTT.syncToSource = function(syncBackToVisual,origin,src) {
	if ( FTT.activeEditor == 'visualLight' ) {
//		if ( origin != 'livePreview' ) {FTT.debug('sync to source');}
		src = FTT.UIVisual.innerHTML.replace(/<i>(([^<]|<(?!\/i>))*)<\/i>/g,'//$1//').replace(/<b>(([^<]|<(?!\/b>))*)<\/b>/g,'**$1**').replace(/<div>(([^<]|<(?!\/div>))*)<\/div>/g, '<br />' + '$1').replace(/(<br( \/)?>)+/g, '\n').replace(/<a class="FTTUnnamedPipeTrickInternalLink" href="\/wiki\/([^"]*)">(([^<]|<(?!\/a>))*)<\/a>/g, '[[$1|]]').replace(/<a class="FTTUnnamedInternalLink" href="\/wiki\/([^"]*)">(([^<]|<(?!\/a>))*)<\/a>/g, '[[$1]]').replace(/<a class="FTTNamedInternalLink" href="\/wiki\/([^"]*)">(([^<]|<(?!\/a>))*)<\/a>/g, '[[$1|$2]]').replace(/<a class="external" href="([^"]*)">[1]<\/a>/g, '[$1]').replace(/<a class="external" href="([^"]*)">(([^<]|<(?!\/a>))*)<\/a>/g, '[$1 $2]');
		if ( syncBackToVisual == false ) {
			FTT.syncBackToVisual = false;
		}
		FTT.UITextInput.setValue(FTT.listToRaw(src).replace(/(\n)+/g, '\n'));
	}
};
FTT.insertMarkup = function(type, content, markUpStart, markUpEnd, focusNodeMP, focusOffsetMP, anchorNodeMP, anchorOffsetMP, mode) {
//	FTT.debug('insertMarkup: type: '+type+', content: '+content+', markUpStart: '+markUpStart+', markUpEnd: '+markUpEnd+', mode: '+mode);
	if ( ! ['link','ping'].includes(type) ) { //getLastActiveField runs when opening the link form, otherwise here
		FTT.activeInput = FTT.getLastActiveField();
	}
	FTT.markUpStart = { 'italic':'//','bold':'**','struck':'<s>','superscript':'<sup>','subscript':'<sub>','code':'<code>','underline':'<u>','small':'<small>','big':'<big>','nowiki':'<'+'nowiki>','h1':'= ','h2':'== ','h3':'=== ','h4':'==== ','h5':'===== ','h6':'====== ','blockquote':'<blockquote>','PingDropDown':content,'cI':markUpStart};
	FTT.markUpEnd = { 'italic':'FTTCRT//','bold':'FTTCRT**','struck':'FTTCRT</s>','superscript':'FTTCRT</sup>','subscript':'FTTCRT</sub>','code':'FTTCRT</code>','underline':'FTTCRT</u>','small':'FTTCRT</small>','big':'FTTCRT</big>','nowiki':'FTTCRT<'+'/nowiki>','h1':'FTTCRT =','h2':'FTTCRT ==','h3':'FTTCRT ===','h4':'FTTCRT ====','h5':'FTTCRT =====','h6':'FTTCRT ======','blockquote':'FTTCRT</blockquote>','PingDropDown':'FTTCRT','cI':'FTTCRT' + markUpEnd};
	FTT.formChanged = true; //if markup has been inserted, don't close without warning
	if ( type == 'link' ) {
		if ( FTT.UIinsertLinkLink.getValue().match(new RegExp('^(File:|'+E1(FTT.NS[6])+'):')) ) {
			FTT.markUpStart.link = '[['+FTT.UIinsertLinkLink.getValue().replace(/_/g,' ')+'|thumb|'+FTT.UIinsertLinkName.getValue()+']]';
			FTT.markUpEnd.link = 'FTTCRT';
		} else if ( FTT.UIinsertLinkLink.getValue().match(/^(www\.|[A-Za-z0-9]+:\/\/)/) ) {
//			FTT.debug('looks like an external link you\'re trying to insert');
			if ( FTT.UIinsertLinkLink.getValue().match(/^www\./) ) {
				FTT.linkToInsert = 'http://' + FTT.UIinsertLinkLink.getValue();
			} else {
				FTT.linkToInsert = FTT.UIinsertLinkLink.getValue();
			}
			FTT.useLinkName = false;
			if ( FTT.UIinsertLinkName.getValue() != '' ) {
				FTT.useLinkName = ' ';
			}
			FTT.markUpStart.link = '[' + FTT.linkToInsert + (FTT.useLinkName || '');
			FTT.markUpEnd.link = FTT.UIinsertLinkName.getValue() + ']FTTCRT';
			FTT.linkToInsertInternalCheck = FTT.rewritunUrUrlz(FTT.linkToInsert,'insertlink',FTT.UIinsertLinkName.getValue());
			if ( FTT.linkToInsertInternalCheck != FTT.linkToInsert ) {
				FTT.markUpStart.link = FTT.linkToInsertInternalCheck.replace(/(.*[^\]])([\]]+)$/,'$1');
				FTT.markUpEnd.link = FTT.linkToInsertInternalCheck.replace(/(.*[^\]])([\]]+)$/,'$2') + 'FTTCRT';
			} else {
				FTT.insertLinkStart = 'FTTINSERTEDLINKLINK ' + FTT.linkToInsert;
				FTT.insertLinkEnd = ' FTTINSERTEDLINKNAMESTART-' + FTT.UIinsertLinkName.getValue() + '-FTTINSERTEDLINKNAMEEND';
			}
		} else {
//			FTT.debug('I\'m guessing article/template title/ping.');
			FTT.pipedLink = '';
			FTT.linkOpenBrackets = '[[';
			FTT.linkCloseBrackets = ']]';
			FTT.linkValue = FTT.UIinsertLinkLink.getValue();
			if ( FTT.checkLinkInsertingTemplate ) {
				FTT.checkForTemplateNSRegExp = new RegExp('^' + E1(FTT.NS[10]) + ':');
				FTT.linkOpenBrackets = '{{';
				if ( FTT.linkValue.match(FTT.checkForTemplateNSRegExp) ) {
					FTT.linkValue = FTT.linkValue.replace(FTT.checkForTemplateNSRegExp,'');
				}
				FTT.linkCloseBrackets = '}}';
				if ( FTT.UIinsertLinkName.getValue() != '' ) {
					FTT.pipedLink = '|';
				}
			} else if ( FTT.linkValue.match(/:/) || FTT.UIinsertLinkName.getValue() != '' ) {
				FTT.pipedLink = '|';
				FTT.linkOpenBrackets = '[[';
				FTT.linkCloseBrackets = ']]';
			}
			FTT.markUpStart.link = FTT.linkOpenBrackets + FTT.linkValue + FTT.pipedLink;
			FTT.markUpEnd.link = FTT.UIinsertLinkName.getValue() + FTT.linkCloseBrackets + 'FTTCRT';
			if ( mode == 'ping' ) {
				FTT.markUpStart.link = FTT.settings.pingText.replace(/PINGUSER/g,FTT.escapeReplacement(FTT.linkValue));
				FTT.markUpEnd.link = '';
			}
		}
	}
	if ( mode == 'ping' && FTT.UITextInputSummaryMinorLayout.isVisible() && FTT.activeInput == 'UITextInputSummary' ) { //only basic wikitext supported, no templates
		FTT.UITextInputSummary.encapsulateContent('[['+FTT.NS[2]+':'+FTT.linkValue+'|'+FTT.linkValue+']]','');
		FTT.undoSave();
		return;
	} else if ( FTT.activeInput == 'UITextInputTitle' ) {
		if ( FTT.pipedLink == '' && FTT.UITextInputTitle.getRange().to != FTT.UITextInputTitle.getRange().from ) { //some text has been selected but we are about to insert an unpiped link
			FTT.markUpStart[type] = FTT.markUpStart[type]+'|';
		}
		FTT.UITextInputTitle.encapsulateContent(FTT.markUpStart[type],FTT.markUpEnd[type].replace(/FTTCRT/,''));
		FTT.undoSave();
		return;
	} else if ( FTT.activeInput == 'UITextInputSummary' ) {
		if ( FTT.pipedLink == '' && FTT.UITextInputSummary.getRange().to != FTT.UITextInputSummary.getRange().from ) { //some text has been selected but we are about to insert an unpiped link
			FTT.markUpStart[type] = FTT.markUpStart[type]+'|';
		}
		FTT.UITextInputSummary.encapsulateContent(FTT.markUpStart[type],FTT.markUpEnd[type].replace(/FTTCRT/,''));
		FTT.undoSave();
		return;
	}
	if ( FTT.activeEditor == 'visualLight' ) {
		if ( focusNodeMP != anchorNodeMP ) {
			focusNodeMP.textContent = focusNodeMP.textContent.slice(0,focusOffsetMP) + 'FTTINSERTMARKUP' + focusNodeMP.textContent.slice(focusOffsetMP);
			anchorNodeMP.textContent = anchorNodeMP.textContent.slice(0,anchorOffsetMP) + 'FTTINSERTMARKUP' + anchorNodeMP.textContent.slice(anchorOffsetMP);
		} else {
			if ( focusOffsetMP == anchorOffsetMP ) {
				focusNodeMP.textContent = focusNodeMP.textContent.slice(0,focusOffsetMP) + 'FTTINSERTMARKUPFTTINSERTMARKUP' + focusNodeMP.textContent.slice(focusOffsetMP);
			} else if ( focusOffsetMP > anchorOffsetMP ) {
				focusNodeMP.textContent = focusNodeMP.textContent.slice(0,anchorOffsetMP) + 'FTTINSERTMARKUP' + focusNodeMP.textContent.slice(anchorOffsetMP,focusOffsetMP) + 'FTTINSERTMARKUP' + focusNodeMP.textContent.slice(focusOffsetMP);
			} else {
				focusNodeMP.textContent = focusNodeMP.textContent.slice(0,focusOffsetMP) + 'FTTINSERTMARKUP' + focusNodeMP.textContent.slice(focusOffsetMP,anchorOffsetMP) + 'FTTINSERTMARKUP' + focusNodeMP.textContent.slice(anchorOffsetMP);
			}
		}
		FTT.syncToSource(); //in visual we insert those generic "FTTINSERTMARKUP" markers, then we sync to source. These markers allow us to find the corresponding offset in source for the anchorNodeMP and focusNodeMP. The generic markers are replaced with the desired content IN SOURCE, the source change triggers a sync to visual.
		FTT.MarkUpStartOffset = FTT.UITextInput.getValue().split('FTTINSERTMARKUP')[0].length;
		if ( FTT.UITextInput.getValue().split('FTTINSERTMARKUP')[1] ) {
			FTT.MarkUpEndOffset = FTT.UITextInput.getValue().split('FTTINSERTMARKUP')[0].length + FTT.UITextInput.getValue().split('FTTINSERTMARKUP')[1].length;
		} else {
			FTT.MarkUpEndOffset = FTT.MarkUpStartOffset;
		}
		FTT.UITextInput.setValue(FTT.UITextInput.getValue().replace(/FTTINSERTMARKUP/g,'').replace(/FTTINSERTMARKUP/g,''));
		FTT.UITextInput.selectRange(FTT.MarkUpStartOffset,FTT.MarkUpEndOffset);
	}
	if ( type == 'link' && FTT.pipedLink == '' && FTT.getRange().to != FTT.getRange().from ) {
		FTT.markUpStart.link = FTT.markUpStart.link+'|';
	}
	FTT.encapMsgTry = 0;
	if ( FTT.getRange().to == FTT.getRange().from ) {
		FTT.encapMsgTry = 1;
	}
	FTT.encapsulateContent(FTT.markUpStart[type], FTT.markUpEnd[type]);
	FTT.encapMsg = FTT.B1['wikieditor-toolbar-tool-'+type+'-example'];
	if (['struck','code','underline'].includes(type)) {
		FTT.encapMsg = FTT.msgs[type];
	}
	if ( FTT.encapMsg && FTT.encapMsgTry ) {
		FTT.encapsulateContent(FTT.encapMsg,'');
		var DelayedEncapMsg = setInterval(function () {
			clearInterval(DelayedEncapMsg);
//				FTT.debug('select range '+FTT.getRange().to+' to '+(FTT.getRange().to-FTT.encapMsg.length));
				FTT.selectRange(FTT.getRange().to,FTT.getRange().to-FTT.encapMsg.length);
		},50);
	}
	if ( FTT.activeEditor == 'visualLight' ) {
		FTT.UIVisualAllElements = $('#FTTVisualLight *,#FTTVisualLight');
		FTT.UIVisualFoundCaret = false;
		for (FTT.UIVisualAllElementsInt=0;FTT.UIVisualAllElementsInt<FTT.UIVisualAllElements.length;FTT.UIVisualAllElementsInt++){
			for (FTT.UIVisualAllElementsChildrenInt=0;FTT.UIVisualAllElementsChildrenInt<FTT.UIVisualAllElements[FTT.UIVisualAllElementsInt].childNodes.length;FTT.UIVisualAllElementsChildrenInt++){
				if ( FTT.UIVisualAllElements[FTT.UIVisualAllElementsInt].childNodes[FTT.UIVisualAllElementsChildrenInt].nodeName == '#text' && FTT.UIVisualAllElements[FTT.UIVisualAllElementsInt].childNodes[FTT.UIVisualAllElementsChildrenInt].textContent.match('FTTCRT') ) {
//					FTT.debug('found caret textnode');
					FTT.UIVisualFoundCaret = true;
					FTT.UIVisualCaretNode = FTT.UIVisualAllElements[FTT.UIVisualAllElementsInt].childNodes[FTT.UIVisualAllElementsChildrenInt];
					FTT.UIVisualCaretOffset = FTT.UIVisualCaretNode.textContent.split('FTTCRT')[0].length;
					FTT.UIVisualCaretNode.textContent = FTT.UIVisualCaretNode.textContent.replace('FTTCRT','');
					break;
				}
			}
			if ( FTT.UIVisualFoundCaret ) {
//				FTT.debug('found caret element');
				FTT.UIVisualFoundCaret = true;
				break;
			}
		}
//		FTT.debug('focus visualLight input');
		document.getSelection().collapse(FTT.UIVisualCaretNode,FTT.UIVisualCaretOffset);
	} else if ( ['source','2010wikitext'].includes(FTT.activeEditor) ) {
		FTT.sourceFocusOffset = FTT.getValue().split('FTTCRT')[0].length;
		FTT.setValue(FTT.getValue().replace(/FTTCRT/,''));
		FTT.selectRange(FTT.sourceFocusOffset);
	}
	FTT.undoSave();
};
FTT.linkFieldChange = function(mode) {
	var DelayedSkipLink = setInterval(function () {//skipFieldChange doesn't work without a delay because the field change happens earlier than the choose event
		clearInterval(DelayedSkipLink);
		if ( FTT.skipFieldChange ) { //when a user is picked from the dropdown when pinging, don't bother looking this up
			delete FTT.skipFieldChange;
			return;
		}
		if ( mode == 'link' && FTT.B1.citemap != '' && FTT.UIinsertLinkLink.getValue().match(/:\/\//) ) {
			FTT.UIinsertLinkWeb2Cit.toggle(true);
			$('#Web2CitLink')[0].href = 'https://web2cit.toolforge.org/' + D1(FTT.UIinsertLinkLink.getValue());
		} else if ( FTT.UIinsertLinkWeb2Cit ) {
			FTT.UIinsertLinkWeb2Cit.toggle(false);
		}
//		FTT.debug('linkFieldChange: get page title suggestions');
		if ( mode == 'link' && [']]','}}'].includes(FTT.UIinsertLinkLink.getValue().slice(-2)) ) {
			FTT.submitInsertedLink('linkField');
			return;
		}
		if ( mode == 'contentmove' ) {
			if ( FTT.oTT.moveContentToPage.getValue() != '' ) {
				FTT.oTT.allowPageCreation.setDisabled(false);
			} else {
				FTT.oTT.allowPageCreation.setDisabled(true);
				FTT.oTT.allowPageCreation.setSelected(false);
			}
			if ( FTT.oTT.allowPageCreation.isSelected() ) {
				FTT.oTT.moveContentToPage.setOptions([]);
				return;
			}
			FTT.linkFieldSearchTerm = FTT.oTT.moveContentToPage.getValue();
		} else {
			FTT.linkFieldSearchTerm = FTT.UIinsertLinkLink.getValue();
		}
		if ( mode == 'ping' || mode == 'contentmove' || ( FTT.UIinsertLinkLink && ! FTT.UIinsertLinkLink.getValue().match(/^(.*:\/\/|www\.)/) ) ) { //external links are not article titles so don't bother looking for suggestions
			if ( mode == 'ping' ) {
				if ( FTT.UIinsertLinkLink.getValue().length < 2 ) {
					FTT.UIinsertLinkLink.setOptions(FTT.cITSmenuItems);
					return;
				}
				FTT.titleSuggestParams = {action:'query',format:'json',formatversion: 2,list:'allusers',auprefix:FTT.linkFieldSearchTerm,auwitheditsonly:1,aulimit:18,auprop:'blockinfo'};
			} else {
				FTT.titleSuggestParams = {action:'opensearch',format:'json',formatversion: 2,'search':FTT.linkFieldSearchTerm,namespace:[0,2,3,4,5],limit:6};
			}
			api.get(FTT.titleSuggestParams).then(function(suggestedTitles,userInt,suggestTitle) {
				if ( mode == 'ping' && FTT.UIinsertLinkLink.getValue() == '' ) { //avoid race condition repeatedly pressing backspace until field is empty. User could get to 1 or 2 characters, trigger API call, continue to backspace during API call and the result would overwrite the menu despite the field now being empty.
//					FTT.debug('linkFieldChange: field is empty now, skip menu generation');
					return;
				}
				FTT.suggestedTitles = [];
				FTT.suggestedTitlesArr = [];
				if ( mode == 'ping' ) {
					for ( userInt=0;userInt<suggestedTitles.query.allusers.length;userInt++) {
						suggestTitle = suggestedTitles.query.allusers[userInt];
						if ( ! suggestTitle.blockid ) { //blocked users probably don't want to be pinged, also some vandals create usernames that look similar to existing ones
							FTT.suggestedTitles.push({data:suggestTitle.name});
							FTT.suggestedTitlesArr.push(suggestTitle.name);
						}
					}
				} else {
					for(FTT.suggestedTitlesInt=0;FTT.suggestedTitlesInt<suggestedTitles[1].length;FTT.suggestedTitlesInt++){
						FTT.suggestedTitles.push({data:suggestedTitles[1][FTT.suggestedTitlesInt]});
						FTT.suggestedTitlesArr.push(suggestedTitles[1][FTT.suggestedTitlesInt]);
					}
				}
				if ( mode == 'contentmove' ) {
					FTT.oTT.moveContentToPage.setOptions(FTT.suggestedTitles);
					if ( FTT.oTT.moveContentToPage.getValue() != '' && ! FTT.suggestedTitlesArr.includes(FTT.oTT.moveContentToPage.getValue()) && ! FTT.oTT.allowPageCreation.isSelected() ) {
						FTT.UIReplyButton.setDisabled(true);
					} else {
						FTT.UIReplyButton.setDisabled(false);
					}
				} else {
					FTT.UIinsertLinkLink.setOptions(FTT.suggestedTitles);
				}
			});
			if ( mode == 'contentmove' && FTT.oTT.moveContentToPage.getValue() == '' ) {
				FTT.UIReplyButton.setDisabled(false);
			}
		} else if ( mode == 'link' ) {
			FTT.UIinsertLinkLink.setOptions([]);
		} else if ( FTT.oTT ) {
			FTT.oTT.moveContentToPage.setOptions([]);
		}
	},10);
};
FTT.canonicalToLocal = function(text,nsInt) {
	FTT.canonNS = {'0':'','1':'Talk','2':'User','3':'User talk','4':'Project','5':'Project talk','6':'File','7':'File talk','8':'MediaWiki','9':'MediaWiki talk','10':'Template','11':'Template talk','12':'Help','13':'Help talk','14':'Category','15':'Category talk','100':'Portal','101':'Portal talk','118':'Draft','119':'Draft talk','710':'TimedText','711':'TimedText talk','828':'Module','829':'Module talk','2300':'Gadget','2301':'Gadget talk','2302':'Gadget definition','2303':'Gadget definition talk','-2':'Media','-1':'Special'};
	for (nsInt=0;nsInt<Object.keys(FTT.NS).length;nsInt++) {
		text = text.replace(new RegExp('^'+FTT.canonNS[Object.keys(FTT.NS)[nsInt]]+':'),FTT.escapeReplacement(FTT.NS[Object.keys(FTT.NS)[nsInt]])+':');
	}
	return text;
};
FTT.getLastActiveField = function(range,c) {
	range = FTT.getRange();
	FTT.activeElLast = '';
	if ( document.activeElement.parentElement && document.activeElement.parentElement.id ) {
		FTT.activeElLast = document.activeElement.parentElement.id;
	}
	if ( ['FTTUITextInputTitle','FTTUITextInputSummary'].includes(FTT.activeElLast) ) {
		return FTT.activeElLast.slice(3);
	}
	if ( FTT.activeElLast == 'FTTUITextInput' ) {
		return 'main';
	}
	c = document.activeElement.classList;
	if ( c.contains('CodeMirror-code') || c.contains('ace_text-input') ) {
		return 'main';
	}
	$('#FTTUICancelButton').addClass('FTTfixed');//prevent unwanted scrolling
	FTT.UICancelButton.focus(); //force title/main body/summary to get blurred if currently in focus, only needed when transferring to link form from entering brackets or @
	if ( (FTT.lastBlurTitle || 0)+200 > new Date().getTime() ) {
		FTT.UITextInputTitle.focus();
		$('#FTTUICancelButton').removeClass('FTTfixed');
		return 'UITextInputTitle';
	} else if ( (FTT.lastBlurSummary || 0)+200 > new Date().getTime() ) {
		FTT.UITextInputSummary.focus();
		$('#FTTUICancelButton').removeClass('FTTfixed');
		return 'UITextInputSummary';
	} else {
		FTT.UICancelButton.$button.blur();
		$('#FTTUICancelButton').removeClass('FTTfixed');
		FTT.focusInput();
		FTT.selectRange(range.from,range.to);
		return 'main';
	}
};
FTT.insertLink = function(focusNode,focusOffset,anchorNode,anchorOffset,mode) {
	FTT.activeInput = FTT.getLastActiveField();
	FTT.selectedLink = FTT[FTT.activeInput].getValue().slice(FTT[FTT.activeInput].getRange().from,FTT[FTT.activeInput].getRange().to);
	if ( FTT.selectedLink.match(/\n/) ) {
//		FTT.debug('insertLink: multiline selection for a link? NOPE NOPE NOPE');
		FTT[FTT.activeInput].selectRange(FTT[FTT.activeInput].getRange().to);
	} else if ( FTT.selectedLink.match(/^[A-Za-z0-9]{2,14}:\/\//) ) {
//		FTT.debug('insertLink: found protocol URL in selected text, adding single brackets');
		FTT.insertMarkup('cI','','[',']',focusNode,focusOffset,anchorNode,anchorOffset,mode);
		return;
	} else if ( FTT.selectedLink.match(new RegExp('^('+E1(FTT.NS[6])+'|'+E1(FTT.NS[14])+'|[Ff]ile|[Cc]ategory):')) ) {
//		FTT.debug('insertLink: found category: or file: link, adding double brackets and colon');
		FTT.insertMarkup('cI','','[[:',']]',focusNode,focusOffset,anchorNode,anchorOffset,mode);
		return;
	} else if ( FTT.selectedLink != '' ) {
//		FTT.debug('insertLink: found selected text (not file: or category: or protocol URL), adding double brackets');
		FTT.insertMarkup('cI','','[[',']]',focusNode,focusOffset,anchorNode,anchorOffset,mode);
		return;
	}
	FTT.checkLinkInsertingTemplate = false;
	FTT.insLinkPlaceholder = FTT.msgs.insertLinkLink;
	if ( mode == FTT.lastLinkFormType && FTT.UIinsertLinkForm && FTT.UIinsertLinkForm.isVisible() ) {
		FTT.UIinsertLinkForm.toggle(false);
		return;
	}
	if ( mode == 'ping' || FTT.lastLinkFormType == 'ping' ) { // don't toggle link insertion form for ping insertion, destroy existing ping insertion form
//		FTT.debug('ping mode or last link form was opened by ping, removing form first');
		delete FTT.UIinsertLinkForm;
		delete FTT.UIinsertLinkLink;
		delete FTT.UIinsertLinkName;
		$('#UIinsertLinkForm').remove();
		if ( mode == 'ping' ) {
			FTT.insLinkPlaceholder = FTT.B1['userlogin-yourname'];
		}
	}
	FTT.lastLinkFormType = mode;
	if ( $('#UIinsertLinkForm')[0] ) {
//		FTT.debug('link form was opened previously, empty fields and make it visible again');
		FTT.UIinsertLinkLink.setValue('');
		FTT.UIinsertLinkName.setValue('');
		FTT.UIinsertLinkForm.toggle(true);
		FTT.scrollAndCall($('#UIinsertLinkForm')[0],function(){FTT.UIinsertLinkLink.focus();});
	} else {
		FTT.UIinsertLinkLink = new OO.ui.ComboBoxInputWidget( {
			id: 'FTTUIinsertLinkLink',
			classes: [ 'FTTMarginHalfEm',FTT.FTTUITextInputTitleClass ],
			placeholder: FTT.insLinkPlaceholder,
			title: FTT.B1['wikieditor-toolbar-tool-link'],
			options: [],//{data:'Option 1',label: 'Option One'}
			spellcheck: false,
			autocomplete:false,
			menu: {filterFromInput: true}
		} );
		FTT.UIinsertLinkName = new OO.ui.TextInputWidget( {
			classes: [ 'FTTMarginHalfEm',FTT.FTTUITextInputTitleClass ],
			title:FTT.B1['wikieditor-toolbar-tool-ilink-example'],
			placeholder: FTT.msgs.insertLinkName,
		} );
		FTT.UIinsertCatLinkButton = new OO.ui.ButtonWidget( {
			label: FTT.B1['wikieditor-toolbar-tool-link-insert'],
			flags: [ 'primary', 'progressive' ],
			classes: [ FTT.buttonOnTheWrongSideClass ],
		} );
		FTT.UIinsertCatLinkButton.toggle(false);
		FTT.UIinsertCatLinkButton.on('click', function() { FTT.submitInsertedCatLink(); });
		FTT.submitInsertedCatLink = function() {
			FTT.UIinsertLinkLink.setValue(':'+FTT.UIinsertLinkLink.getValue());
			FTT.insertMarkup('link','','','',focusNode,focusOffset,anchorNode,anchorOffset,'catlink');
			FTT.UIinsertLinkForm.toggle(false);
		};
		FTT.UIinsertFileLinkButton = new OO.ui.ButtonWidget( {
			label: FTT.B1['wikieditor-toolbar-tool-link-insert'],
			flags: [ 'primary', 'progressive' ],
			classes: [ FTT.buttonOnTheWrongSideClass ],
		} );
		FTT.UIinsertFileLinkButton.toggle(false);
		FTT.UIinsertFileLinkButton.on('click', function() {
			FTT.UIinsertLinkLink.setValue(':'+FTT.UIinsertLinkLink.getValue());
			FTT.insertMarkup('link','','','',focusNode,focusOffset,anchorNode,anchorOffset,'catlink');
			FTT.UIinsertLinkForm.toggle(false); 
		});
		FTT.UIinsertLinkButton = new OO.ui.ButtonWidget( {
			label: FTT.B1['wikieditor-toolbar-tool-link-insert'],
			flags: [ 'primary', 'progressive' ],
			classes: [ FTT.buttonOnTheWrongSideClass ],
			disabled:true
		} );
		FTT.submitInsertedLink = function(origin,mode) {
			if ( origin == 'linkField' && FTT.UIinsertLinkLink.getValue().slice(-2) == '}}' ) {
				FTT.checkLinkInsertingTemplate = true;
			}
			if ( origin == 'linkField' ) {
				FTT.UIinsertLinkLink.setValue(FTT.UIinsertLinkLink.getValue().slice(0,FTT.UIinsertLinkLink.getValue().length -2));
			}
			FTT.insertMarkup('link','','','',focusNode,focusOffset,anchorNode,anchorOffset,mode);
			FTT.UIinsertLinkForm.toggle(false);
		};
		FTT.UIinsertLinkButton.on('click', function() { FTT.submitInsertedLink('button',mode); });
		FTT.UIinsertRefButton = new OO.ui.ButtonWidget( {
			id: 'FTTUIInsertRefButton',
			label: FTT.B1['wikieditor-toolbar-tool-reference-title'],
			flags: [ 'primary', 'progressive' ],
			classes: [ FTT.buttonOnTheWrongSideClass ],
			disabled:true
		} );
		if ( FTT.B1.citemap == '' || mode == 'ping' ) {
//			FTT.debug('mention menu or no citemap available for your wiki!! If no citemap, GO CREATE IT!! https://www.wikidata.org/wiki/Q112131235 en:User:Alexis_Jazz/Factotum/MakeCitemap.js');
			FTT.UIinsertRefButton.toggle(false);
		}
		if ( mode == 'ping' ) {
			FTT.UIinsertLinkName.toggle(false);
			FTT.UIinsertLinkButton.setLabel(FTT.B1['echo-displaysnippet-title']);
		}
		FTT.UIinsertRefButton.on('click', function() {FTT.insertRef();});
		FTT.insertRef = function() {
			FTT.UIinsertRefButton.setDisabled(true);
			$('#FTTUIInsertRefButton').addClass('FTTPendingBlink');
			FTT.templateMap = FTT.testValidJSON(FTT.getItemLS('FTTCitemap'));
			if ( typeof FTT.templateMap != 'object' ) {
//				FTT.debug('insertRef: loading template/parameter map for cites');
				$.getJSON( mw.util.wikiScript('api'),{format:'json',action:'query',titles:FTT.B1.citemap,prop:'revisions',rvprop:'content'} ).done( function ( data ) {
					FTT.citemapString = data.query.pages[Object.keys(data.query.pages)[0]].revisions[0]['*'];
					FTT.setItemLS('FTTCitemap',FTT.pack(FTT.citemapString,'UTF16'));
					FTT.templateMap = JSON.parse(FTT.citemapString);
//					FTT.debug('insertRef: template/parameter map for cites loaded');
					FTT.insertRef();
				});
				return;
			}
//			FTT.debug('insertRef: get stuff from web2cit for reference..');
			FTT.web2CitUrl = 'https://web2cit.toolforge.org/translate/';
			FTT.web2CitData = {'url':FTT.UIinsertLinkLink.getValue(),'format':'mediawiki','citoid':'false'};
			FTT.getRefData = function(params,callback,handleError,doCall) {
				callback = function (jqXHR,textStatus,errorThrown,TDInt,TDInt2,TDInt3,RefInt4) {
//					FTT.debug('insertRef: got the requested stuff from web2cit');
//					FTT.debug(jqXHR);
					FTT.refDataObj = jqXHR[0];
					FTT.linkCiteTemplateType = FTT.templateMap.templateTypeMap[FTT.refDataObj.itemType];
					FTT.linkCiteTemplateParamsMap = FTT.templateMap[FTT.templateMap.templateParamMap[FTT.linkCiteTemplateType]];
					FTT.refDataString = FTT.linkCiteTemplateType;
					for ( TDInt=0;TDInt<Object.keys(FTT.linkCiteTemplateParamsMap).length;TDInt++) {
						FTT.TDKey = Object.keys(FTT.linkCiteTemplateParamsMap)[TDInt];
						FTT.TDVal = Object.values(FTT.linkCiteTemplateParamsMap)[TDInt];
						if ( typeof FTT.TDVal == 'string' && typeof FTT.refDataObj[FTT.TDKey] == 'string' ) {
							FTT.refDataString = FTT.refDataString + '|' + FTT.TDVal + '='+FTT.refDataObj[FTT.TDKey].toString().replace(/\|/g,'{{!}}');
						} else if ( typeof FTT.TDVal == 'string' && typeof FTT.refDataObj[FTT.TDKey] == 'object' ) {
//							FTT.debug('string in templatedata but reference returned an array. fuck this shit');
							FTT.refDataString = FTT.refDataString + '|' + FTT.TDVal + '=';
							for (RefInt4=0;RefInt4<FTT.refDataObj[FTT.TDKey].length;RefInt4++) {
								FTT.refDataString = FTT.refDataString + FTT.refDataObj[FTT.TDKey][RefInt4].toString().replace(/\,/g,' ') + ', ';
							}
							FTT.refDataString = FTT.refDataString.replace(/, $/,''); //remove trailing comma
						} else if ( typeof FTT.TDVal == 'object' ) {
//							FTT.debug('insertRef: array in templatedata :-(');
							for (TDInt2=0;TDInt2<FTT.TDVal.length;TDInt2++) {
								if ( typeof FTT.TDVal[TDInt2] == 'string' && typeof FTT.refDataObj[FTT.TDKey] == 'object' && typeof FTT.refDataObj[FTT.TDKey][TDInt2] == 'string' ) {
									FTT.refDataString = FTT.refDataString + '|' + FTT.TDVal[TDInt2] + '='+FTT.refDataObj[FTT.TDKey][TDInt2].toString().replace(/\|/g,'{{!}}');
								} else if ( typeof FTT.TDVal[TDInt2] == 'object' ) {
//									FTT.debug('insertRef: array in array in templatedata :-( :-(');
									for (TDInt3=0;TDInt3<FTT.TDVal[TDInt2].length;TDInt3++) {
										if ( typeof FTT.TDVal[TDInt2][TDInt3] == 'string' && typeof FTT.refDataObj[FTT.TDKey] == 'object' && typeof FTT.refDataObj[FTT.TDKey][TDInt2] == 'object' && typeof FTT.refDataObj[FTT.TDKey][TDInt2][TDInt3] == 'string' ) {
											FTT.refDataString = FTT.refDataString + '|' + FTT.TDVal[TDInt2][TDInt3] + '='+FTT.refDataObj[FTT.TDKey][TDInt2][TDInt3].toString().replace(/\|/g,'{{!}}');
										}
									}
								}
							}
						}
					}
					FTT.refTagName = '';
					if ( FTT.refDataObj.publicationTitle ) {
						FTT.refTagName = FTT.refDataObj.publicationTitle;
					} else if ( FTT.refDataObj.url && FTT.refDataObj.url.match(/:\/\/([^\/]*)\//)){
						FTT.refTagName = FTT.refDataObj.url.match(/:\/\/([^\/]*)\//)[1].replace(/www\./,'');
					}
					if ( FTT.refDataObj.date ) {
						if ( FTT.refTagName != '' ) {
							FTT.refTagName = FTT.refTagName + '_';
						}
						FTT.refTagName = FTT.refTagName + FTT.refDataObj.date;
					}
					if ( FTT.UIinsertLinkName.getValue() != '' ) {
						FTT.refTagName = FTT.UIinsertLinkName.getValue();
					}
					FTT.refString = FTT.wikiMsgs.refOpen.replace(/REFNAME/g,FTT.refTagName) + '{{'+FTT.refDataString+'}}' + FTT.wikiMsgs.refClose;
					FTT.insertMarkup('cI','',FTT.refString,'',focusNode,focusOffset,anchorNode,anchorOffset);
					$('#FTTUIInsertRefButton').removeClass('FTTPendingBlink');
					FTT.UIinsertRefButton.setDisabled(false);
					FTT.UIinsertLinkForm.toggle(false);
				};
				handleError = function ( jqXHR, textStatus, errorThrown ) {
//					FTT.debug('didn\'t get the requested stuff from web2cit');
//					FTT.debug(jqXHR);
//					FTT.debug(textStatus);
//					FTT.debug(errorThrown);
				};
				doCall = function () {
					$.ajax( {	url: FTT.web2CitUrl, dataType: 'json', data: FTT.web2CitData, type: 'GET', success: callback, error: handleError } );
				};
				doCall();
			};
			FTT.getRefData();
		};
		FTT.UIinsertLinkLink.on('enter', function() { FTT.submitInsertedLink('button',mode); });
		FTT.UIinsertLinkName.on('enter', function() { FTT.submitInsertedLink('button',mode); });
		FTT.UIinsertLinkLink.on('change',function() {
			FTT.canonicalLink = FTT.canonicalToLocal(FTT.UIinsertLinkLink.getValue());
			if ( FTT.canonicalLink != FTT.UIinsertLinkLink.getValue() ) {
				FTT.UIinsertLinkLink.setValue(FTT.canonicalLink);
			}
			FTT.linkFieldChange((mode || 'link'));
			if ( FTT.UIinsertLinkLink.getValue() == '' ) {
				FTT.UIinsertLinkButton.setDisabled(true);
				FTT.UIinsertRefButton.setDisabled(true);
			} else if ( FTT.UIinsertLinkLink.getValue().match(/\.pdf$/) || ! FTT.UIinsertLinkLink.getValue().match(/(^www\.|:\/\/[^\/])/) ) {
				FTT.UIinsertLinkButton.setDisabled(false);
				FTT.UIinsertRefButton.setDisabled(true);
			} else {
				FTT.UIinsertLinkButton.setDisabled(false);
				FTT.UIinsertRefButton.setDisabled(false);
			}
			if ( FTT.UIinsertLinkLink.getValue().match(new RegExp('^(category|'+E1(FTT.NS[14])+'):.','i')) ) {
				FTT.UIinsertLinkButton.setLabel(FTT.B1['mw-widgets-categoryselector-add-category-placeholder']);
				FTT.UIinsertCatLinkButton.toggle(true);
				FTT.UIinsertFileLinkButton.toggle(false);
			} else if ( FTT.UIinsertLinkLink.getValue().match(new RegExp('^(File:|'+E1(FTT.NS[6])+'):')) ) {
				FTT.UIinsertLinkButton.setLabel(FTT.B1.listfiles_thumb);
				FTT.UIinsertFileLinkButton.toggle(true);
				FTT.UIinsertCatLinkButton.toggle(false);
			} else {
				FTT.UIinsertLinkButton.setLabel(FTT.B1['wikieditor-toolbar-tool-link-insert']);
				FTT.UIinsertCatLinkButton.toggle(false);
				FTT.UIinsertFileLinkButton.toggle(false);
			}
		});
		FTT.UIinsertLinkCancel = new OO.ui.ButtonWidget( {
			label: FTT.B1.cancel,
			flags: [ FTT.cancelFlag ],
			classes: [ FTT.buttonOnTheWrongSideClass ]
		} );
		FTT.UIinsertLinkCancel.$button.attr('aria-label',FTT.msgs.cancelLinkForm);
		FTT.UIinsertLinkCancel.on('click', function() { FTT.UIinsertLinkForm.toggle(false); });
		FTT.UIinsertLinkWeb2Cit = new OO.ui.LabelWidget( {
			label: new OO.ui.HtmlSnippet('<a id="Web2CitLink" target="_new" href="https://web2cit.toolforge.org/">Web2Cit</a>'),
		} );
		FTT.UIinsertLinkWeb2Cit.toggle(false);
		FTT.UIinsertLinkButtonBar = new OO.ui.HorizontalLayout( {
			items: [
				FTT.UIinsertLinkButton,
				FTT.UIinsertCatLinkButton,
				FTT.UIinsertFileLinkButton,
				FTT.UIinsertRefButton,
				FTT.UIinsertLinkCancel,
				FTT.UIinsertLinkWeb2Cit
			],
		} );
		FTT.UIinsertLinkForm = new OO.ui.FormLayout( {
			items: [
				FTT.UIinsertLinkLink,
				FTT.UIinsertLinkName,
				FTT.UIinsertLinkButtonBar
			],
			id: 'UIinsertLinkForm',
			classes: ['FTTInsertLinkForm'],
		} );
		$('#FTTMainButtonBar').prepend(FTT.UIinsertLinkForm.$element);
		$('#UIinsertLinkForm .oo-ui-comboBoxInputWidget-dropdownButton').remove();
		$('#UIinsertLinkForm .oo-ui-comboBoxInputWidget').removeClass('oo-ui-comboBoxInputWidget');
		FTT.UIinsertLinkButton.scrollElementIntoView();
		FTT.UIinsertLinkLink.focus();
		if ( mode == 'ping' ) {
			FTT.linkFieldChange(mode);
			$('#FTTUIinsertLinkLink div[role=listbox]').removeClass('oo-ui-element-hidden');
			FTT.UIinsertLinkLink.getMenu().on( 'choose', function ( menuOption ) {
//				FTT.debug( menuOption.getData() );
				FTT.skipFieldChange = true;
				FTT.insertMarkup('link','',menuOption.getData(),'',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset,'ping');
				delete FTT.UIinsertLinkForm;
				$('#UIinsertLinkForm').remove();
			});
		}
	}
	if ( FTT.getValue().slice(FTT.getRange().to -2,FTT.getRange().to) == '{{' ) {
		FTT.checkLinkInsertingTemplate = true;
	}
	if ( focusNode == 'linkSwitch' ) {
		if ( mode == 'main' ) { //main text input, templates also valid (unlike title and summary)
			FTT.fixField = 'UITextInput';
		} else if ( mode == 'title' ) {
			FTT.fixField = 'UITextInputTitle';
		} else if ( mode == 'summary' ) {
			FTT.fixField = 'UITextInputSummary';
		}
		FTT.linkSwitchRange = FTT[FTT.fixField].getRange().to;
		FTT[FTT.fixField].setValue(FTT[FTT.fixField].getValue().slice(0,FTT[FTT.fixField].getRange().to -2) + FTT[FTT.fixField].getValue().slice(FTT[FTT.fixField].getRange().to)); //remove the brackets you just entered so we don't get too many
		FTT[FTT.fixField].selectRange(FTT.linkSwitchRange -2);
		FTT.UIinsertLinkLink.focus();
	}
	if ( FTT.checkLinkInsertingTemplate ) {
		FTT.UIinsertLinkLink.setValue(FTT.NS[10] + ':'); //preload Template: into link field
	}
	delete FTT.lastPressedKey;
};
FTT.initVisualLight = function() {
	FTT.UIVisual = document.createElement('div');
	FTT.UIVisual.contentEditable = true;
	FTT.UIVisual.className = 'FTTVisualLight';
	FTT.UIVisual.id = 'FTTVisualLight';
	FTT.UITextInput.on('change', function() { FTT.syncToVisual(); });
	$('body').bind("mouseup keyup touchend", function() {
		FTT.focusNode = window.getSelection().focusNode;
		FTT.anchorNode = window.getSelection().anchorNode;
		FTT.caretIndex = window.getSelection();
		FTT.anchorOffset = window.getSelection().anchorOffset;
		FTT.focusOffset = window.getSelection().focusOffset;
		FTT.selectionType = window.getSelection().type;
	});
	if ( FTT.settings.pingDropDownAt ) {
		FTT.UIVisual.onkeydown = function(event){
			if ( event.originalEvent && event.originalEvent.key == '@' ) {
				FTT.showPingDropDown('VisualLight');
			}
		};
	}
	if ( ['source','2010wikitext'].includes(FTT.activeEditor) ) {//user enabled editorSwitch but has source or 2010wikitext default
//		FTT.debug('initialized visualLight, hiding it as your activeEditor is source or 2010wikitext');
		FTT.UIVisual.classList.add('FTTNoDisplay');
	}
};
if ( FTT.settings.quoteSelect ) {
	$('#mw-content-text').bind("mouseup keyup touchend", function() {
		if ( ! ['TEXTAREA','INPUT'].includes(document.activeElement.nodeName) ) {
			$(document).ready(function() {
				if ( window.getSelection().toString() != '' ) {
					FTT.selectedText = window.getSelection().toString();
//					FTT.debug('selected text: '+FTT.selectedText);
				} else {
					var DelayedOpenFormCheck = setInterval(function () {
						clearInterval(DelayedOpenFormCheck);
						if ( ! FTT.openingFormInProgress || (FTT.openingFormInProgress+100) < new Date().getTime() ) { //no form was opened, or a form was opened more than 100ms ago
//							FTT.debug('selected text emptied');
							FTT.selectedText = '';
						}
					},50);
				}
			});
		}
	});
}
FTT.lastUsed = function() {
	if ( FTT.settings.editor == 'lastused' && ['comment','edit','newsection','newheading'].includes(FTT.PRMOpened.type) ) {
		FTT.setItemLS('FTTLastCmtEditor',FTT.activeEditor);
	}
};
FTT.loadWikiEditor = function(a,b){
	if ( ! $('#FTTUITextInput .wikiEditor-ui')[0] ) {
//		FTT.debug('loadWikiEditor: loading 2010 wikitext editor');
		if ( M1('wgPageContentModel') == 'wikitext' && ! FTT.PRMOpened.highlightRef && ( FTT.settings['2010codeMirror2023'] || ( FTT.settings['2010templateDefault'] && M1('wgCanonicalNamespace') == 'Template' ) ) ) {
//			FTT.debug('loadWikiEditor: set usecodemirror to 1');
			mw.user.options.set('usecodemirror',1);
		} else {
//			FTT.debug('loadWikiEditor: set usecodemirror to 0');
			mw.user.options.set('usecodemirror',0);
		}
		mw.loader.using(['ext.wikiEditor']).then(function() {
			mw.addWikiEditor(FTT.UITextInput.$input);
			FTT.toggleEd2010Label();
			FTT.activeEditor = '2010wikitext';
			FTT.lastUsed();
			if ( a == 'ace' && ! $('#editform textarea#wpTextbox1')[0] ) {
				FTT.aceUpMySleeve('loadWikiEditor');
			}
			if ( mw.user.options.get('usecodemirror') ) {
				FTT.aceMirrorEnable();
			}
			var DelayedCodeMirrorEnable = setInterval(function () {
				if ( b > 10 ) {
					clearInterval(DelayedCodeMirrorEnable);
				}
				b = b++;
				if ( $('.CodeMirror-line').length == 0 && $('#FTTReplyForm #mw-editbutton-codemirror .oo-ui-image-progressive')[0] ) {
					clearInterval(DelayedCodeMirrorEnable);
//					FTT.debug('loadWikiEditor: CodeMirror thinks it\'s enabled, but it\'s really not');
					$('#FTTReplyForm #mw-editbutton-codemirror a')[0].click(); //click to bring the button in sync with the disabled state
					if ( ! FTT.PRMOpened.highlightRef ) {
						$('#FTTReplyForm #mw-editbutton-codemirror a')[0].click(); //click to actually enable CM
					}
				}
			},100);
			if ( $('#FTTReplyForm #mw-editbutton-codemirror .oo-ui-image-progressive')[0] ) {
				FTT.codeMirrorText = '';
				for(FTT.syncCodeMirrorInt=0;FTT.syncCodeMirrorInt<$('#FTTReplyForm .CodeMirror-line').length;FTT.syncCodeMirrorInt++){
					FTT.codeMirrorText = FTT.codeMirrorText + $('#FTTReplyForm .CodeMirror-line')[FTT.syncCodeMirrorInt].innerText + '\n';
				}
				FTT.setValue(FTT.codeMirrorText);
			}
		});
	}
};
FTT.aceMirrorEnable = function(){
	$(document).ready(function() {
		mw.user.options.set('usecodeeditor',1);
//		FTT.debug('aceMirrorEnable: unhide 2010 wikitext editor toolbar (if hidden) and enable CodeEditor/CodeMirror');
		$('#FTTReplyForm .wikiEditor-ui-top').removeClass('FTTNoDisplay');
		if ( $('#FTTReplyForm .group-codeeditor-main a')[0] ) {
//			FTT.debug('aceMirrorEnable: click CodeEditor button to enable it');
			$('#FTTReplyForm .group-codeeditor-main a')[0].click();
		} else if ( M1('wgPageContentModel') == 'wikitext' ) {
//			FTT.debug('aceMirrorEnable: clicking CodeMirror button to enable it');
			//$('#FTTReplyForm #mw-editbutton-codemirror a')[0].click();
			//thanks to @SerDIDG for this other method to enable CodeMirror (https://phabricator.wikimedia.org/T214989#7287480)
			mw.loader.using( [ 'ext.CodeMirror', 'ext.CodeMirror.lib', 'ext.CodeMirror.mode.mediawiki' ] ).done( function () {
				FTT.CodeMirrorInstance = CodeMirror.fromTextArea( $('#FTTTextarea, #wpTextbox1').get( 0 ), {
					mwConfig: mw.config.get( 'extCodeMirrorConfig' ),
					lineNumbers: true,
					mode: 'text/mediawiki'
				} );
			} );
		}
	});
};
FTT.getCurrentEditor = function() {
	if ( $('#FTTReplyForm .group-codeeditor-main a .oo-ui-image-progressive')[0] && $('.codeEditor-status .codeEditor-status-line')[0] ) {
		return 'CodeEditor';
	} else if ( $('#FTTTextAndPreview .CodeMirror')[0] ) { //2023-11 update
		return 'CodeMirror';
	} else if ( FTT.activeEditor == 'visualLight' ) {
		return 'visualLight';
	} else {
		return 'source';
	}
};
//Todo: The following functions should probably support VisualLight as well
FTT.focusInput = function(start) {
	if ( FTT.activeEditor == '2010wikitext' && $('#FTTUITextInput .CodeMirror-code')[0] ) {
		document.getSelection().collapse($('#FTTUITextInput .CodeMirror-code')[0],0);
	} else if ( FTT.activeEditor == '2010wikitext' && $('#FTTUITextInput .ace_text-input')[0] ) {
		$('#FTTUITextInput .ace_text-input')[0].focus();
	} else if ( FTT.activeEditor == 'visualLight' ) {
		document.getSelection().collapse(FTT.UIVisual,FTT.UIVisual.childNodes.length);
	} else if ( ['source','2010wikitext'].includes(FTT.activeEditor) && start == 'start' ) {
		FTT.UITextInput.moveCursorToStart();
	} else if ( ['source','2010wikitext'].includes(FTT.activeEditor) ) {
		$('#FTTUITextInput textarea')[0].focus(); // is there any particular reason why UITextInput.focus is failing these days? sigh
		FTT.UITextInput.focus();
	}
};
FTT.selectRange = function(from,to,scroll,ed,int) {
//	FTT.debug('selectRange:');
	ed = FTT.getCurrentEditor();
	if ( ed == 'CodeEditor' || ed == 'CodeMirror' ) {
		if ( typeof to == 'undefined' ) {
			to = from;
		}
//		FTT.debug('selectRange, pre: '+from+' post: '+to);
		FTT.textbox = $('#FTTTextarea');
		if ( scroll ) {
			FTT.textbox.textSelection( 'setSelection',{start:from,end:from});
			FTT.textbox.textSelection('scrollToCaretPosition');
			FTT.textbox.textSelection( 'setSelection',{start:to,end:to});
			FTT.textbox.textSelection('scrollToCaretPosition');
		}
		FTT.textbox.textSelection( 'setSelection', { start: from, end: to } );
	} else {
		FTT.UITextInput.selectRange(from, to);
		if ( scroll ) {
			int = 0;
			var ReSelect = setInterval(function () {
//				FTT.debug('selectRange: ReSelect');
				FTT.focusRunning = 1;
				FTT.UITextInput.$input[0].blur(); //Blink-based browsers are difficult
				FTT.UITextInput.$input[0].selectionEnd = from; //move cursor to selection start
				FTT.UITextInput.focus(); //scroll
				FTT.UITextInput.$input[0].blur(); //blur so we can focus again
				FTT.UITextInput.$input[0].selectionEnd = to; //move cursor to selection end
				FTT.UITextInput.focus(); //scroll
//				FTT.debug('selectRange: call selectRange');
				FTT.selectRange(from,to,0); //select
				int++;
				if ( int >= 2 ) {
					FTT.focusRunning = 0;
					clearInterval(ReSelect);
				}
			},50);
		}
	}
};
FTT.getRange = function(ed,from,to) {
	ed = FTT.getCurrentEditor();
	if ( ed == 'CodeEditor' ) {
//		FTT.debug('getRange: get range from CodeEditor/CodeMirror (CaretPosition only)');
		FTT.textbox = $('#FTTTextarea');
		from = FTT.textbox.textSelection( 'getCaretPosition' );
		to = from+FTT.textbox.textSelection('getSelection').length;
		return {from:from,to:to};
	} else if ( ed == 'CodeMirror' ) { //2023-11 update
		FTT.textbox = $('#FTTTextarea');
		FTT.CMSelectInfo = FTT.CodeMirrorInstance.doc.listSelections()[0];
		//convert line+character to JUST CHARACTER. Wtf am I supposed to do with line numbers????
		FTT.CMLineNoJesusWept1 = FTT.CMSelectInfo.anchor.ch;
		for(FTT.CMSelectInt=0;FTT.CMSelectInt<FTT.CMSelectInfo.anchor.line;FTT.CMSelectInt++) {
			FTT.CMLineNoJesusWept1 = (FTT.CMLineNoJesusWept1 + FTT.CodeMirrorInstance.doc.getLine(FTT.CMSelectInt).length);
		}
		FTT.CMLineNoJesusWept2 = FTT.CMSelectInfo.head.ch;
		for(FTT.CMSelectInt=0;FTT.CMSelectInt<FTT.CMSelectInfo.head.line;FTT.CMSelectInt++) {
			FTT.CMLineNoJesusWept2 = (FTT.CMLineNoJesusWept2 + FTT.CodeMirrorInstance.doc.getLine(FTT.CMSelectInt).length);
		}
		if ( FTT.CMLineNoJesusWept1 < FTT.CMLineNoJesusWept2 ) {
			return {from:FTT.CMLineNoJesusWept1,to:FTT.CMLineNoJesusWept2};
		} else {
			return {from:FTT.CMLineNoJesusWept2,to:FTT.CMLineNoJesusWept1};
		}
	} else {
//		FTT.debug('getRange: get range from UITextInput');
		return FTT.UITextInput.getRange();
	}
};
FTT.encapsulateContent = function(pre,post,ed) {
	ed = FTT.getCurrentEditor();
	if ( ed == 'CodeEditor' || ed == 'CodeMirror' ) {
//		FTT.debug('encapsulateContent, pre: '+pre+' post: '+post);
		FTT.textbox = FTT.textbox || $('#FTTTextarea');
		FTT.textbox.textSelection('encapsulateSelection',{pre:pre,post:post});
	} else {
		FTT.UITextInput.encapsulateContent(pre, post);
		FTT.focusInput();
	}
};
FTT.getValue = function(ed) {
	ed = FTT.getCurrentEditor();
//	FTT.debug('getValue');
	if ( ed == 'CodeEditor' ) {
		FTT.textbox = $('#FTTTextarea');
		return FTT.textbox.textSelection('getContents');
	} else if ( ed == 'CodeMirror' ) {
		return FTT.CodeMirrorInstance.doc.getValue();
	} else {
		return FTT.UITextInput.getValue();
	}
};
FTT.setValue = function(val,ed,pos,c) {
	ed = FTT.getCurrentEditor();
//	FTT.debug('setValue');
	if ( ed == 'CodeEditor' ) {
		FTT.textbox = FTT.textbox || $('#FTTTextarea');
		pos = FTT.getRange();
		FTT.textbox.textSelection('setContents',val);
		c = document.activeElement.classList;
		if ( c.contains('CodeMirror-code') || c.contains('ace_text-input') ) {
//			FTT.debug('setValue: call selectRange');
			FTT.selectRange(pos.from,pos.to);
		}
	} else if ( ed == 'CodeMirror' ) { //2023-11 update
		FTT.CodeMirrorInstance.doc.setValue(val);
	} else {
		FTT.UITextInput.setValue(val);
	}
};
FTT.main = {};
FTT.main.getValue = FTT.getValue;
FTT.main.setValue = FTT.setValue;
FTT.main.encapsulateContent = FTT.encapsulateContent;
FTT.main.getRange = FTT.getRange;
FTT.main.selectRange = FTT.selectRange;
FTT.main.focus = FTT.focusInput;
FTT.aceMirrorDisable = function(ed){
	ed = FTT.getCurrentEditor();
	if ( ed == 'CodeEditor' ) {
//		FTT.debug('aceMirrorDisable: disable codeEditor');
		$('#FTTReplyForm .group-codeeditor-main a')[0].click();
		return true;
	} else if ( ed == 'CodeMirror' ) {
		$('#FTTReplyForm #mw-editbutton-codemirror a')[0].click();
		return true;
	}
	return false;
};
FTT.passiveSyncFromCodeMirror = function(mode){
	var DelayedCodeMirrorSync = setInterval(function () {
		clearInterval(DelayedCodeMirrorSync);
		FTT.textbox = $( '#FTTTextarea' );
		//FTT.setValue(FTT.textbox.textSelection( 'getContents'));
		FTT.setValue(FTT.CodeMirrorInstance.doc.getValue());
		//FTT.doPreview('livepreview',FTT.PRMOpened); //todo: this still needed?
	},100);
};
FTT.aceUpMySleeve = function(a){ //thanks to w:en:User:BrandonXLF/SVGEditor.js and m:User:NguoiDungKhongDinhDanh/QuickFunction.js for figuring out how to load this!
	delete FTT.livePreviewCodeMirror;
	mw.user.options.set('usecodeeditor',1);
	if ( ! FTT.PRMOpened.highlightRef && ( FTT.settings['2010codeMirror2023'] || ( FTT.settings['2010templateDefault'] && M1('wgCanonicalNamespace') == 'Template' ) ) ) {
//		FTT.debug('aceUpMySleeve: set usecodemirror to 1');
		mw.user.options.set('usecodemirror',1);
	} else {
//		FTT.debug('aceUpMySleeve: set usecodemirror to 0');
		mw.user.options.set('usecodemirror',0);
	}
	if ( ! $('#FTTUITextInput .wikiEditor-ui')[0] && a != 'loadWikiEditor' && ( !FTT.isMobile || !FTT.settings.MFAdjeditor2010) ) {
		FTT.loadWikiEditor('ace');
		return;
	}
	FTT.UITextInput.$input[0].id = 'wpTextbox1';
	if ( M1('wgPageContentModel') == 'wikitext' ) {
		mw.loader.using(['ext.CodeMirror'], function() {
//			FTT.debug('loaded CodeMirror');
			if ( FTT.enableLivePreview ) {
				$('#FTTReplyForm').on('keydown',function(){FTT.passiveSyncFromCodeMirror('livepreview');});
			}
			$(document).ready(function() {
				FTT.UITextInput.$input[0].id = 'FTTTextarea';
			});
		});
	} else if ( ['Scribunto','css','javascript','json'].includes(M1('wgPageContentModel')) ){
		if ( M1('wgPageContentModel') == 'Scribunto' ) {
			mw.config.set('wgCodeEditorCurrentLanguage','lua');
		} else {
			mw.config.set('wgCodeEditorCurrentLanguage', M1('wgPageContentModel'));
		}
		if (mw.loader.getState('ext.codeEditor') === 'ready') {
			mw.loader.state({'ext.codeEditor': 'loaded'});
		}
		mw.loader.using(['ext.codeEditor'], function() {
//			FTT.debug('loaded ext.codeEditor');
			$(document).ready(function() {
				FTT.UITextInput.$input[0].id = 'FTTTextarea';
			});
		});
	}
};
FTT.maybeAceUpMySleeve = function(){
	if ( ( !FTT.isMobile || !FTT.settings.MFAdjeditor2010) && (FTT.settings['2010codepageDefault'] && ['Scribunto','css','javascript','json'].includes(M1('wgPageContentModel'))) || (FTT.settings['2010templateDefault'] && M1('wgCanonicalNamespace') == 'Template') || (FTT.pickEditor == '2010wikitext' && ['comment','edit','newsection','newheading'].includes(FTT.PRMOpened.type)) || ( FTT.settings['2010wikitextDefault'] && M1('wgPageContentModel') == 'wikitext' && ['heading','editFullPage'].includes(FTT.PRMOpened.type) ) ) {
		FTT.aceUpMySleeve();
	}
};
FTT.toggleEd2010Label = function() {
	if ( FTT.UISwitchEditorButton && ['Scribunto','css','javascript','json'].includes(M1('wgPageContentModel')) ) {
		FTT.UISwitchEditorButton.setLabel(new OO.ui.HtmlSnippet('<span class="ace-tm"><span class="ace_string">&lt;</span><span class="ace_keyword">/</span><span class="ace_variable">&gt;</span></span>'));
	} else if ( FTT.UISwitchEditorButton ) {
		FTT.UISwitchEditorButton.setLabel(new OO.ui.HtmlSnippet('<span class="cm-mw-accessible-colors"><span class="cm-mw-section-header">&lt;</span><span class="cm-mw-parserfunction-name">/</span><span class="cm-mw-htmltag-attribute">&gt;</span></span>'));
	}
	$('#FTTUISwitchEditorButton').addClass('FTT2010Label');
};
FTT.toggleEditor = function() {
	if ( ( ! FTT.settings.editorSwitchSkip2010 || ! ['comment','edit','newsection'].includes(FTT.PRMOpened.type) ) && ( FTT.activeEditor == 'source' || ( FTT.activeEditor == 'visualLight' && FTT.settings.editorSwitchSkipSource ) ) ) {
//		FTT.debug('switch to 2010 wikitext editor');
		mw.notify(FTT.msgs.switch2010Notice);
		FTT.activeEditor = '2010wikitext';
		FTT.lastUsed();
		if ( $('#FTTReplyForm .group-codeeditor-main a')[0] || $('#FTTReplyForm #mw-editbutton-codemirror a')[0] ) {
			FTT.aceMirrorEnable();
		} else {
			FTT.aceUpMySleeve();
		}
		FTT.toggleEd2010Label();
		mw.util.addCSS('#FTTliveToggle{display:inline}');
	} else if ( ['comment','edit','newsection'].includes(FTT.PRMOpened.type) && ! FTT.settings.editorSwitchSkipvisual && ( FTT.activeEditor == '2010wikitext' || ( FTT.activeEditor == 'source' && FTT.settings.editorSwitchSkip2010 ) ) ) {
//		FTT.debug('switch to visualLight');
		mw.notify(FTT.msgs.switchVisualLightNotice);
		if ( FTT.livePreviewDisabled == false ) {
			FTT.livePreviewToggle();
		}
		mw.util.addCSS('#FTTliveToggle{display:none}');
		if ( FTT.activeEditor == '2010wikitext' && ($('#FTTReplyForm .group-codeeditor-main a .oo-ui-image-progressive')[0] || $('#FTTReplyForm #mw-editbutton-codemirror .oo-ui-image-progressive')[0]) ) {
			FTT.aceMirrorDisable();
		}
		FTT.UITextInput.toggle(false);
		if ( ! FTT.UIVisual.id ) {
			FTT.initVisualLight();
		}
		if ( FTT.UISwitchEditorButton ) {
			FTT.UISwitchEditorButton.setLabel('</>');
			$('#FTTUISwitchEditorButton').removeClass('FTT2010Label');
		}
		$('#FTTUISwitchEditorButton').addClass('FTTHalfOpacity');
		FTT.UIVisual.classList.remove('FTTNoDisplay');
		FTT.activeEditor = 'visualLight';
		FTT.lastUsed();
		FTT.syncToVisual();
	} else {
//		FTT.debug('switch to source editor');
		mw.notify(FTT.msgs.switchSourceNotice);
		if ( $('#FTTReplyForm .group-codeeditor-main a')[0] || $('#FTTReplyForm #mw-editbutton-codemirror a')[0] ) {
			FTT.aceMirrorDisable();
			$('#FTTReplyForm .wikiEditor-ui-top').addClass('FTTNoDisplay');
		}
		FTT.UITextInput.toggle(true);
		if ( FTT.activeEditor == 'visualLight' ) {
			FTT.UIVisual.classList.add('FTTNoDisplay');
			FTT.syncToSource();
		}
		if ( FTT.UISwitchEditorButton ) {
			FTT.UISwitchEditorButton.setLabel('</>');
			$('#FTTUISwitchEditorButton').removeClass('FTTHalfOpacity FTT2010Label');
		}
		FTT.activeEditor = 'source';
		FTT.lastUsed();
		mw.util.addCSS('#FTTliveToggle{display:inline}');
	}
	FTT.focusInput();
};
FTT.loadEditNotice = function(notreally) {
//	FTT.debug('enom: Loading editnotice');
	FTT.enom = {}; // this will be a fork of EditNoticesOnMobile because I'm a lazy fuck
	FTT.enom.neverPopup = FTT.settings.noticeNeverPopup;
	FTT.enom.evilClasses = ['fmbox','fmbox-system','tmbox','tmbox-content','messagebox'];
	FTT.enom.storeNotice = function(notice){
		FTT.enom.cachedNotices = FTT.testValidJSON(FTT.getItemLS('ENOM'));
		if ( ! FTT.enom.cachedNotices ) {
			FTT.setItemLS('ENOM',FTT.pack('{}','UTF16'));
			FTT.enom.cachedNotices = FTT.testValidJSON(FTT.getItemLS('ENOM'));
		}
		if ( FTT.enom.cachedNotices ) { //not sure how all browsers behave if localStorage is unavailable, so to be safe, test validity
			FTT.enom.cachedNotices[FTT.enom.pageTitle] = {CRC32:FTT.enom.testIfEmptyInnerTextCRC32,date:FTT.enom.time,text:notice};
//			FTT.debug('enom: generated checksum '+FTT.enom.cachedNotices[FTT.enom.pageTitle].CRC32+' for notice on '+FTT.enom.pageTitle);
			FTT.enom.cachedNoticesNew = JSON.stringify(FTT.enom.cachedNotices);
			if ( FTT.enom.cachedNoticesNew.length > 200000 ) { //it's quite unlikely a user would accumulate >200K in edit notices, but if it somehow happens we purge all and start with a clean slate
				FTT.enom.cachedNoticesNew = '{}';
			}
			FTT.setItemLS('ENOM',FTT.pack(FTT.enom.cachedNoticesNew,'UTF16'));
		}
	};
	FTT.enom.getNotice = function() {
		FTT.enom.cachedNotices = FTT.testValidJSON(FTT.getItemLS('ENOM'));
		FTT.enom.pageTitle = FTT.PRMOpened.pageTitle.replace(/_/g,' ');
		FTT.enom.newNotice = false;
		if ( FTT.enom.cachedNotices ) {
			FTT.enom.update = false;
			FTT.enom.oldEditnoticeCRC32 = 0;
			if ( FTT.enom.cachedNotices[FTT.enom.pageTitle] ) {
				FTT.enom.oldEditnotice = FTT.enom.cachedNotices[FTT.enom.pageTitle].text;
				FTT.enom.oldEditnoticeCRC32 = FTT.enom.cachedNotices[FTT.enom.pageTitle].CRC32;
			}
			for (FTT.enom.cachedInt=0;FTT.enom.cachedInt<Object.keys(FTT.enom.cachedNotices).length;FTT.enom.cachedInt++){
				FTT.enom.checkEntry = FTT.enom.cachedNotices[Object.keys(FTT.enom.cachedNotices)[FTT.enom.cachedInt]];
				if ( typeof window.enomCacheExpiry == 'number' ) { //give the user some control over the expiry period by specifying enomCacheExpiry in their common.js.
					FTT.enom.cacheExpiry = window.enomCacheExpiry;
				} else {
					FTT.enom.cacheExpiry = 1209600000; //keep checksums for 2 weeks (1209600000 ms)
				}
				FTT.enom.cacheExpiryText = 7200000; //keep text for 2 hours (7200000 ms)
				if ( FTT.enom.checkEntry.date < FTT.enom.time -FTT.enom.cacheExpiry || ( FTT.enom.checkEntry.date < FTT.enom.time -FTT.enom.cacheExpiryText && Object.keys(FTT.enom.cachedNotices)[FTT.enom.cachedInt] == FTT.enom.pageTitle) ) {//entry >2 weels old or the current page and >2 hours (7200000 ms) old
//					FTT.debug('enom: removed '+Object.keys(FTT.enom.cachedNotices)[FTT.enom.cachedInt]+' from locally cached notices');
					delete FTT.enom.cachedNotices[Object.keys(FTT.enom.cachedNotices)[FTT.enom.cachedInt]];
					FTT.enom.update = true;
				} else if ( FTT.enom.checkEntry.date < FTT.enom.time -FTT.enom.cacheExpiryText && FTT.enom.cachedNotices[Object.keys(FTT.enom.cachedNotices)[FTT.enom.cachedInt]].text ) {
//					FTT.debug('enom: remove noticetext for '+Object.keys(FTT.enom.cachedNotices)[FTT.enom.cachedInt]+' from locally cached notices (older than 2 hours)');
					delete FTT.enom.cachedNotices[Object.keys(FTT.enom.cachedNotices)[FTT.enom.cachedInt]].text;
					FTT.enom.update = true;
				}
				if ( FTT.enom.update ) {
					FTT.setItemLS('ENOM',FTT.pack(FTT.enom.cachedNotices,'UTF16'));
				}
			}
			if ( FTT.enom.cachedNotices[FTT.enom.pageTitle] ){ //notice was cached less than 2 hours ago
//				FTT.debug('enom: found cached notice');
				FTT.enom.popupNotice(FTT.enom.cachedNotices[FTT.enom.pageTitle].text,false);
				return;
			}
		}
		FTT.enom.namespaces = mw.util.escapeRegExp(Object.values(FTT.NS).toString()).replace(/^,/,'').replace(/,/g,'|'); // "Talk|User|User talk|" etc
		FTT.enom.pageTitleNoNS = FTT.enom.pageTitle.replace(new RegExp('^('+FTT.enom.namespaces+'):'),'').replace(/\//g,'-');
		if ( FTT.PRMOpened.editintro ) {
			if ( FTT.B1.PF ) { //ParserFunctions are available on this wiki so you can get this either transcluded as a page if it exists or just see the text as entered.
				FTT.enom.wikitext = '{{#ifexist:' + FTT.PRMOpened.editintro + '|{{' + FTT.PRMOpened.editintro + '}}|'+FTT.PRMOpened.editintro+'}}';
			} else { //No ParserFunctions, assume the editintro must be transcluded
				FTT.enom.wikitext = '{{' + FTT.PRMOpened.editintro + '}}';
			}
		} else {
			FTT.enom.wikitext = '<div id="EditNoticeOnMobile">{{#ifexist:MediaWiki:Editnotice-{{NAMESPACENUMBER}}-'+FTT.enom.pageTitleNoNS+'|{{MediaWiki:Editnotice-{{NAMESPACENUMBER}}-'+FTT.enom.pageTitleNoNS+'}}|{{#ifexist:MediaWiki:Editnotice-{{NAMESPACENUMBER}}|{{MediaWiki:Editnotice-{{NAMESPACENUMBER}}}}}}}}</div><div id="enom_end"></div>';
		}
		FTT.enom.editNoticeParams = {
			format:'json',
			action:'parse',
			disablelimitreport:true,
			title:FTT.enom.pageTitle,
			contentmodel:'wikitext',
			pst:'1',
			prop:'text',
			formatversion:'2',
			uselang: FTT.userLang,
			text:FTT.enom.wikitext
		};
		mw.loader.using(['mediawiki.api'], function(){
//			FTT.debug('enom: download notice');
			FTT.enom.newNotice = true;
			var api = new mw.Api();
			api.post( FTT.enom.editNoticeParams ).done( function ( data ) {
				FTT.enom.parsednotices = data.parse.text.replace(/\n/g,'').replace(/.*(<div id="EditNoticeOnMobile">.*<\/div>)<div id="enom_end"><\/div>.*/,'$1');
				FTT.enom.testIfEmpty = document.createElement('div');
				FTT.enom.testIfEmpty.classList = 'enomTempDiv enomTempDivNew'; //need to attach this to the DOM to work with it.. maybe it's possible another way but this'll work
				FTT.enom.testIfEmpty.innerHTML = FTT.enom.parsednotices;
				$('body:eq(0)').append(FTT.enom.testIfEmpty);
				FTT.enom.suppressPopup = typeof $('.enomTempDivNew .nopopupnotice')[0];
				/*
				We are going to test if the notice contains any text content after removing elements that don't contain vital information about editing some particular page.
					* Nomobile elements wouldn't be displayed anyway. Edit notice creators should use "nomobile" as they see fit. Please consider that edit notices on mobile are presented as a popup, not a passive box above the text area.
					* The editnotice-link just links to the page containing the notice. It should be displayed on mobile (so nomobile shouldn't be added to it) but we ignore it when checking if the notice has any text content.
					* ..
				*/
				$('.enomTempDiv .nomobile,.enomTempDiv .editnotice-link,.enomTempDiv .editnotice-group').remove();
				$('.enomTempDiv *').removeClass(FTT.enom.evilClasses);
				FTT.enom.testIfEmptyInnerText = $('.enomTempDivNew')[0].innerText.trim();
				FTT.enom.testIfEmptyInnerTextCRC32 = FTT.CRC32(FTT.enom.testIfEmptyInnerText);
//				FTT.debug('enom: checksum for this notice: '+FTT.enom.testIfEmptyInnerTextCRC32);
				if ( FTT.enom.testIfEmptyInnerText == '' ) {
//					FTT.debug('enom: notice is (practically) empty, blanking');
					FTT.enom.parsednotices = '';
				}
				$('.enomTempDiv').remove();
				if ( FTT.enom.testIfEmptyInnerTextCRC32 == FTT.enom.oldEditnoticeCRC32 || FTT.enom.suppressPopup == 'object' || FTT.enom.neverPopup ) {
//					FTT.debug('enom: notice is identical to what we had cached (based on CRC32 checksum) or contains "nopopupnotice" class or you disabled automatic popups, adding button but no popup.');
					FTT.enom.popupNotice(FTT.enom.parsednotices,false);
				} else {
//					FTT.debug('enom: notice is different from what (if anything) was cached, adding button and creating popup');
					FTT.enom.popupNotice(FTT.enom.parsednotices,true);
				}
				FTT.enom.storeNotice(FTT.enom.parsednotices);
			});
		});
	};
	FTT.enom.fixWindowed = function(){
		if ( window.innerWidth < 710 ) { //'large' is 700px
			$('.oo-ui-windowManager.oo-ui-windowManager-modal.oo-ui-windowManager-fullscreen').addClass('oo-ui-windowManager-floating').removeClass('oo-ui-windowManager-fullscreen');
		}
	};
	FTT.enom.cleanupStyling = function(){
//		FTT.debug('enom: cleaning up notice');
		$('#EditNoticeOnMobile *').removeClass(FTT.enom.evilClasses);
		for (FTT.enom.noticeElementsInt=0;FTT.enom.noticeElementsInt<$('#EditNoticeOnMobile *').length;FTT.enom.noticeElementsInt++){
			$('#EditNoticeOnMobile *')[FTT.enom.noticeElementsInt].style.background = ''; //remove background colors that many edit notices have. Not appropriate in a popup
		}
		if ( $('#EditNoticeOnMobile .mw-collapsible')[0] ) {
			mw.loader.using('jquery.makeCollapsible').then( function () { //WP:HD includes a collapsible "Help desk templates" block
				mw.util.addCSS('#EditNoticeOnMobile .mw-collapsible-toggle a{color:#3366cc}#EditNoticeOnMobile .mw-collapsible-toggle{font-weight:normal;}');
				$($('#EditNoticeOnMobile .mw-collapsible')).makeCollapsible(); //T111565 FTFY
			});
		}
	};
	FTT.enom.showPopup = function(noticetext){
		if ( ! noticetext ) {
			noticetext = FTT.enom.noticeText;
		}
		mw.loader.using(['oojs-ui-core','oojs-ui-windows']).then(function(){
//			FTT.debug('enom: enom.shopPopup, loaded oojs-ui-windows');
			FTT.enom.waitForElement('#EditNoticeOnMobile',FTT.enom.cleanupStyling); //popup doesn't immediately exist..
			FTT.popup(noticetext,'#EditNoticeOnMobile');
		});
	};
	FTT.enom.waitForElement = function(element,action,skipInstant,observerInt){
		observerInt = 0;
		if ( $(element)[0] && ! skipInstant ) {
//			FTT.debug('enom: found element '+element+', it\'s already here.');
			action();
			return;
		}
		FTT.enom.processObservations = function(){
			observerInt++;
			if ( $(element)[0] ) {
//				FTT.debug('enom: found element '+element);
				action();
				FTT.enom.observer.disconnect();
			} else if ( observerInt > 50 ) {
				FTT.enom.observer.disconnect(); //generally shouldn't happen but the observer shouldn't keep running until the end of time if the element never shows
			}
		};
		FTT.enom.observer = new MutationObserver(FTT.enom.processObservations); //looks for new HTML elements in the DOM. like the source editor if we expect it to be loading
		FTT.enom.observer.observe(document.body,{childList: true,subtree: true,attributes: false,characterData: false});
	};
	FTT.enom.popupNotice = function(noticetext,popup){
		if ( noticetext == '' || noticetext.match(/<div[^>]EditNoticeOnMobile[^>]*><\/div>/) ) { //empty notice, don't show anything
//			FTT.debug('enom: notice is empty (no notice for this page)');
			return;
		}
		if ( !FTT.isMobile || !FTT.settings.MFAdjEditNotice ) {
			FTT.UIeditNoticeButton.toggle(true);
		}
		FTT.enom.noticeText = noticetext;
		if ( popup && ! FTT.enom.neverPopup ) { //shove popup into user's face only if freshly downloaded
			FTT.enom.showPopup(noticetext);
		}
	};
	if ( notreally != 'notreally' ) { //otherwise we just needed some functions to be loaded
		FTT.enom.getNotice();
	}
};
FTT.toggleFramed = function(f,int,els) {
	FTT.toggleFArr = ['UIUndoButton','UIRedoButton','markUpDropDown','UIMarkupLinkButton','cITSbuttonMenu','UISwitchEditorButton','UISettingsButton','UIoneTimeToolsButton','UIeditNoticeButton'];
	for (int=0;int<FTT.toggleFArr.length;int++) {
		if ( FTT[FTT.toggleFArr[int]] ) {
			FTT[FTT.toggleFArr[int]].toggleFramed(f);
			if ( f ) {
				FTT[FTT.toggleFArr[int]].$button.addClass('FTT075Opacity');
			} else {
				FTT[FTT.toggleFArr[int]].$button.removeClass('FTT075Opacity');
			}
		}
	}
	els = $('#FTTMainButtonBar,#FTTCustomInserts,#FTTCustomInsertsAutoPost');
	if ( f ) {
		els.addClass('FTT075Opacity');
	} else {
		els.removeClass('FTT075Opacity');
	}
};
FTT.setCustomBackground = function(url) {
	if ( ( url || FTT.settings.customBackground != '' ) && (!FTT.isMobile || !FTT.settings.MFAdjcustomBackground) ) {
		$('#FTTCustomBGTest').remove();
		$('body').append('<div id="FTTCustomBGTest" class="FTTNoDisplay"><img src="'+(url||FTT.settings.customBackground)+'"/></div>');
		var BGWidth = setInterval(function (int) {
			int++;
			if ( $('#FTTCustomBGTest img')[0].width > 0 || int > 15 ) {
				clearInterval(BGWidth);
				if ( ( $('#FTTCustomBGTest img')[0].width / $('#FTTCustomBGTest img')[0].height ) <= 1 ) {
					$('#FTTReplyForm').addClass('FTTPortrait');
				} else {
					$('#FTTReplyForm').removeClass('FTTPortrait');
				}
				return;
			}
		},100);
		FTT.toggleFramed(true);
		$('#FTTReplyForm')[0].style['background-image'] = 'url("'+(url||FTT.settings.customBackground)+'")';
		$('#FTTReplyForm').addClass('FTTCustomBG');
	}
};
FTT.editSummaryPipeTrick = function() { //also adds rewritun on blur to summary and title
	$(FTT.BSummaEl).blur(function() { FTT.UITextInputSummary.setValue(FTT.rewritunUrUrlz(FTT.UITextInputSummary.getValue().replace(/\[\[(.*):([^:]*)\|\]\]/,'[[$1:$2|$2]]'),'contentmove'));});
	$(FTT.BTitleEl).blur(function() { FTT.UITextInputTitle.setValue(FTT.rewritunUrUrlz(FTT.UITextInputTitle.getValue())); });
};
FTT.showProtectedWarning = function(title,force) {
	if ( ! FTT.shownProtectionWarn || force ) {
		FTT.shownProtectionWarn = true;
		FTT.protectionLevelText = '{{int:Protectedpagetext|{{subst:#switch:{{subst:PROTECTIONLEVEL:edit}}|sysop=editprotected|{{subst:PROTECTIONLEVEL:edit}}}}|'+FTT.B1['restriction-edit']+'}}'; //WHY is MediaWiki:Protectedpagetext called with a parameter like "editprotected" to indicate admin-only protection? WHY??
		api.get( {format: 'json', 'pst':1, 'formatversion':2, contentmodel:'wikitext', title: title, action: 'parse', disablelimitreport: true, uselang: FTT.userLang, text: FTT.protectionLevelText} ).then( function ( data ) {
			if ( ! $('#FTTProtectedWarning')[0] ) {//when called in quick succession the warning from the first request could be here now
				if ( ! FTT.enom ) {
					FTT.loadEditNotice('notreally');
				}
				FTT.enom.waitForElement('#EditNoticeOnMobile',FTT.enom.cleanupStyling);
				FTT.popup('<div id="EditNoticeOnMobile">'+data.parse.text+'</div>');
			}
		} );
	}
};
FTT.getInnerTextTitles = function() {
	FTT.titleElementsArray = Array.from($('#mw-content-text .mw-headline'));
	FTT.innerTextTitles = [];
	for (FTT.innerTextTitlesInt=0;FTT.innerTextTitlesInt<FTT.titleElementsArray.length;FTT.innerTextTitlesInt++) {
		FTT.innerTextTitles.push(FTT.titleElementsArray[FTT.innerTextTitlesInt].innerText);
	}
	return FTT.innerTextTitles;
};
FTT.checkIfTitleUnique = function() {
	if ( typeof FTT.innerTextTitles == 'undefined' ) {
		FTT.getInnerTextTitles();
	}
	if ( FTT.innerTextTitles.indexOf(FTT.flattenWikiText(FTT.UITextInputTitle.getValue())) != -1 ) {
		$(FTT.BTitleEl).addClass('FTTRedBG');
		FTT.UITextInputTitle.setLabel(FTT.msgs.sectionTitleNotUnique);
	} else {
		$(FTT.BTitleEl).removeClass('FTTRedBG');
		FTT.UITextInputTitle.setLabel('');
	}
};
FTT.appendToFirstBlockParent = function(targetelement,formelement,mode,dispInt) {
//	FTT.debug('find first block parent for element:');
//	FTT.debug(targetelement);
	if ( targetelement == null ) {
//		FTT.debug('hey, they didn\'t give me an element. Oh my god, they killed it! YOU BASTARDS!! In retaliation, I\'m reparsing the page!');
		FTT.youBastards = true;
		FTT.parsePageInPlace();
		return false;
	}
	if ( M1('skin') == 'minerva' && $('.talk-overlay.visible .FTTLinks')[0] ) {
		FTT.testParentDisplay = targetelement;
		for(dispInt=0;dispInt<10;dispInt++){
//			FTT.debug('test display of:');
//			FTT.debug(FTT.testParentDisplay);
			if ( typeof FTT.testParentDisplay == 'object' && FTT.testParentDisplay && getComputedStyle(FTT.testParentDisplay).display == 'none' ) {
//				FTT.debug('element associated with this FTT icon is not currently visible. You\'re viewing a thread which was internally copied into an overlay. Multiple elements with the same ID?? Funky advanced mobile mode Minerva shit.');
				targetelement = $('.talk-overlay.visible')[0];//fallback if the next loop finds nothing
				for(FTT.findCloneInt=0;FTT.findCloneInt<$('.talk-overlay.visible .FTTLinks').length;FTT.findCloneInt++){
//					FTT.debug('test element #' + FTT.findCloneInt);
					if ($('.talk-overlay .FTTLinks')[FTT.findCloneInt].id.match(E1(FTT.PRMOpened.id))){
//						FTT.debug('found cloned FTT icon for this comment');
						targetelement = $('.talk-overlay .FTTLinks')[FTT.findCloneInt];
//						FTT.debug(targetelement);
					}
				}
				break;
			}
			if ( FTT.testParentDisplay ) {
				FTT.testParentDisplay = FTT.testParentDisplay.parentElement;
			}
		}
	}
	FTT.appendToFirstBlockParentTypes = ['block'];
	if ( mode == 'permalink' ) {
		FTT.appendToFirstBlockParentTypes.push('inline-block');
	}
//	FTT.debug('check if we need to go up parentElements, test display of:');
	while ( typeof targetelement == 'object' && targetelement != null && ! FTT.appendToFirstBlockParentTypes.includes(getComputedStyle(targetelement).display) && ! targetelement.classList.contains('mw-parser-output') ) {
//		FTT.debug(targetelement);
//		FTT.debug('..NOPE.');
		targetelement = targetelement.parentElement;
	}
	if ( mode == 'permalink' ) {
		targetelement.prepend(formelement);
	} else {
		targetelement.append(formelement);
	}
};
FTT.isWikiloveCmt = function(a){
//	FTT.debug('determine if this element is a wikilove comment (barnstar, cookie, kitten, puppy, goat, etc):');
//	FTT.debug(a);
	for(FTT.wikiloveInt=0;FTT.wikiloveInt<100;FTT.wikiloveInt++){
		if ( a == null || a.id == 'mw-content-text' ) {
			return false;
		}
		if ( ! ['LI','OL','UL','DD','DL','P','SPAN','SMALL','H1','H2','H3','H4','H5','H6'].includes(a.nodeName) && a.querySelectorAll('H1,H2,H3,H4,H5,H6,.LegacyCmt,.FTTCmt').length < 2 && ( ! a.classList || (! a.classList.contains('FTTH1SectContainer') && ! a.classList.contains('FTTH2SectContainer') && ! a.classList.contains('FTTH1SectionsContainer') && ! a.classList.contains('FTTH2SectionsContainer') ) ) ) {
//			FTT.debug('unexpected nodeName: '+a.nodeName+' and node only contains one signature element');
			return true;
		}
		a = a.parentElement;
	}
	return false;
};
FTT.applyFunctionToSelection = function(a){//a=function()
	FTT.encapsulateContent('FTTAPPLYFUNC','FTTAPPLYFUNC');
	FTT.extractedSelectedText = FTT.getValue().match(/FTTAPPLYFUNC([^]*)FTTAPPLYFUNC/)[1];
	FTT.processedExtractedSelectedText = a(FTT.extractedSelectedText);
	FTT.setValue(FTT.getValue().replace(/FTTAPPLYFUNC[^]*FTTAPPLYFUNC/,FTT.processedExtractedSelectedText));
};
FTT.preloadWikiText = function(PRM, trigger,MediaWikiMsg) {
//	FTT.debug('preloadWikiText: preload comment or section or full page to edit');
	if ( PRM.type == 'newsection' ) {
//		FTT.debug('preloadWikiText: new section, no defined preload');
		return;
	}
	PRM = FTT.addPageAndSectionTitleToRPL(PRM);
	FTT.disableForm(true); //disable form until preload is done
	FTT.UIReplyButton.setLabel(FTT.B1['htmlform-submit']);
	FTT.getWikiTextParams = {action: 'query', prop: 'revisions', format: 'json', rvprop:'content|ids',rvslots:'*'};
	if ( PRM.type == 'editFullPage' ) {
		if ( M1('wgRevisionId') && M1('wgRevisionId') != M1('wgCurRevisionId') ) {
//			FTT.debug('preloadWikiText: obtain wikitext for old revision: '+M1('wgRevisionId'));
			FTT.oldRevLoaded = 1;
			FTT.getWikiTextParams.revids = M1('wgRevisionId');//editing an old revision
		} else if ( M1('wgCurRevisionId') ) {
//			FTT.debug('preloadWikiText: obtain wikitext for current revision');
			FTT.getWikiTextParams.titles = PRM.pageTitle.replace(/_/,' ');
		} else if ( MediaWikiMsg ) {
//			FTT.debug('preloadWikiText: preload MediaWiki system message');
			FTT.getWikiTextParams = {action: 'query',meta:'allmessages',ammessages:M1('wgTitle')};
		}
	} else {
		FTT.getWikiTextParams.titles = PRM.pageTitle;
	}
	api.get( FTT.getWikiTextParams ).then( function ( data ) {
//		FTT.debug(data);
		if ( data.query.pages && data.query.pages[-1] && data.query.pages[-1].missing == '' && FTT.PRMOpened.subtype == 'locator' ) {
//			FTT.debug('preloadWikiText: page doesn\'t exist, subtype locator');
			PRM.origPageTitle = PRM.pageTitle;
			PRM.pageTitle = FTT.PRM[PRM.pageTitleInt].pageTitle;
			if ( PRM.pageTitle.replace(/ /g,'_') != PRM.origPageTitle ) {
//				FTT.debug('preloadWikiText: section/comment was possibly moved. trying page title found using legacy method.');
				FTT.PRMOpened = PRM;
				FTT.PRM[PRM.int] = PRM;
				FTT.PRMEdit[PRM.int] = PRM;
				FTT.PRMEdit[PRM.int].type = 'edit';
			}
			FTT.cancelReply();
			FTT.openReplyForm(FTT.PRMOpened, 'OpenForm_getInsertionPointComment');
			return 'cancel';
		}
		if ( MediaWikiMsg ) {
			FTT.wikiTextForEdit = '';
			try{FTT.wikiTextForEdit = ( data.query.allmessages[0]['*'] || '' );} catch (e) {}
//			FTT.debug('preloadWikiText: got MediaWiki system message');
		} else {
			FTT.wikiTextForEdit = data.query.pages[Object.keys(data.query.pages)[0]].revisions[0].slots.main['*'];
			FTT.sourceRev = data.query.pages[Object.keys(data.query.pages)[0]].revisions[0].revid; //will be used as baserevid for full section/page editing
//			FTT.debug('preloadWikiText: got source revision ID: '+FTT.sourceRev);
		}
		if ( PRM.type == 'editFullPage' ) {
//			FTT.debug('preloadWikiText: editing a full page, preload existing wikitext');
			FTT.setValue(FTT.wikiTextForEdit);
			FTT.undoRevisions.UITextInput = [];
			FTT.undoSave();
			FTT.applyModules('afterPreload');
			FTT.disableForm(false);
			FTT.focusInput('start');
			FTT.maybeAceUpMySleeve(trigger);
			if ( trigger == 'highlightRef' ) {
//				FTT.debug('preloadWikiText: select a specific reference after clicking an edit marker from a reference');
				FTT.highlightRef(undefined,FTT.refNumInList);
			} else if ( FTT.settings.refList && $('.FTTRef')[0] ) {
//				FTT.debug('preloadWikiText: loading reflist');
				FTT.highlightRef(undefined,0,true);
			}
			FTT.openingFormInProgress = new Date().getTime();
			return;
		}
		if ( PRM.type == 'heading' ) {
//			FTT.debug('preloadWikiText: preload section wikitext');
			FTT.sectionTextForPreload = FTT.getInsertionPointSection(FTT.PRMOpened, FTT.wikiTextForEdit).sectiontext;
			if ( FTT.sectionTextForPreload == null ) {
//				FTT.debug('preloadWikiText: section not found, try preloading text by section number instead'); //this happens on sections like "== {{int:license-header}} ==" as they don't contain the innerText "Licensing". You might be wondering why not always do this? Because section numbers are not permanent, if sections are added/removed after you loaded the page (hello ArchiverBot!), they break. This is very much a MediaWiki problem and FTT works around it by determining the sectionnum from wikitext.
				FTT.sectionNumFromLink = -1;
				try{FTT.sectionNumFromLink = Number(FTT.processElementArray[FTT.PRMOpened.int].querySelectorAll('A')[0].href.match(/section=(T\-)?([0-9]*)/)[2]);} catch (e) {}
				if ( FTT.sectionNumFromLink > -1 ) {
					FTT.sectionTextForPreload = FTT.getSectionByNum(FTT.wikiTextForEdit,FTT.sectionNumFromLink);
				} else {
					FTT.sectionTextForPreload = '';
				}
			}
			FTT.setValue(FTT.sectionTextForPreload);
			FTT.undoRevisions.UITextInput = [];
			FTT.undoSave();
			FTT.applyModules('afterPreload');
			FTT.disableForm(false);
			FTT.focusInput('start');
			FTT.maybeAceUpMySleeve();
			FTT.openingFormInProgress = new Date().getTime();
			return;
		}
//		FTT.debug('preloadWikiText: preloading existing comment');
		FTT.restoreNewlineRegExp = new RegExp(E1('<br/>'), 'g');
		FTT.restoreDoubleNewlineRegExp = new RegExp(E1('<br style="margin-bottom:0.5em"/>'), 'g');
		FTT.wikiTextForEditData = FTT.getInsertionPointComment(PRM, FTT.wikiTextForEdit);
		if ( FTT.wikiTextForEditData == 'retry' ) { //locator in a section or page that was possibly moved, pageTitle has already been adjusted
			FTT.cancelReply();
			FTT.openReplyForm(FTT.PRMOpened);
			return;
		}
		FTT.wikiTextForEditComment = FTT.wikiTextForEditData.relevantComment;
		if ( ! FTT.wikiTextForEditData.multiline ) {
			FTT.wikiTextForEditComment = FTT.wikiTextForEditComment.replace(/^[\*\:\# ┌─┘]*/, '');
		}
		if ( FTT.wikiTextForEditData.relevantComment.match(/^[\*\:\# ]*[┌─┘]/) ) {
			FTT.reinsertNL = true;
		}
		FTT.prefLegacySig = E1(mw.user.options.get('nickname'));
		FTT.prefLegacySigMatches = FTT.wikiTextForEditComment.match(new RegExp(FTT.prefLegacySig));
		FTT.endOfCmtSigRegExp = new RegExp(' *<span id="[^"]*" class="FTTCmt".*');
		//if the legacy signature is just a single link (like "[[User:Example]]"), don't try to match it
		if ( ! FTT.wikiTextForEditComment.match(FTT.endOfCmtSigRegExp) && FTT.prefLegacySig.length > 4 && ! mw.user.options.get('nickname').match(/^[ ]*\[\[[^\[\]]*\]\][ ]*$/) && FTT.prefLegacySigMatches && FTT.prefLegacySigMatches.length == 1 ) {
			FTT.endOfCmtSigRegExp = new RegExp(' *'+FTT.prefLegacySig+'.*');
		}
		FTT.wikiTextForEditSigForPreview = FTT.wikiTextForEditComment.match(FTT.endOfCmtSigRegExp);
		if ( FTT.wikiTextForEditSigForPreview ) {
			FTT.wikiTextForEditSigForPreview = FTT.wikiTextForEditComment.match(FTT.endOfCmtSigRegExp)[0];
		} else {
			FTT.wikiTextForEditSigForPreview = '';
		}
		FTT.wikiTextForEditCommentStripped = FTT.wikiTextForEditComment.replace(FTT.endOfCmtSigRegExp, '');
		if ( FTT.wikiTextForEditCommentStripped.match(/<table/) ) { //cheap regex to determine if we should bother at all
			FTT.htmlTablesInOrigCmt = FTT.wikiTextForEditCommentStripped.match(/<table[^\n>]*>([^<]|<(?!\/table>))*<\/table>/g);
			if ( FTT.htmlTablesInOrigCmt ) {
				for ( FTT.htmlTableInt=0;FTT.htmlTableInt<FTT.htmlTablesInOrigCmt.length;FTT.htmlTableInt++){
					FTT.wikiTextForEditCommentStripped = FTT.wikiTextForEditCommentStripped.replace(FTT.htmlTablesInOrigCmt[FTT.htmlTableInt],FTT.htmlToWikiTable(FTT.htmlTablesInOrigCmt[FTT.htmlTableInt]));
				}
			}
		}
		FTT.wikiTextForEditCommentMultiline = FTT.wikiTextForEditCommentStripped.replace( FTT.restoreNewlineRegExp, '\n' ).replace(FTT.restoreDoubleNewlineRegExp,'\n\n');
		FTT.wikiTextForEditCommentMultilineRawList = FTT.listToRaw(FTT.wikiTextForEditCommentMultiline);
		FTT.wikiTextForEditSTLOld = '';
		FTT.wikiTextForEditSTLNew = FTT.wikiTextForEditCommentMultilineRawList;
		//todo: this regex CAN BECOME A RUNAWAY REGEX. (it did when it was applied to whole sections) It needs to be improved/replaced. (https://en.wikipedia.org/w/index.php?title=Wikipedia:Village_pump_(technical)&oldid=1090343769#How_to_give_labels_to_specific_objects_in_template?)
		FTT.RETFEscapeMatchedTEMPLATERegExp = new RegExp('\\{\\{(([^\\{\\}]|\\{[^\\{]|\\}[^\\}]|\\{\\{(([^\\{\\}]|\\{[^\\{|\\}[^\\}])*)\\}\\}|\\{\\{(([^\\{\\}]|\\{[^\\{|\\}[^\\}]|\\{\\{(([^\\{\\}]|\\{[^\\{|\\}[^\\}])*)\\}\\})*)\\}\\})*)\\}\\}','g'); //catches up to two level of nested templates like {{template1|{{template2|template3}}}}}}
		FTT.wikiTextForEditSTLRegExp = new RegExp('(\\{\\{#tag:syntaxhighlight\\|(([^\n\\{\\}]|\\}[^\\}]|' + FTT.RETFEscapeMatchedTEMPLATERegExp.source + ')*))\\{\\{' + FTT.B1.newline + '\\}\\}','g');
		while ( FTT.wikiTextForEditSTLOld != FTT.wikiTextForEditSTLNew ) {
			FTT.wikiTextForEditSTLOld = FTT.wikiTextForEditSTLNew;
			FTT.wikiTextForEditSTLNew = FTT.wikiTextForEditSTLNew.replace(FTT.wikiTextForEditSTLRegExp,'$1\n');
		}
		FTT.wikiTextForEditSTLTagRegExp = new RegExp('(\\{\\{#tag:syntaxhighlight\\|(([^\\{\\}\\|]|\\}[^\\}]|' + FTT.RETFEscapeMatchedTEMPLATERegExp.source + ')*))(\\|[^\\}\n\\\\|]+)?(\\|[^\\}\n\\\\|]+)?(\\|[^\\}\n\\\\|]+)?(\\|[^\\}\n\\\\|]+)?\\}\\}','g');
		FTT.wikiTextForEditSTLNew = FTT.wikiTextForEditSTLNew.replace(FTT.wikiTextForEditSTLTagRegExp,'<syntaxhighlight $12 $13 $14 $15>\n$2</syntaxhighlight>');
		for(FTT.wikiTextForEditSTLSpaceCleanInt=0;FTT.wikiTextForEditSTLSpaceCleanInt<4;FTT.wikiTextForEditSTLSpaceCleanInt++){
			FTT.wikiTextForEditSTLNew = FTT.wikiTextForEditSTLNew.replace(/(<syntaxhighlight)([ ]?\|)/g,'$1 ').replace(/(<syntaxhighlight)([^>\|\n]*[ ]?\|)/g,'$1 ').replace(/(<syntaxhighlight)([^>\|\n]*[ ]?\|)/g,'$1 ').replace(/(<syntaxhighlight)([^>\|\n]*[ ]?\|)/g,'$1 ');
		}
		FTT.wikiTextForEditSTLNew = FTT.wikiTextForEditSTLNew.replace(/(<syntaxhighlight(([^>\n])*))([^ ])[ ]*>/,'$1$4>').replace(/<syntaxhighlight lang=text[ ]*>[\n]*(([^<]|<(?![\/]?syntaxhighlight>))*)<\/syntaxhighlight>/g,'<pre>\n$1</pre>');
		FTT.setValue(FTT.wikiTextForEditSTLNew.trim());
		FTT.undoRevisions.UITextInput = [];
		FTT.undoSave();
		FTT.applyModules('afterPreload');
		FTT.disableForm(false);
//		FTT.debug('preloadWikiText: edit preload done, focus text input');
		FTT.focusInput();
		FTT.maybeAceUpMySleeve();
	}, function ( code, data ) { FTT.APIError(code, data);
	});
};
FTT.redo = function() {
	FTT.undoField = FTT.getLastActiveField(); // UITextInputTitle, UITextInputSummary or main (formerly UITextInput)
	if ( typeof FTT.redoRevisions[FTT.undoField][FTT.redoRevisions[FTT.undoField].length -1] == 'string' ) {
		if ( FTT.undoRevisions[FTT.undoField][FTT.undoRevisions[FTT.undoField].length -1] != FTT[FTT.undoField].getValue() ) {
			FTT.undoRevisions[FTT.undoField].push(FTT[FTT.undoField].getValue());
		}
		FTT[FTT.undoField].focus();
		FTT[FTT.undoField].setValue(FTT.redoRevisions[FTT.undoField][FTT.redoRevisions[FTT.undoField].length -1]);
		FTT.redoCurRev[FTT.undoField] = FTT.redoRevisions[FTT.undoField][FTT.redoRevisions[FTT.undoField].length -1];
		FTT.redoRevisions[FTT.undoField].pop();
		if ( FTT.redoRevisions[FTT.undoField].length == 0 ) {
			$('.FTTSVGRedo:not(.FTTSVGUndo)').addClass('FTTHalfOpacity');
			FTT.UIRedoButton.setDisabled(true);
		}
	}
};
FTT.undo = function() {
	FTT.undoField = FTT.getLastActiveField();
	FTT[FTT.undoField].focus();
	if ( FTT[FTT.undoField].getValue() != FTT.undoRevisions[FTT.undoField][FTT.undoRevisions[FTT.undoField].length -1] ) {
		FTT.undoArrPos = 1;
	} else {
		FTT.undoArrPos = 2;
	}
	if ( typeof FTT.undoRevisions[FTT.undoField][FTT.undoRevisions[FTT.undoField].length -FTT.undoArrPos] == 'string' ) {
		FTT.redoRevisions[FTT.undoField].push(FTT[FTT.undoField].getValue());
		FTT.redoCurRev[FTT.undoField] = FTT[FTT.undoField].getValue();
		$('.FTTSVGRedo:not(.FTTSVGUndo)').removeClass('FTTHalfOpacity');
		FTT.UIRedoButton.setDisabled(false);
		FTT[FTT.undoField].setValue(FTT.undoRevisions[FTT.undoField][FTT.undoRevisions[FTT.undoField].length -FTT.undoArrPos]);
		FTT.undoRevisions[FTT.undoField].pop();
		if ( FTT.undoRevisions[FTT.undoField].length < 2 ) {
			$('.FTTSVGRedo.FTTSVGUndo').addClass('FTTHalfOpacity');
			FTT.UIUndoButton.setDisabled(true);
		}
	}
};
FTT.undoSave = function(int) {
	for(int=0;int<3;int++) {
		if ( FTT.settings.undoFunc ) {
			FTT.undoSaveCurText = FTT[FTT.undoFields[int]].getValue();
			if ( FTT.undoSaveCurText != FTT.undoRevisions[FTT.undoFields[int]][FTT.undoRevisions[FTT.undoFields[int]].length -1] && FTT.undoSaveCurText != FTT.redoCurRev[FTT.undoFields[int]] ) {
				FTT.maxUndoRevs[FTT.undoFields[int]] = Math.floor(5000000 / FTT.undoSaveCurText.length); //5MB
				if ( FTT.maxUndoRevs[FTT.undoFields[int]] < 10 ) {
					FTT.maxUndoRevs[FTT.undoFields[int]] = 10; //theoretical max ram usage would be 40MB when editing a 4MB wiki page, but those are rare and you wouldn't dare edit those on a phone
				}
				while ( FTT.undoRevisions[FTT.undoFields[int]].length >= FTT.maxUndoRevs[FTT.undoFields[int]] ) {
					FTT.undoRevisions[FTT.undoFields[int]].shift();
				}
				FTT.undoRevisions[FTT.undoFields[int]].push(FTT.undoSaveCurText);
				FTT.redoRevisions = {'UITextInputTitle':[],'UITextInputSummary':[],'main':[]};
				$('.FTTSVGRedo:not(.FTTSVGUndo)').addClass('FTTHalfOpacity');
				FTT.UIRedoButton.setDisabled(true);
				if ( FTT.undoRevisions[FTT.undoFields[int]].length > 1 ) {
					$('.FTTSVGRedo.FTTSVGUndo').removeClass('FTTHalfOpacity');
					FTT.UIUndoButton.setDisabled(false);
				}
//				//FTT.debug(FTT.undoRevisions[FTT.undoFields[int]].toString());
			}
		}
	}
};
FTT.undoAutoSave = function(startTime,fieldInt) {
	FTT.undoStart = startTime;
	FTT.undoFields = ['UITextInputTitle','UITextInputSummary','main'];
	for(fieldInt=0;fieldInt<3;fieldInt++) {
		FTT.undoFocus(FTT.undoFields[fieldInt]);
	}
	var undoSave = setInterval(function () {
		if ( ! FTT.UITextInput.isElementAttached() || FTT.undoStart != startTime ) { //form was closed or closed and another was opened
//			FTT.debug('undoSave: form closed');
			clearInterval(undoSave);
			return;
		}
		FTT.undoSave();
	},10000);
};
FTT.undoFocus = function(field) {
	FTT.main.$input = FTT.UITextInput.$input; //todo: CodeEditor/CodeMirror
	FTT[field].$input.on('focus',function(){
		if ( FTT.redoRevisions[field].length == 0 ) {
			$('.FTTSVGRedo:not(.FTTSVGUndo)').addClass('FTTHalfOpacity');
			FTT.UIRedoButton.setDisabled(true);
		} else {
			$('.FTTSVGRedo:not(.FTTSVGUndo)').removeClass('FTTHalfOpacity');
			FTT.UIRedoButton.setDisabled(false);
		}
		if ( FTT.undoRevisions[field].length < 2 ) {
			$('.FTTSVGRedo.FTTSVGUndo').addClass('FTTHalfOpacity');
			FTT.UIUndoButton.setDisabled(true);
		} else {
			$('.FTTSVGRedo.FTTSVGUndo').removeClass('FTTHalfOpacity');
			FTT.UIUndoButton.setDisabled(false);
		}
	});
	if ( FTT.settings.undoShortcuts ) {
		FTT[field].$input.on('keydown',function(event){
			if ( event.ctrlKey && ( (event.shiftKey && event.key == 'Z') || event.key == 'y' ) ) {
				event.preventDefault();
				FTT.redo();
			} else if ( event.ctrlKey && event.key == 'z' ) {
				event.preventDefault();
				FTT.undo();
			}
		});
	}
};
FTT.loadOnDemand = function(triggerElement,force) {
	if ( FTT.ninjaLoaded || $('html.ve-active')[0] || M2('action') == 'edit' || M1('wgAction') == 'edit' ) {
//		FTT.debug('VE active or already loaded, skipping');
		return;
	}
	if ( document.activeElement.nodeName == 'A' && ! force ) {
//		FTT.debug('you clicked a link within a header. not loading links');
		return;
	}
//	FTT.debug('run searchNodeContentsLoop after clicking a header with ninjaLoader enabled');
	FTT.searchNodeContentsLoop();
	if ( triggerElement && triggerElement.target.classList.contains('mw-headline') ) {
		window.location.hash = '#' + triggerElement.target.id;
	} else if ( triggerElement && triggerElement.target.nodeName.match(/^H[1-6]$/) && triggerElement.target.querySelectorAll('.mw-headline')[0] ) {
		window.location.hash = '#' + triggerElement.target.querySelectorAll('.mw-headline')[0].id;
	}
	if ( M2('FTTScrToTime') ) {
		FTT.scrollToComment(M2('FTTScrToUsr'),M2('FTTScrToTime'));
	}
	$('.FTTninjaBtn').remove();
};
if ( FTT.goNinja ) {
	if ( FTT.isMobile ) { //avoid collapsible section stuff on mobile
		FTT.ninjaBtn = document.createElement('li');
		FTT.ninjaBtn.classList.add('FTTninjaBtn');
		FTT.ninBtn = 'ninjabutton';
		FTT.ninjaBtn.innerHTML = '<a class="menu__item--FTT"><span class="mw-ui-icon-minerva-die mw-ui-icon"></span><span>'+FTT.msgs.FTT+'</span></a>';
		FTT.ninjaBtn.onclick = function(event) {FTT.loadOnDemand(event,1);};
		$('#p-navigation')[0].prepend(FTT.ninjaBtn);
	} else {
		FTT.ninjaHeaders = '#firstHeading,#mw-content-text H1,#mw-content-text H2,#mw-content-text H3,#mw-content-text H4,#mw-content-text H5,#mw-content-text H6';
		$('body').keydown(function(event){
			if ( event.ctrlKey && event.which == 13 ) {
				FTT.loadOnDemand();
			}
		});
		$(FTT.ninjaHeaders).on('click',FTT.loadOnDemand);
		$('#mw-content-text').on('dblclick',function(event) { //also load on double click
			FTT.loadOnDemand();
			$(document).ready(function() {
				if ( $('.FTTSVGChevronIconRot:eq(0)')[0] ) {
					FTT.testElCollap = event.target;
					while ( FTT.testElCollap ) {
						if ( ( FTT.testElCollap.classList.contains('FTTH2SectContainer') || FTT.testElCollap.classList.contains('FTTH1SectContainer') ) && FTT.testElCollap.querySelectorAll('H1 .FTTSVGChevronIconRot,H2 .FTTSVGChevronIconRot')[0] ) {
							FTT.testElCollap.querySelectorAll('H1 .FTTSVGChevronIconRot,H2 .FTTSVGChevronIconRot')[0].click();
						} else if ( FTT.testElCollap.id == 'mw-content-text' ) {
							break;
						}
						FTT.testElCollap = FTT.testElCollap.parentElement;
					}
				}
				event.target.scrollIntoView(FTT.smoothScroll);
			});
		});
	}
}
FTT.movePreviewAbove = function(P,E) {
	P = FTT.PRMOpened;
	if ( ( FTT.settings.previewAboveFull && (P.type == 'editFullPage' || (P.type == 'heading' && P.type == 'edit') ) ) || ( FTT.settings.previewAboveOther && P.type != 'editFullPage' && ! (P.type == 'heading' && P.type == 'edit') ) ) {
		if ( $('#FTTOverlay')[0] ) {
			E = '#FTTOverlay';
		}
		$(E||'.FTTForm')[0].insertBefore($('#FTTPreviewBox')[0],$('#FTTReplyForm')[0]);
		FTT.previewAbove = 1;
	}
};
FTT.BMainEl = '#FTTUITextInput textarea';
FTT.BTitleEl = '#FTTUITextInputTitle input';
FTT.BSummaEl = '#FTTUITextInputSummary input';
FTT.BAllEl = FTT.BMainEl+','+FTT.BTitleEl+','+FTT.BSummaEl;
FTT.submittedLines = [];
FTT.openReplyForm = function(PRM, trigger, skipVEcheck, ORFint, headerInt, IBInt, IBParamsInt,moreInt) {
//	FTT.debug('openReplyForm, trigger: '+trigger);
	if ( FTT.PRMOpened && FTT.PRMOpened.justSettings ) {
		return;
	}
	FTT.loadSettings();
	FTT.applyModules('beforeOpenForm');
	delete FTT.watched;
	delete FTT.watchlistexpiry;
	delete FTT.sectionNumFromLink;
	delete FTT.ignoreEmptyTitle;
	delete FTT.newSectionLevel;
	delete FTT.killNewReplyCheck;
	if ( FTT.testValidJSON(D2(PRM)) ) {
//		FTT.debug('decode and parse PRM');
		PRM = JSON.parse(D2(PRM.replace(/\%27/g, '\'')));
	}
	FTT.postCommentSuccess = false;
	if ( FTT.settings.warnExit ) {
		window.onbeforeunload = function() { if ( FTT.getValue().length > 30 && ! FTT.skipWarnExit && $('#FTTUITextInput')[0] ) { return ''; } };
	}
//	FTT.debug(PRM);
//	FTT.debug('openReplyForm');
	FTT.switchToEdit = ( FTT.formChanged == false && $('#FTTUITextInput')[0] && PRM.type == 'edit' );
	if ( PRM.type == 'comment' && $('#FTTUITextInput')[0] ) { //there is already a reply form open, add a ping instead
//		FTT.debug('openReplyForm: form already open, adding ping');
		if ( FTT.getValue().slice(FTT.getValue().length - 1, FTT.getValue().length) != " " ) {
			FTT.spaceBeforePing = " ";
		} else {
			FTT.spaceBeforePing = "";
		}
		FTT.userPingText = FTT.createPingText(PRM,'addping',trigger);
		if ( FTT.userPingText && FTT.getValue().match(E1(FTT.userPingText)) ) {
//			FTT.debug('this user has already been mentioned. I\'ll scroll FTT into view so you can see');
			FTT.UITextInput.scrollElementIntoView();
			FTT.UIReplyButton.scrollElementIntoView();
			FTT.focusInput();
		} else if ( FTT.userPingText ) {
//			FTT.debug('insert user ' + FTT.userPingText);
			//FTT.encapsulateContent() is the easier way to do this, but that will always focus the input field and scroll it into view. the focus isn't the issue, the scrolling can be if you are adding multiple names from elsewhere in the discussion
			FTT.cursorPos = FTT.getRange().from;
			FTT.pingLength = FTT.spaceBeforePing.length + FTT.userPingText.length;
			FTT.setValue(FTT.getValue().slice(0, FTT.cursorPos) + FTT.spaceBeforePing + FTT.userPingText + FTT.getValue().slice(FTT.cursorPos));
			FTT.undoSave();
			$(FTT.BMainEl)[0].selectionStart = FTT.cursorPos + FTT.pingLength;
			$(FTT.BMainEl)[0].selectionEnd = FTT.cursorPos + FTT.pingLength;
		}
	} else if ( $('#FTTUITextInput')[0] && PRM.type != 'comment' && PRM.type != 'edit' ) {
//		FTT.debug('openReplyForm: not adding a mention for not-comment icons');
		FTT.UITextInput.scrollElementIntoView();
		FTT.UIReplyButton.scrollElementIntoView();
	} else if ( ! $('#FTTUITextInput')[0] || FTT.switchToEdit ) { //no open form yet or switching to edit form
		if ( FTT.switchToEdit ) {
			FTT.cancelReply();
		}
		if ( ! FTT.userName && FTT.settings.anoneditwarn && !FTT.anonWarnShown ) {
//			FTT.debug('show anoneditwarning');
			mw.loader.using(['oojs-ui-core','oojs-ui-windows']).then(function(){
				FTT.FFDarkMode();
				$('#FTTOverlay').addClass('FTTNoDisplay');
				OO.ui.confirm(new OO.ui.HtmlSnippet(FTT.B1.anoneditwarning),{size:'medium'}).done( function(confirmd) {
					FTT.FFDarkMode(1);
					$('#FTTOverlay').removeClass('FTTNoDisplay');
					if ( confirmd ) {
						FTT.anonWarnShown = 1;
						FTT.openReplyForm(PRM, trigger, ORFint, headerInt, IBInt, IBParamsInt);
					}
				});
			});
			return;
		} else if ( FTT.anonWarnShown == 1 ) {
			FTT.anonWarnShown = 2;
		} else if ( FTT.anonWarnShown == 2 ) {
			FTT.notify(FTT.B1['exception-nologin']);
		}
		if ( !skipVEcheck && ($('html.ve-loading')[0] || ( ! $('html.ve-active')[0] && M2('action') == 'edit' && ( ! $('#wpTextbox1')[0] || $('#wpTextbox1').hasClass('ve-dummyTextbox') ) ) )) {
//			FTT.debug('VE seems to be loading/active');
			FTT.VEOFInt = 0;
			var DelayVEOpenForm = setInterval(function () { // wait for VE to be done loading
				if ( $('html.ve-active')[0] || FTT.VEOFInt > 150 || (!$('html.ve-loading')[0] && FTT.VEOFInt>5)) {
					clearInterval(DelayVEOpenForm);
					if ( FTT.VEOFInt <= 150 ) {
						FTT.openReplyForm(PRM, trigger, 1, 1, ORFint, headerInt, IBInt, IBParamsInt);
					}
				}
				FTT.VEOFInt++;
			},200);
			return;
		}
		FTT.openingFormInProgress = new Date().getTime();
		var DelayedFormOpenCheck = setInterval(function () {
			clearInterval(DelayedFormOpenCheck);
			if ( ! $('#FTTUITextInput')[0] && FTT.openingFormInProgress < new Date().getTime()-FTT.openingFormInProgressDelay+20 && ! FTT.youBastards ) {
				FTT.addScrewedLink('openReplyForm: open form failed','Could not open form.');
				var DelayedNeverMindFormOpenCheck = setInterval(function (int) {
					int++;
					if ( $('#FTTUITextInput')[0] && $('.oo-ui-window-frame #FTTScrewed')[0] && $('.oo-ui-window-frame .oo-ui-buttonElement-button')[0] ) {
//						FTT.debug('openReplyForm: never mind, it was just slow');
						$('.oo-ui-window-frame .oo-ui-buttonElement-button:eq(0)')[0].click();
						mw.notify(FTT.msgs.nevermind);
						clearInterval(DelayedNeverMindFormOpenCheck);
					} else if ( int > 30 ) {
//						FTT.debug('openReplyForm: nope still no form');
						clearInterval(DelayedNeverMindFormOpenCheck);
					}
				},100);
			} else if ( FTT.youBastards ) {
				delete FTT.youBastards;
			}
		},FTT.openingFormInProgressDelay);
		if ( typeof FTT.RETFTypoRegEx == 'undefined' && ( FTT.B1.AWBtyposTitle || FTT.settings.AWBtyposCustomTitle )&& FTT.settings.AWBtypos && ( !FTT.isMobile || !FTT.settings.MFAdjAWBtypos) ) {
			FTT.RETF('','init'); //load AWB's RegExTypoFix now so it'll be ready when the comment is previewed/posted
		}
		mw.loader.using( [ 'oojs-ui-core','oojs-ui-widgets' ] ).then( function () {
		try{
//		FTT.debug('there is no reply form yet, let\'s create one');
		FTT.pickEditor = ( ( FTT.settings.editor == 'lastused' && ['comment','edit','newsection','newheading'].includes(PRM.type) && FTT.getItemLS('FTTLastCmtEditor') ) || (FTT.settings.editor != 'lastused' && FTT.settings.editor) || 'source' );
		FTT.enableLivePreview = ( ( !FTT.isMobile || !FTT.settings.MFAdjlivePreview) && (((FTT.isDiscussionPage && FTT.settings.livePreviewCmt && ['comment','edit','newheading','newsection'].includes(PRM.type) )||((!FTT.isDiscussionPage || !['comment','edit','newheading','newsection'].includes(PRM.type)) && FTT.settings.livePreviewOther))));
		if ( FTT.goNinja && ! FTT.ninjaLoaded ) { //full page edit/new section icons can be available in ninja mode. Actually opening a form and trying to submit generally assumes searchNodeContentsLoop already ran, so.. (I was able to post a new section without loading stuff but encountered a JS error afterwards - too risky)
//			FTT.debug('openReplyForm: ninja mode, load links');
			FTT.loadOnDemand(undefined,true);
		}
		if ( FTT.settings.collapIcons && FTT.settings.collapsible && PRM.pageTitleInt ) {
			FTT.findContainerEl = FTT.processElementArray[PRM.pageTitleInt];
			while ( FTT.findContainerEl && ! FTT.findContainerEl.classList.contains('FTTH1SectContainer') && ! FTT.findContainerEl.classList.contains('FTTH2SectContainer') ) {
				FTT.findContainerEl = FTT.findContainerEl.parentElement;
			}
			if ( FTT.findContainerEl ) {
				FTT.miniChevrons = FTT.findContainerEl.querySelectorAll('.FTTCollapMini');
				for ( FTT.miniChevInt=0;FTT.miniChevInt<FTT.miniChevrons.length;FTT.miniChevInt++) {
					FTT.miniChevrons[FTT.miniChevInt].classList.add('FTTNoDisplay');
				}
			}
		}
		FTT.activeEditor = FTT.pickEditor+'';
		if ( FTT.activeEditor == 'visualLight' && ['editFullPage','heading'].includes(PRM.type) ) {
			FTT.activeEditor = 'source';
			mw.util.addCSS('#FTTliveToggle{display:inline}');
		}
		if ( FTT.settings.floatReturn && ! FTT.floatReturnLink && ! FTT.IECRAP ) {
			FTT.addfloatReturn();
			addEventListener('scroll',FTT.togglefloatReturn);
		}
		FTT.retriedNetworkError = 0;
		delete FTT.moveToDefault;
		if ( ( $('.FTTPurpleBG').length - FTT.purpleBGJoker ) > 0 ) {
//			FTT.debug('openReplyForm: there\'s an element with the FTTPurpleBG class, either an edit that hasn\'t finished yet in which case it\'s advisable to wait, or one that failed in which case it may not be safe to proceed in this state');
			mw.notify(FTT.B1.actionfailed,{type:'error'});
			FTT.openingFormInProgress = new Date().getTime();
			return;
		}
		FTT.PRMOpened = PRM;
		if ( FTT.oTT ) { //if the block status or section move/archive setting was changed in oneTimeTools when the form was opened previously, reset it
			delete FTT.oTT;
		}
		delete FTT.commentTextIndent;//if an indented comment is posted followed by a new (sub)section the indentation of the previous comment shouldn't be reused for the preview
		delete FTT.smartTestNode;//so smartlivepreview doesn't first check a node from a previously opened form
		FTT.lastCheckedForNewComments = 0;
		delete FTT.textbox;
		FTT.oldRevLoaded = 0;
		FTT.smartOldVals = [];
		FTT.smartNewVals = [];
		FTT.redoRevisions = {'UITextInputTitle':[],'UITextInputSummary':[],'main':[]};
		FTT.undoRevisions = {'UITextInputTitle':[],'UITextInputSummary':[],'main':[]};
		FTT.redoCurRev = {};
		FTT.maxUndoRevs = {};
		FTT.reinsertNL = false;
		FTT.forceSign = 0;
		FTT.textInputLive = false;
		FTT.livePreviewDisabled = true;
		FTT.smartViewingDiff = 0;
		FTT.firstPreview = 1;
		FTT.previewAbove = 0;
		FTT.sideBySide = false;
		FTT.replyToWikiLove = false;
		FTT.skipWarnExit = false;
		FTT.FTTFormHTML = document.createElement('div');
		FTT.FTTFormHTML.id = 'FTTForm-' + PRM.id;
		FTT.FTTFormHTML.classList = [ 'FTTNoDisplay FTTForm' ];
		if ( FTT.settings.wrongUI ) {
			FTT.FTTFormHTML.classList.add('FTTOtherSide');
		}
		if ( ['comment','edit'].includes(PRM.type) ) {
			FTT.FTTFormHTML.classList.add('FTTFormComment');
		}
		$('#mw-content-text .FTTFirstReply').addClass('FTTNoDisplay'); //reply-to-section-starter speech balloons can get rather close to the reply form/preview due to their negative margin (which they use to avoid page jumping)
		FTT.FTTFormNegativeMargin = 0;
		FTT.RETFsummary = '';
		if ( PRM.type == 'comment' ) {
//			FTT.debug('create form to add comment');
			FTT.origCmtIndent = FTT.getIndentationFromHTML(PRM.int);
			FTT.cmtIndentHTML = FTT.origCmtIndent;
			FTT.relevantCmtElement = FTT.processElementArray[PRM.int];
			FTT.relevantCmtElementInt = PRM.int;
//			FTT.debug('HTMLindent: find element to insert form below for RLP #' + PRM.int + ' with indentation level ' + FTT.origCmtIndent);
			for(FTT.trueIndentInSectionHTMLInt=PRM.int+1;FTT.trueIndentInSectionHTMLInt<=Number(Object.keys(FTT.PRM)[Object.keys(FTT.PRM).length -1]);FTT.trueIndentInSectionHTMLInt++){
				if ( FTT.PRM[FTT.trueIndentInSectionHTMLInt] && FTT.PRM[FTT.trueIndentInSectionHTMLInt].type == 'newheading' ) {
//					FTT.debug('HTMLindent: ran into next section while looking for form insertion point in HTML');
					break;
				} else if ( FTT.PRM[FTT.trueIndentInSectionHTMLInt] && ! FTT.PRM[FTT.trueIndentInSectionHTMLInt].freshcomment ) {
					FTT.testCmtIndentHTML = FTT.getIndentationFromHTML(Number(FTT.trueIndentInSectionHTMLInt));
					if ( FTT.testCmtIndentHTML <= FTT.origCmtIndent ) {
//						FTT.debug('HTMLindent: indentation of #' + FTT.trueIndentInSectionHTMLInt + ' is ' + FTT.testCmtIndentHTML + ', this is what we were looking for');
						break;
					} else {
						FTT.relevantCmtElement = FTT.processElementArray[Number(FTT.trueIndentInSectionHTMLInt)];
						FTT.relevantCmtElementInt = Number(FTT.trueIndentInSectionHTMLInt);
						FTT.cmtIndentHTML = FTT.testCmtIndentHTML;
//						FTT.debug('HTMLindent: indentation of #' + FTT.trueIndentInSectionHTMLInt + ' is ' + FTT.cmtIndentHTML + ', too high');
					}
				}
			}
//			FTT.debug('HTMLindent: final relevantCmtElement ID: ' + FTT.relevantCmtElementInt);
			FTT.FTTFormNegativeMargin = ( FTT.origCmtIndent - FTT.cmtIndentHTML ) * FTT.indentWidth;
//			FTT.debug('HTMLindent: margin: ' + FTT.FTTFormNegativeMargin + 'em');
			FTT.FTTFormHTML.style = 'margin-' + FTT.CSSDirectionL + ':' + ( FTT.FTTFormNegativeMargin + FTT.indentWidth ) + 'em';
			if ( FTT.settings.HLreply ) {
				FTT.HLCmt(FTT.processElementArray[PRM.int],1);
			}
			if ( FTT.relevantCmtElement.nodeName == 'LI' ) {
				FTT.newLiForForm = document.createElement('li');
				FTT.newLiForForm.classList.add('FTTnewLi');
				FTT.insertAfter(FTT.relevantCmtElement,FTT.newLiForForm);
				FTT.newLiForForm.append(FTT.FTTFormHTML);
			} else {
				FTT.appendToFirstBlockParent(FTT.relevantCmtElement,FTT.FTTFormHTML);
			}
			if ( M1('wgCanonicalNamespace') == 'User_talk' ) {
				FTT.replyToWikiLove = FTT.isWikiloveCmt(FTT.processElementArray[PRM.int]);
			}
		} else if ( PRM.type == 'edit' && document.getElementById('FTTLink-' + FTT.escapeHTML(PRM.id)) ) {
//			FTT.debug('edit type, appending form to:');
//			FTT.debug(document.getElementById('FTTLink-' + FTT.escapeHTML(PRM.id)));
			FTT.appendToFirstBlockParent(document.getElementById('FTTLink-' + FTT.escapeHTML(PRM.id)),FTT.FTTFormHTML);
		} else if ( ( PRM.type == 'newheading' || PRM.type == 'heading' ) && PRM.section != 0 ) {
			FTT.processHeaderElementArray = Array.from($('.FTTH1SectContainer>H1,.FTTH2SectContainer>H2,.FTTH2SectContainer>H3,.FTTH2SectContainer>H4,.FTTH2SectContainer>H5,.FTTH2SectContainer>H6,#mw-content-text .mw-parser-output>H1,#mw-content-text .mw-parser-output>H2,#mw-content-text .mw-parser-output>H3,#mw-content-text .mw-parser-output>H4,#mw-content-text .mw-parser-output>H5,#mw-content-text .mw-parser-output>H6'));
			FTT.findNextHeader = false;
			FTT.nodeNameLevel = FTT.processElementArray[PRM.int].parentElement.nodeName.slice(-1);
			FTT.relevantCmtElement = 0;
			FTT.formAdded = 0;
			for (headerInt = 0; headerInt < FTT.processHeaderElementArray.length; headerInt++) {
				if ( FTT.processHeaderElementArray[headerInt] == FTT.processElementArray[PRM.int].parentElement ) {
//					FTT.debug('(new)heading, ran into the header associated with these PRM. Will use the next header we find to prepend the form so it ends up below the existing section');
					FTT.findNextHeader = true;
				} else if ( FTT.findNextHeader && FTT.processHeaderElementArray[headerInt].nodeName.slice(-1) <= FTT.nodeNameLevel ) {
					if ( FTT.settings.collapsible && (FTT.processElementArray[PRM.int].parentElement.classList.contains('FTTH1SectContainer') || FTT.processElementArray[PRM.int].parentElement.classList.contains('FTTH2SectContainer')) && FTT.processHeaderElementArray[headerInt].parentElement != FTT.processElementArray[PRM.int].parentElement ) {
//						FTT.debug('(new)heading, last section of a collapsible block, append to block');
						FTT.processElementArray[PRM.int].parentElement.append(FTT.FTTFormHTML);
						$('#FTTnSecBottom').addClass('FTTNoDisplay');
						FTT.formAdded = 1;
						break;
					}
					FTT.relevantCmtElement = FTT.processHeaderElementArray[headerInt];
					break;
				}
			}
			if ( FTT.relevantCmtElement ) {
//				FTT.debug('newheading, inserting form in parentElement of this element, before this element:');
//				FTT.debug(FTT.relevantCmtElement);
				FTT.relevantCmtElement.parentElement.insertBefore(FTT.FTTFormHTML,FTT.relevantCmtElement);
			} else if ( !FTT.formAdded ) {
//				FTT.debug('no relevantCmtElement found, appending form to the bottom of the page');
				FTT.newHeadingPrependEl = $('#mw-content-text>.printfooter,#mw-content-text>#FTTnSecBottomDiv')[0];
				if ( FTT.newHeadingPrependEl ) {
//					FTT.debug('insert form before printfooter/FTTnSecBottomDiv');
					$('#mw-content-text')[0].insertBefore(FTT.FTTFormHTML,FTT.newHeadingPrependEl);
				} else {
					$('#mw-content-text').append(FTT.FTTFormHTML);
				}
				$('#FTTnSecBottom').addClass('FTTNoDisplay');
			}
		} else if ( PRM.type == 'FCL' ) {
//			FTT.debug('FCL, appending form to processElementArray #' + PRM.int);
			FTT.appendToFirstBlockParent(FTT.processElementArray[PRM.int],FTT.FTTFormHTML);
		} else if ( PRM.type == 'editFullPage' || ( PRM.type == 'heading' && PRM.section == 0 ) ) {
			if ( FTT.settings.clearEditFullPage ) {
//				FTT.debug('hiding page content, appending for mto #content');
				$('#mw-content-text').addClass('FTTNoDisplay');
				$('#content').append(FTT.FTTFormHTML);
			} else {
//				FTT.debug('appending form to #mw-content-text');
				$('#mw-content-text').prepend(FTT.FTTFormHTML);
			}
		} else if ( PRM.type == 'newsection' ) {
			$('#FTTnSecBottom').addClass('FTTNoDisplay'); //hide new section link at the bottom
			if ( FTT.settings.reverseSectionOrder && $('.FTTH1SectionsContainer')[0] ) {
//				FTT.debug('newsection with section reversal enabled and H1 section found, prepending to:');
//				FTT.debug($('.FTTH1SectionsContainer')[0]);
				$('.FTTH1SectionsContainer')[0].prepend(FTT.FTTFormHTML);
			} else if ( FTT.settings.reverseSectionOrder && $('.FTTH2SectionsContainer')[0] ) {
//				FTT.debug('newsection with section reversal enabled and H2 (but no H1) section found, prepending to:');
//				FTT.debug($('.FTTH2SectionsContainer')[0]);
				$('.FTTH2SectionsContainer')[0].prepend(FTT.FTTFormHTML);
			} else {
//				FTT.debug('newsection, appending to #mw-content-text');
				$('#mw-content-text>div.mw-parser-output,#mw-content-text')[0].append(FTT.FTTFormHTML);
			}
		}
		if ( PRM.origReplyTo != 'NO-VALUE-PLEASE-IGNORE' && FTT.settings.autoPing && PRM.type == "comment" && PRM.origReplyTo != FTT.userName && !( PRM.origReplyTo == M1('wgTitle') && M1('wgNamespaceNumber') == 3 ) ) { //don't ping yourself and don't ping the recipient on their own talk page
//			FTT.debug('preloading auto-mention');
			FTT.preloadText = FTT.createPingText(PRM,'preload',trigger);
		} else {
//			FTT.debug('skipping auto-mention');
			FTT.preloadText = '';
		}
		if ( FTT.UITextInputTitle ) { FTT.preloadTitle = FTT.UITextInputTitle.getValue(); } else { FTT.preloadTitle = ''; }
		if ( FTT.UITextInputSummary ) { FTT.preloadSummary = FTT.UITextInputSummary.getValue(); } else { FTT.preloadSummary = ''; }
		if ( FTT.settings.limitWidth == false ) {
			FTT.FTTUITextInputTitleClass = 'FTTNoMaxWidth';
		} else {
			FTT.FTTUITextInputTitleClass = '';
		}
		if ( PRM.type == 'newheading' ) {
			FTT.titlePlaceholder = FTT.msgs.newHeadingSubj;
		} else {
			FTT.titlePlaceholder = FTT.B1.subject;
		}
		FTT.UITextInputTitle = new OO.ui.TextInputWidget( {
			id: 'FTTUITextInputTitle',
			classes: [ 'FTTMarginHalfEm',FTT.FTTUITextInputTitleClass ],
			value: FTT.preloadTitle,
			placeholder: FTT.titlePlaceholder,
		} );
		if ( PRM.type == 'editFullPage' ) {
			FTT.UITextInputRows = 25;
		} else if ( ['newheading','heading','newsection'].includes(PRM.type) ) {
			FTT.UITextInputRows = 15;
		} else {
			FTT.UITextInputRows = 6;
		}
		FTT.UITextInput = new OO.ui.MultilineTextInputWidget( {
			id: 'FTTUITextInput',
			rows: FTT.UITextInputRows,
			spellcheck: ( M1('wgPageContentModel') == 'wikitext' && FTT.settings.spellcheck ),
			value: FTT.preloadText,
		} );
		FTT.UIVisual = document.createElement('div');
		FTT.UIVisual.className = 'FTTNoDisplay';
		if ( (FTT.activeEditor == 'visualLight' || ( FTT.settings.editorSwitch && ! FTT.settings.editorSwitchSkipvisual ) ) && ! ['editFullPage','heading'].includes(PRM.type) ) {
			FTT.initVisualLight();
		}
		if ( FTT.pickEditor == 'visualLight' && ! ['editFullPage','heading'].includes(PRM.type) ) {
			FTT.UITextInput.toggle(false);
			mw.util.addCSS('#FTTliveToggle{display:none}');
		}
//		if ( FTT.pickEditor == 'visualLight' && FTT.settings.debug ) {FTT.UIVisual.style = 'background:#FBFFFF';FTT.UITextInput.toggle(true);}//FTT.debug
		FTT.checkIfLivePreviewShouldBeRendered = function(oldTextInputValue,oldTextInputTitleValue) {
//			FTT.debug('checkIfLivePreviewShouldBeRendered');
			if ( FTT.livePreviewDisabled ) {
//				FTT.debug('checkIfLivePreviewShouldBeRendered: disabled live preview');
				return;
			}
			if ( FTT.activeEditor == 'visualLight' ) {
//				FTT.debug('checkIfLivePreviewShouldBeRendered: VisualLight is active, skip live preview as this combination causes too many problems (mostly focusnode reset and removal of newlines at the end of the text');
				if ( FTT.livePreviewDisabled == false ) { //there could be a delayed preview that triggers after an interval unaware of this setting
					FTT.livePreviewToggle();
				}
			} else if ( FTT.smartViewingDiff || $('#FTTPreviewBox>.mw-parser-output.diff')[0] ) {
				FTT.smartViewingDiff = 1;
//				FTT.debug('viewing a diff, skip preview');
			} else if ( oldTextInputValue == FTT.getValue() && oldTextInputTitleValue == FTT.UITextInputTitle.getValue() ) {
				FTT.smartViewingDiff = 0;
//				if ( ! FTT.settings.smartLivePreview ) { FTT.debug('checkIfLivePreviewShouldBeRendered: input unchanged, skipping preview'); } 
			} else {
				FTT.smartViewingDiff = 0;
//				FTT.debug('checkIfLivePreviewShouldBeRendered: input has changed, render preview');
				FTT.doPreview('livepreview',PRM);
			}
			FTT.oldTextInputValue = FTT.getValue();
			FTT.oldTextInputTitleValue = FTT.UITextInputTitle.getValue();
			// the delay/interval means that if you type fast you don't send out an API request for every single keystroke.
			if ( FTT.settings.smartLivePreview ) {
				FTT.livePreviewInterval = 600; //with smart live preview we can afford it as it massively reduces parse requests anyway
				if ( FTT.settings.aggressiveLivePreview ) { //can increase server load, bandwidth usage and CPU time to render previews
					FTT.livePreviewInterval = 180; //30%
				}
			} else {
				FTT.livePreviewInterval = 1000;
				if ( FTT.settings.aggressiveLivePreview ) {
					FTT.livePreviewInterval = 300; //30%
				}
			}
			var DelayedPreview = setInterval(function () { // only parse preview once a second
				clearInterval(DelayedPreview);
				if ( FTT.UIReplyButton.isElementAttached() ) {
					FTT.checkIfLivePreviewShouldBeRendered(FTT.oldTextInputValue,FTT.oldTextInputTitleValue);
				}
			}, FTT.livePreviewInterval);
		};
		FTT.smartLivePreview = function() {
			if ( FTT.smartLivePreviewInProgress ) {
//				FTT.debug('smartLivePreview: you either type very fast or have a very slow computer. Postpone smartLivePreview'); 
				FTT.runSmartLivePreview = true;
				return;
			}
			if ( FTT.livePreviewDisabled || ! FTT.UIReplyButton.isElementAttached() ) {
//				FTT.debug('smartLivePreview: disabled live preview or form closed');
				return;
			}
			FTT.smartCurrentInput = FTT.getValue();
			if ( ! FTT.oldTextInputValue ) {
//				FTT.debug('smartLivePreview: no oldTextInputValue, smart preview not possible yet');
				return;
			} else if ( FTT.oldTextInputValue == FTT.smartCurrentInput ) {
//				FTT.debug('smartLivePreview: input seems unchanged since the last preview was rendered');
				return;
			}
			FTT.smartLivePreviewInProgress = true;
			FTT.smartOldTextInputValue = FTT.oldTextInputValue;
			FTT.smartInputLength = FTT.smartCurrentInput.length;
			FTT.smartLiveStartOffset = 0;
//			FTT.smartLiveStartOffsetTime = new Date().getTime(); //FTT.debug
			for (FTT.smartInputInt=1;FTT.smartInputInt<FTT.smartInputLength+1;FTT.smartInputInt++){
				FTT.smartLiveStartOffset = FTT.smartInputInt;
				if ( FTT.smartCurrentInput[FTT.smartInputInt-1] != FTT.smartOldTextInputValue[FTT.smartInputInt-1] ) {
					FTT.smartLiveStartOffset = FTT.smartInputInt -1;
					break;
				}
			}
//			FTT.debug('smartLivePreview: determined offset for unchanged text from the start in '+(new Date().getTime()-FTT.smartLiveStartOffsetTime)+'ms : '+FTT.smartLiveStartOffset);
//			FTT.smartLiveEndOffsetTime = new Date().getTime(); //FTT.debug
			FTT.smartInputLength = FTT.smartCurrentInput.length;
			FTT.smartOldInputLength = FTT.smartOldTextInputValue.length;
			for (FTT.smartInputInt=0;FTT.smartInputInt<FTT.smartInputLength;FTT.smartInputInt++){
				FTT.smartLiveEndOffset = FTT.smartInputInt -1;
				if ( FTT.smartCurrentInput[FTT.smartInputLength-FTT.smartInputInt] != FTT.smartOldTextInputValue[FTT.smartOldInputLength-FTT.smartInputInt] ) {
					FTT.smartLiveEndOffset = FTT.smartInputInt -1;
					break;
				}
			}
//			FTT.debug('smartLivePreview: offset for unchanged text from the end in '+(new Date().getTime()-FTT.smartLiveEndOffsetTime)+'ms : '+FTT.smartLiveEndOffset);
			if ( FTT.smartLiveStartOffset < 1 ) { //zero would cause the slice below to work with -1, starting from the end
				FTT.smartLiveStartOffsetWide = 1;
			} else {
				FTT.smartLiveStartOffsetWide = FTT.smartLiveStartOffset;
			}
			FTT.smartChangedTextWide = FTT.smartCurrentInput.slice(FTT.smartLiveStartOffsetWide-1,(FTT.smartInputLength-FTT.smartLiveEndOffset)+1); //changed text +1 character before and after to find wikitext italic/bold markup
			if ( FTT.smartLiveStartOffsetWide+1 == FTT.smartInputLength ) {
				FTT.smartChangedText = FTT.smartChangedTextWide.slice(1,FTT.smartChangedTextWide.length); //just the changed text
			} else {
				FTT.smartChangedText = FTT.smartChangedTextWide.slice(1,FTT.smartChangedTextWide.length-1);
			}
//			FTT.debug('smartLivePreview: changedtext: "'+FTT.smartChangedText+'", wide: "'+FTT.smartChangedTextWide+'"');
			if ( FTT.smartViewingDiff || FTT.smartChangedText.match(/[<>\{\}\[\]\|\#\*\:"\!\n\/]/) || FTT.smartChangedTextWide.match(/(''|\-\-|==)/) ) { //two single quotes would signify italic/bold wikitext markup
//				FTT.debug('smartLivePreview: viewing diff or special character found in changed text, wait for parsed preview');
				FTT.smartLivePreviewInProgress = false;
				return;
			}
//			FTT.smartGetNodeTime = new Date().getTime(); //FTT.debug
			FTT.smartUpdatedTextNode = false;
			FTT.smartTryNodeResult = false;
			FTT.smartTryGotMatch = 0;
			try{FTT.smartTryNodeResult = FTT.smartTryNode();} catch (e) {} //who knows how smartTryNode could throw JS errors?
			if ( FTT.smartTryNodeResult ) {
//				FTT.debug('smartLivePreview: still the same node, update done');
				FTT.smartPreviewElements = []; //skip the next for loop
			} else {
				FTT.smartPreviewElements = $('#FTTPreviewBox *:not(.FTTCmt,.FTTCmt *,.FTTSigSeparator,.ext-discussiontools-init-replylink-buttons,.ext-discussiontools-init-replylink-bracket,.ext-discussiontools-init-replylink-reply,.ext-discussiontools-init-section-subscribe,.mw-headline),#FTTPreviewBox .mw-headline:not(.mw-headline:eq(0))');
			}
			for (FTT.smartPreviewElementsInt=0;FTT.smartPreviewElementsInt<FTT.smartPreviewElements.length;FTT.smartPreviewElementsInt++){
				for (FTT.smartPreviewTextNodesInt=0;FTT.smartPreviewTextNodesInt<FTT.smartPreviewElements[FTT.smartPreviewElementsInt].childNodes.length;FTT.smartPreviewTextNodesInt++){
					FTT.smartTestNode = FTT.smartPreviewElements[FTT.smartPreviewElementsInt].childNodes[FTT.smartPreviewTextNodesInt];
					try{FTT.smartTryNodeResult = FTT.smartTryNode();} catch (e) {} //who knows how smartTryNode could throw JS errors?
					if ( FTT.smartTryGotMatch ) {
						break;
					}
				}
				if ( FTT.smartTryGotMatch ) {
//					FTT.debug('smartLivePreview: text node located in '+(new Date().getTime()-FTT.smartGetNodeTime)+'ms');
//					if ( FTT.smartUpdatedTextNode ) {FTT.debug('smartLivePreview: text node updated, we\'re done here');}
					break;
				}
			}
			FTT.smartLivePreviewInProgress = false;
			if ( FTT.runSmartLivePreview ) {
				FTT.runSmartLivePreview = false;
				FTT.smartLivePreview();
			}
		};
		FTT.smartTryNode = function(oldData,newData,linkTestInt) {
			if ( !FTT.smartViewingDiff && FTT.smartTestNode.nodeName == '#text' && FTT.smartTestNode.isConnected ) {
				FTT.liveNodeText = FTT.smartTestNode.data.replace(/\n$/,''); //strip newline which appears at the end of the textnode of a P tag when creating a new page, no idea where it came from. Did I insert it? It's not in the unparsed wikitext or parser output.
				FTT.liveNodeRegExp = new RegExp(E1(FTT.liveNodeText));
				FTT.liveNodeRegExpG = new RegExp(E1(FTT.liveNodeText),'g');
				if ( FTT.smartOldTextInputValue.match(FTT.liveNodeRegExp) && FTT.liveNodeText != '' && FTT.smartOldTextInputValue.match(FTT.liveNodeRegExpG).length == 1 ) { //if the content of the text node isn't unique we're not gonna bother. Too much trouble to figure out which match would be the right one.
					FTT.smartOldTextInputValueSplit = FTT.smartOldTextInputValue.split(FTT.liveNodeText);
					if ( FTT.smartOldTextInputValueSplit[0].length <= FTT.smartLiveStartOffset && FTT.smartOldTextInputValueSplit[1].length <= FTT.smartLiveEndOffset ) {
//						FTT.debug('smartLivePreview: changed text is within range of this textnode');
//						FTT.debug(FTT.smartTestNode);
						FTT.smartTryGotMatch = 1;
						FTT.smartNodeStartOffsetOld = FTT.smartOldTextInputValue.split(FTT.liveNodeText)[0].length;
						FTT.smartNodeEndOffsetOld = FTT.smartOldTextInputValue.split(FTT.liveNodeText)[1].length;
						FTT.smartNodeEndOffsetNew = FTT.smartInputLength - FTT.smartNodeEndOffsetOld;
						FTT.smartNodeContentsFromCurrent = FTT.smartCurrentInput.slice(FTT.smartNodeStartOffsetOld,FTT.smartNodeEndOffsetNew);
						FTT.smartOldVals.push(FTT.liveNodeText);
						FTT.smartNewVals.push(FTT.smartNodeContentsFromCurrent);
						oldData = FTT.smartTestNode.data+'';
						FTT.smartTestNode.data = FTT.smartTestNode.data.replace(FTT.liveNodeText,FTT.smartNodeContentsFromCurrent);
						if ( oldData == FTT.smartTestNode.data ) {
//							FTT.debug('smartLivePreview: replacement matched nothing?');
							return false;
						}
						FTT.smartTestNodeLinkTest = FTT.smartTestNode.parentElement;
						for(linkTestInt=0;linkTestInt<5;linkTestInt++) {
							if ( FTT.smartTestNodeLinkTest.nodeName == 'A' ) {
//								FTT.debug('smartLivePreview: textnode was part of a link, the link name may have been updated, but the link target may also be affected. reparsing');
								FTT.queuePreview(new Date().getTime());
								return false;
							}
							FTT.smartTestNodeLinkTest = FTT.smartTestNodeLinkTest.parentElement;
						}
						FTT.oldTextInputValue = FTT.smartCurrentInput;
						FTT.smartUpdatedTextNode = true;
						return true;
					}
				}
			}
			return false;
		};
		FTT.queuePreview = function(time,int) {
			FTT.queuePreviewTime = time;
			var DelayPrevQueue = setInterval(function () {
				if ( FTT.queuePreviewTime != time ) { //this function was re-called within a second, the last instance can have it
					clearInterval(DelayPrevQueue);
				} else if ( int > 15 ) { //has been running for 15 seconds, give up
					clearInterval(DelayPrevQueue);
					FTT.queuePreviewTime = 0;
				} else if ( !FTT.doPreviewInProgress ) {
					clearInterval(DelayPrevQueue);
					FTT.queuePreviewTime = 0;
					FTT.doPreview('preview',PRM);
				}
				int++;
			},1000);
		};
		if ( FTT.settings.saveDraft && PRM.type != 'edit' ) {
			FTT.saveDraftEvery();
		}
		FTT.buttonOnTheWrongSideClass = '';
		if ( !FTT.settings.wrongUI ) {
			FTT.buttonOnTheWrongSideClass = 'FTTFloatRight';
		}
		if ( ( M1('wgCurRevisionId') != M1('wgRevisionId') ) && PRM.type == 'editFullPage' && M1('wgRevisionId') ) {
			$(document.getElementById('FTTForm-' + FTT.escapeHTML(PRM.id))).append('<div class="FTTWarning">' + FTT.B1.editingold + '</div>');
		}
		FTT.UIReplyButton = new OO.ui.ButtonWidget( {
			id: 'FTTUIReplyButton',
			label: FTT.B1['htmlform-submit'],
			flags: ['progressive'],
			classes: [ FTT.buttonOnTheWrongSideClass ]
		} );
		if ( FTT.userNameUnderscore ) {
			FTT.UIReplyButton.setFlags('primary');
		}
		FTT.UIReplyButton.on('click', function() { FTT.postReply1(PRM,'button'); });
		FTT.UIPreviewButton = new OO.ui.ButtonWidget( {
			id: 'FTTUIPreviewButton',
			label: FTT.B1.preview,
			flags: [ 'primary' ],
			classes: [ FTT.buttonOnTheWrongSideClass ]
		} );
		FTT.UIPreviewButton.on('click', function() { FTT.doPreview('preview',PRM); });
		FTT.UIDiffButton = new OO.ui.ButtonWidget( {
			id: 'FTTUIDiffButton',
			label: FTT.B1.showdiff,
			flags: [ 'primary' ],
			classes: [ FTT.buttonOnTheWrongSideClass ]
		} );
		FTT.UIDiffButton.on('click', function() { FTT.doDiff(PRM); });
		FTT.UIDiffButton.toggle(false);
		FTT.UICancelButton = new OO.ui.ButtonWidget( {
			id: 'FTTUICancelButton',
			label: FTT.B1.cancel,
			classes: [ FTT.buttonOnTheWrongSideClass,'FTTCancelBtn' ],
			flags: [ FTT.cancelFlag ]
		} );
		FTT.UIdiffSize = new OO.ui.LabelWidget( {
			classes: ['FTTDiffSize',FTT.buttonOnTheWrongSideClass]
		} );
		FTT.UIdiffSize.toggle(0);
		FTT.UISettingsButton = new OO.ui.ButtonWidget( {
			id: 'FTTUISettingsButton',
			label: new OO.ui.HtmlSnippet(FTT.svgFTTIconSettings),
			title: FTT.B1.preferences,
			classes: ['mw-no-invert'],
			framed: false,
		} );
		FTT.UISettingsButton.$button.attr('aria-label',FTT.B1.preferences);
		if ( FTT.activeEditor == 'visualLight' ) {
			FTT.switchEditorClass = 'FTTHalfOpacity';
		} else {
			FTT.switchEditorClass = '';
		}
		FTT.UISwitchEditorButton = new OO.ui.ButtonWidget( {
			id: 'FTTUISwitchEditorButton',
			label: '</>',
			title: FTT.msgs.editorSwitchTitle,
			framed: false,
			classes: ['FTTEditorSwitch',FTT.switchEditorClass]
		} );
		FTT.UISwitchEditorButton.$button.attr('aria-label',FTT.msgs.editorSwitchTitle);
		FTT.UISwitchEditorButton.on('click', function() { FTT.toggleEditor(); } );
		if ( FTT.settings.editorSwitch == false ) {
			FTT.UISwitchEditorButton.toggle(false);
		}
		FTT.UICancelButton.on('click', function() { FTT.cancelReply('user'); } );
		FTT.UIUndoButton = new OO.ui.ButtonWidget( {
			label:new OO.ui.HtmlSnippet('<span class="FTTSVG FTTSVGRedo FTTSVGUndo FTTHalfOpacity"></span>'),
			title: FTT.B1.editundo,
			framed: false,
		} );
		FTT.UIUndoButton.$button.attr('aria-label',FTT.B1.editundo);
		FTT.UIUndoButton.on('click',function(){FTT.undo();});
		FTT.UIUndoButton.setDisabled(true);
		FTT.UIRedoButton = new OO.ui.ButtonWidget( {
			label:new OO.ui.HtmlSnippet('<span class="FTTSVG FTTSVGRedo FTTHalfOpacity"></span>'),
			title: FTT.msgs.redo,
			framed: false,
		} );
		FTT.UIRedoButton.$button.attr('aria-label',FTT.msgs.redo);
		FTT.UIRedoButton.on('click',function(){FTT.redo();});
		FTT.UIRedoButton.setDisabled(true);
		FTT.UIMarkupBoldButton = new OO.ui.MenuOptionWidget( {
			data: 'bold',
			id: 'FTTUIMarkupBoldButton',
			label: FTT.B1.bold,
			title: FTT.B1['wikieditor-toolbar-tool-bold'],
			classes: ['FTTMarkupBold']
		} );
		FTT.UIMarkupBoldButton.$label.attr('aria-label',FTT.B1['wikieditor-toolbar-tool-bold']);
		FTT.UIMarkupItalicButton = new OO.ui.MenuOptionWidget( {
			data: 'italic',
			id: 'FTTUIMarkupItalicButton',
			label: FTT.B1.italic,
			title: FTT.B1['wikieditor-toolbar-tool-italic'],
			classes: ['FTTMarkupItalic']
		} );
		FTT.UIMarkupItalicButton.$label.attr('aria-label',FTT.B1['wikieditor-toolbar-tool-italic']);
		FTT.UIMarkupStrikeButton = new OO.ui.MenuOptionWidget( {
			data: 'struck',
			id: 'FTTUIMarkupStrikeButton',
			label: FTT.B1.strike,
			title: FTT.msgs.strikeThrough,
			classes: ['FTTMarkupStrike']
		} );
		FTT.UIMarkupStrikeButton.$label.attr('aria-label',FTT.msgs.strikeThrough);
		FTT.UIMarkupMoreButton = new OO.ui.MenuOptionWidget( {
			data: 'more',
			label: FTT.B1.moredotdotdot,
		} );
		FTT.UIMarkupSuperScrButton = new OO.ui.MenuOptionWidget( {
			data: 'superscript',
			label: FTT.B1['wikieditor-toolbar-tool-superscript'],
			title: FTT.B1['wikieditor-toolbar-tool-superscript']
		} );
		FTT.UIMarkupSuperScrButton.$label.attr('aria-label',FTT.B1['wikieditor-toolbar-tool-superscript']);
		FTT.UIMarkupSubScrButton = new OO.ui.MenuOptionWidget( {
			data: 'subscript',
			label: FTT.B1['wikieditor-toolbar-tool-subscript'],
			title: FTT.B1['wikieditor-toolbar-tool-subscript']
		} );
		FTT.UIMarkupSubScrButton.$label.attr('aria-label',FTT.B1['wikieditor-toolbar-tool-superscript']);
		FTT.UIMarkupSmallButton = new OO.ui.MenuOptionWidget( {
			data: 'small',
			label: FTT.B1['wikieditor-toolbar-tool-small-example'],
			title: FTT.B1['wikieditor-toolbar-tool-small-example']
		} );
		FTT.UIMarkupSmallButton.$label.attr('aria-label',FTT.B1['wikieditor-toolbar-tool-small-example']);
		FTT.UIMarkupBigButton = new OO.ui.MenuOptionWidget( {
			data: 'big',
			label: FTT.B1['wikieditor-toolbar-tool-big-example'],
			title: FTT.B1['wikieditor-toolbar-tool-big-example']
		} );
		FTT.UIMarkupBigButton.$label.attr('aria-label',FTT.B1['wikieditor-toolbar-tool-big-example']);
		FTT.UIMarkupNowikiButton = new OO.ui.MenuOptionWidget( {
			data: 'nowiki',
			label: FTT.B1['wikieditor-toolbar-tool-nowiki'],
			title: FTT.B1['wikieditor-toolbar-tool-nowiki']
		} );
		FTT.UIMarkupNowikiButton.$label.attr('aria-label',FTT.B1['wikieditor-toolbar-tool-nowiki']);
		FTT.UIMarkupCodeButton = new OO.ui.MenuOptionWidget( {
			data: 'code',
			label: FTT.msgs.code,
			title: FTT.msgs.code
		} );
		FTT.UIMarkupCodeButton.$label.attr('aria-label',FTT.msgs.code);
		FTT.UIMarkupUnderlineButton = new OO.ui.MenuOptionWidget( {
			data: 'underline',
			label: FTT.msgs.underline,
			title: FTT.msgs.underline
		} );
		FTT.UIMarkupUnderlineButton.$label.attr('aria-label',FTT.msgs.underline);
		FTT.markUpDropDown = new OO.ui.ButtonMenuSelectWidget({
			id: 'FTTUIMarkUpDropDown',
			label: new OO.ui.HtmlSnippet('<span id="FTTUIMarkUpDropDownLabel">'+FTT.B1.bold+'</span>'),
			framed: false,
			title: FTT.B1['wikieditor-toolbar-help-page-format'],
			menu: {
				items: [FTT.UIMarkupBoldButton,FTT.UIMarkupItalicButton,FTT.UIMarkupStrikeButton,FTT.UIMarkupMoreButton,FTT.UIMarkupSuperScrButton,FTT.UIMarkupSubScrButton,FTT.UIMarkupSmallButton,FTT.UIMarkupBigButton,FTT.UIMarkupNowikiButton,FTT.UIMarkupCodeButton,FTT.UIMarkupUnderlineButton]
			}
		});
		FTT.markUpDropDown.$label.attr('aria-label',FTT.B1['wikieditor-toolbar-help-page-format']);
		FTT.moreMenu=['UIMarkupSuperScrButton','UIMarkupSubScrButton','UIMarkupSmallButton','UIMarkupBigButton','UIMarkupNowikiButton','UIMarkupCodeButton','UIMarkupUnderlineButton'];
		for(moreInt=0;moreInt<FTT.moreMenu.length;moreInt++){
			FTT[FTT.moreMenu[moreInt]].toggle(false);
		}
		FTT.markUpDropDown.getMenu().on( 'choose', function ( option,more,int ) {
			if ( option.data == 'more' ) {
				for(int=0;int<FTT.moreMenu.length;int++){
					FTT[FTT.moreMenu[int]].toggle(true);
				}
				var DelayMoreMenu = setInterval(function () {
					clearInterval(DelayMoreMenu);
					FTT.markUpDropDown.getMenu().toggle(true);
				},10);
				FTT.UIMarkupMoreButton.toggle(false);
			} else {
				FTT.insertMarkup(option.data,'','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
			}
		});
		FTT.UIMarkupLinkButton = new OO.ui.ButtonWidget( {
			id: 'FTTUIMarkupLinkButton',
			label: new OO.ui.HtmlSnippet(FTT.svgFTTIconLinkBlack),
			title:FTT.B1['wikieditor-toolbar-tool-link-title'],
			framed: false,
			classes: ['mw-no-invert']
		} );
		FTT.UIMarkupLinkButton.$button.attr('aria-label',FTT.B1['wikieditor-toolbar-tool-link-title']);
		FTT.UIoneTimeToolsButton = new OO.ui.ButtonWidget( {
			label: new OO.ui.HtmlSnippet(FTT.svgMagnifier),
			title: FTT.B1['prefs-advancedsearchoptions'],
			framed: false,
			classes: ['mw-no-invert']
		} );
		FTT.UIoneTimeToolsButton.$button.attr('aria-label',FTT.B1['prefs-advancedsearchoptions']);
		FTT.UIeditNoticeButton = new OO.ui.ButtonWidget( {
			label: new OO.ui.HtmlSnippet(FTT.svgFTTWarning),
			title: FTT.msgs.viewEditNotice,
			framed: false,
			classes: ['mw-no-invert']
		} );
		FTT.UIeditNoticeButton.$button.attr('aria-label',FTT.msgs.viewEditNotice);
		FTT.UIeditNoticeButton.on('click', function() { FTT.enom.showPopup(); } );
		FTT.UIeditNoticeButton.toggle(false);
		FTT.UIMarkupLinkButton.on('click', function() { FTT.insertLink(FTT.focusNode, FTT.focusOffset, FTT.anchorNode, FTT.anchorOffset); } );
		FTT.UIoneTimeToolsButton.on('click', function() { FTT.oneTimeToolsOpen(); } );
		if ( ! FTT.settings.onetimetools ) {
			FTT.UIoneTimeToolsButton.toggle(false);
		}
		FTT.replyFormElements = [];
		FTT.MarkupButtonBarArr = [FTT.UIUndoButton,FTT.UIRedoButton,FTT.markUpDropDown,FTT.UIMarkupLinkButton];
		if ( FTT.settings.pingDropDown || FTT.settings.pingDropDownAt ) {
//			FTT.debug('add ping dropdown');
			FTT.commentersInThisSection = FTT.commentersInSectionBySection[FTT.PRMOpened.pageTitleInt];
			FTT.cITSmenuItemsObj = {};
			FTT.cITSmenuItems = [];
			if ( typeof FTT.commentersInThisSection != 'undefined' ) {
				for (FTT.cITSint = 0; FTT.cITSint < FTT.commentersInThisSection.length; FTT.cITSint++) {
					if ( FTT.commentersInThisSection[FTT.cITSint] != FTT.userName ) {
						FTT.cITSmenuItemsObj[FTT.cITSint] = new OO.ui.MenuOptionWidget( { data:FTT.commentersInThisSection[FTT.cITSint],label:FTT.commentersInThisSection[FTT.cITSint] } );
						FTT.cITSmenuItems.push(FTT.cITSmenuItemsObj[FTT.cITSint]);
					}
				}
				delete FTT.cITSint;
			}
			if ( FTT.isDiscussionPage && FTT.settings.pingDropDown ) {
				FTT.cITSbuttonMenu = new OO.ui.ButtonWidget( {
					id:'FTTcITSbuttonMenu',
					framed: false,
					label: FTT.msgs.cITSbuttonMenu,
				});
				FTT.MarkupButtonBarArr.push(FTT.cITSbuttonMenu);
				FTT.cITSbuttonMenu.on('click',function(){FTT.insertLink(FTT.focusNode, FTT.focusOffset, FTT.anchorNode, FTT.anchorOffset,'ping');});
			}
		}
		if ( ! FTT.settings.markup ) {
			FTT.markUpDropDown.toggle(false);
		}
		if ( ! FTT.settings.markupLink ) {
			FTT.UIMarkupLinkButton.toggle(false);
		}
		FTT.MarkupButtonBar = new OO.ui.ButtonGroupWidget( {
			items: FTT.MarkupButtonBarArr,
			id: 'FTTMarkupButtonBar',
			classes: ['FTTLeftRightMargin']
		} );
		FTT.ButtonBarRightItems = [
			FTT.UIoneTimeToolsButton,
			FTT.UIeditNoticeButton,
			FTT.UISwitchEditorButton,
			FTT.UISettingsButton,
		];
		FTT.ButtonBarRight = new OO.ui.ButtonGroupWidget( {
			id: 'FTTButtonBarRight',
			items: FTT.ButtonBarRightItems
		} );
		if ( FTT.settings.wrongUI ) {
			FTT.ButtonBarItems = [
					FTT.UIReplyButton,
					FTT.UIPreviewButton,
					FTT.UICancelButton,
					FTT.UIDiffButton,
					FTT.UIdiffSize,
					FTT.MarkupButtonBar,
					FTT.ButtonBarRight
				];
		} else {
			FTT.ButtonBarItems = [
					FTT.UIReplyButton,
					FTT.UIPreviewButton,
					FTT.UICancelButton,
					FTT.UIDiffButton,
					FTT.UIdiffSize,
					FTT.MarkupButtonBar,
					FTT.ButtonBarRight
				];
		}
		if ( M1('wgCurRevisionId') && (PRM.type == 'editFullPage' || PRM.type == 'edit' || PRM.type == 'heading' ) ) {
			FTT.UIDiffButton.toggle(true);
		}
		FTT.applyModules('beforeButtonBar');
		FTT.ButtonBar = new OO.ui.HorizontalLayout( {
			items: FTT.ButtonBarItems,
			id: 'FTTMainButtonBar',
		} );
		FTT.summaryMaxLengthSectionTitle = 0;
		if ( PRM.sectionTitle ) {
			FTT.summaryMaxLengthSectionTitle = PRM.sectionTitle.length + 6;
		}
		FTT.summaryMaxLength = 500 - FTT.wikiMsgs.summaryCredit.length - FTT.summaryMaxLengthSectionTitle - 200;//on Wikimedia up to 500 seems to be allowed. The 200 is for stuff that's difficult to predict at this point like RETF, sumsnippet but also the templated summaries. It might not always be enough still.
		FTT.UITextInputSummary = new OO.ui.TextInputWidget( {
			id: 'FTTUITextInputSummary',
			type: 'text',
			classes: [ 'FTTMarginHalfEm',FTT.FTTUITextInputTitleClass ],
			value: FTT.preloadSummary,
			autocomplete: true,
			maxLength:FTT.summaryMaxLength,
			placeholder: FTT.B1['tooltip-summary'],
		} );
		FTT.UIMinorCheck = new OO.ui.CheckboxInputWidget( {
			title:FTT.B1['tooltip-minoredit']
		} );
		FTT.UITextInputSummaryMinorLabel = new OO.ui.LabelWidget( {
			label: new OO.ui.HtmlSnippet('<b>' + FTT.B1.minoreditletter + '</b>'),
			title:FTT.B1['tooltip-minoredit']
		} );
		FTT.UITextInputSummaryMinorLayout = new OO.ui.HorizontalLayout( {
			items: [
				FTT.UITextInputSummary,
				FTT.UIMinorCheck,
				FTT.UITextInputSummaryMinorLabel
			],
			classes:['FTTFlexLayout'],
			id:'FTTSummaHoriLayout'
		} );
		FTT.UITextInputSummaryMinorLayout.toggle(false);
		FTT.replyFormElements.push(FTT.UITextInputTitle,FTT.UITextInput,FTT.UITextInputSummaryMinorLayout);
		if ( FTT.settings.customSummary || PRM.type == 'editFullPage' || PRM.type == 'heading' ) {
			FTT.UITextInputSummaryMinorLayout.toggle(true);
			if ( PRM.highlightRef ) { //highlightRef is always of the editFullPage type
				FTT.UITextInputSummary.setValue(FTT.B1['cite-wikieditor-help-page-references']);
			}
			FTT.summaryVisible = true;
		} else {
			FTT.summaryVisible = false;
		}
		FTT.replyFormElements.push(FTT.ButtonBar);
		FTT.ReplyForm = new OO.ui.FormLayout( {
			items: FTT.replyFormElements,
			id: 'FTTReplyForm',
			classes: ['FTTReplyForm'],
		} );
		$(document.getElementById('FTTForm-' + PRM.id)).removeClass('FTTNoDisplay');
		$(document.getElementById('FTTForm-' + PRM.id)).append(FTT.ReplyForm.$element);
		$(FTT.BTitleEl).on('blur',function(){FTT.lastBlurTitle = new Date().getTime();});
		$(FTT.BSummaEl).on('blur',function(){FTT.lastBlurSummary = new Date().getTime();});
		if ( FTT.settings.pingDropDownAt ) {
			FTT.showPingDropDown = function(mode) {
				if ( mode != 'source' || FTT.isDiscussionPage ) {
					FTT.insertLink(FTT.focusNode, FTT.focusOffset, FTT.anchorNode, FTT.anchorOffset,'ping');
				}
			};
			$(FTT.BMainEl).on('input',function(event){
				if ( event.data == '@' || (event.originalEvent && event.originalEvent.data == '@' ) ) {
					FTT.showPingDropDown('source');
				}
			});
			$(FTT.BSummaEl).on('input',function(event){
				if ( event.data == '@' || (event.originalEvent && event.originalEvent.data == '@' ) ) {
					FTT.showPingDropDown('summary');
				}
			});
		}
		if ( FTT.settings.undoFunc ) {
			FTT.undoAutoSave(new Date().getTime());
			FTT.undoSave();
			FTT.UITextInput.$input.on('cut paste',function(){FTT.undoSave();});
		}
		$('#FTTUIMarkUpDropDown,#FTTUIMarkUpDropDown>div').on('mouseenter mouseleave',function(event){
			var DelayMarkupMenu = setInterval(function () {
				clearInterval(DelayMarkupMenu);
				if ($('#FTTUIMarkUpDropDown>a:hover')[0] && event.handleObj.origType == 'mouseenter'){
					FTT.markUpDropDown.getMenu().toggle(true);
				} else if ( !$('#FTTUIMarkUpDropDown:hover,#FTTUIMarkUpDropDown>div:hover,#FTTUIMarkUpDropDown>a:focus')[0] && event.handleObj.origType == 'mouseleave'){
					FTT.markUpDropDown.getMenu().toggle(false);
				}
			},300);
		});
		FTT.UIUndoButton.toggle(FTT.settings.undoFunc && FTT.settings.undoBtn && (!FTT.isMobile||!FTT.settings.MFAdjundoBtn) );
		FTT.UIRedoButton.toggle(FTT.settings.undoFunc && FTT.settings.redoBtn && (!FTT.isMobile||!FTT.settings.MFAdjredoBtn) );
		FTT.insertFromDrop = function(e) {
//			FTT.debug('inputType: '+e.inputType);
//			FTT.debug('data: '+e.data);
//			FTT.debug(e);
			if ( e.inputType == 'insertFromDrop' ) {
				FTT.dropQuote = ( e.data || FTT.insertFromDropData );
				if ( FTT.B1.tq != '' ) {
					FTT.quoteSelect = '{{'+FTT.B1.tq+'|1='+FTT.dropQuote+'}} ';
				} else {
					FTT.quoteSelect = FTT.wikiMsgs.quoteOpen+FTT.dropQuote+FTT.wikiMsgs.quoteClose+' ';
				}
				FTT.newTextWithDragQuote = FTT.getValue().replace(new RegExp(E1(FTT.quoteSelect),'g'),'ALREADYQUOTED'+FTT.semiRandom);
				FTT.newTextWithDragQuote = FTT.newTextWithDragQuote.replace(FTT.dropQuote,FTT.quoteSelect).replace(new RegExp('ALREADYQUOTED'+FTT.semiRandom,'g'),FTT.escapeReplacement(FTT.quoteSelect));
				FTT.setValue(FTT.newTextWithDragQuote);
				FTT.undoSave();
			}
		};
		$('#FTTUITextInput')[0].addEventListener('input',FTT.insertFromDrop);
		$('#FTTUITextInput')[0].addEventListener('textInput',function(e){FTT.insertFromDropData = e.data;}); //on Chrome, first a textInput event fires with data followed by an insertFromDrop WITHOUT data
		if ( (FTT.settings.markupAbove && window.innerWidth > FTT.settings.overlayThreshold) || (FTT.settings.MFmarkupAbove && window.innerWidth <= FTT.settings.overlayThreshold) ) {
			$('#FTTReplyForm')[0].insertBefore($('#FTTMarkupButtonBar')[0],$('#FTTUITextInputTitle')[0]);
			$('#FTTMarkupButtonBar').removeClass('FTTLeftRightMargin');
		}
		$(FTT.BSummaEl)[0].id = 'wpSummary'; //allows sharing autocomplete info with the 2010 wikitext editor. could result in having two elements with this ID when invoking FTT from action=edit, but as FTT obscures the existing 2010 wikitext editor in that case it shouldn't cause any problems
		if ( PRM.type == 'comment' ) {
			if ( FTT.settings.swapIcons ) {
				$('.FTTCmtLink.FTTSVGIcon').addClass('FTTSVGPingIcon').removeClass('FTTSVGIcon'); //swap speech bubbles for speech bubbles with arrows to indicate the changed function
			}
			if ( ! FTT.settings.RLmasq && FTT.settings.dateLinksIcon && FTT.settings.dateLinksIconAlt && FTT.settings.swapIcons ) { //swap link icons for links icons with arrow
				FTT.foundPageIntID = false;
				for (FTT.cmtParamsInt=0;FTT.cmtParamsInt<Object.keys(FTT.PRM).length;FTT.cmtParamsInt++) {
					if ( FTT.foundPageIntID ) {
						if ( FTT.PRM[Object.keys(FTT.PRM)[FTT.cmtParamsInt]].type == 'comment' ) {
//							FTT.debug('swapLinkIcon: swap out link icon for element '+Object.keys(FTT.PRM)[FTT.cmtParamsInt]);
							try {FTT.getFTTIcon = FTT.processElementArray[Object.keys(FTT.PRM)[FTT.cmtParamsInt]].querySelectorAll('.FTTSVGLinkIcon')[0].classList;} catch (e) {delete FTT.getFTTIcon;}
							if ( FTT.getFTTIcon ) {
								FTT.getFTTIcon.add('FTTSVGLinkIconArrow');
								FTT.getFTTIcon.remove('FTTSVGLinkIcon');
							}
						} else {
//							FTT.debug('swapLinkIcon: found the pageIntID element, processed whatever link icons there were, found a non-comment item');
							break;
						}
					}
					if ( ! FTT.foundPageIntID && FTT.PRM[Object.keys(FTT.PRM)[FTT.cmtParamsInt]].int == PRM.pageTitleInt ) {
//						FTT.debug('swapLinkIcon: found PRM #'+PRM.pageTitleInt);
						FTT.foundPageIntID = true;
					}
				}
			}
		}
		if ( PRM.type == 'edit' ) {
			$('#FTTUITextInput').addClass('FTTEditIndicator');
		}
		FTT.setCustomBackground();
		FTT.formChanged = false;
		FTT.formChangedReport = function(e) {
			if ( ! [8,9,13,16,17,18,27,32].includes(e.which) ) { //not backspace/tab/enter/shift/ctrl/alt/escape/space
				FTT.formChanged = true;
				try{$('#FTTUITextInput')[0].removeEventListener('keydown',FTT.formChangedReport);} catch (ev) {}
			}
		};
		$('#FTTUITextInput')[0].addEventListener('keydown',FTT.formChangedReport);
 		if ( FTT.settings.shortcuts ) {
			FTT.postAsMinor = function(PRM) {
				FTT.UIMinorCheck.setSelected(true);
				FTT.postReply1(PRM,'button');
			};
			if ( FTT.settings.submitShortcut ) {
				$('#FTTUITextInputSummary').keydown(function(event){
				if ( ! FTT.UIReplyButton.isDisabled() && ( event.ctrlKey && event.shiftKey && event.which == 13 ) ) {//pressed ctrl+shift+enter on summary field
						event.preventDefault();
						FTT.postAsMinor(PRM);
					}
				});
			}
			$('#FTTUITextInputTitle').keydown(function(event){ //because trying to submit with an empty title will focus the title field, so pressing ctrl+enter twice works this way
				if ( ! FTT.UIReplyButton.isDisabled() && ( ( event.ctrlKey && event.which == 13 && FTT.settings.submitShortcut ) || ( event.altKey && event.shiftKey && event.which == 83 ) ) ) {//pressed ctrl+enter or alt+shift+s
					event.preventDefault();
					FTT.postReply1(PRM,'button');
				}
			});
			$('#FTTUITextInput').keydown(function(event){
				FTT.fullPageOrSectionEdit = ( FTT.PRMOpened.type == 'editFullPage' || ( FTT.PRMOpened.type == 'heading' && FTT.PRMOpened.subtype == 'edit') );
				if ( event.ctrlKey && event.altKey && event.key == 'm' ) {//pressed ctrl+alt+m (77)
					event.preventDefault();
					if ( $('#FTTUITextInput').hasClass('FTTMonoSpace') ) {
						$('#FTTUITextInput').removeClass('FTTMonoSpace mw-editfont-monospace');
					} else {
						$('#FTTUITextInput').addClass('FTTMonoSpace mw-editfont-monospace');
					}
				} else if ( ! FTT.UIReplyButton.isDisabled() && ( event.ctrlKey && event.shiftKey && event.which == 13 && FTT.settings.submitShortcut ) ) {//pressed ctrl+shift+enter
					event.preventDefault();
					FTT.postAsMinor(PRM);
				} else if ( ! FTT.UIReplyButton.isDisabled() && ( ( event.ctrlKey && event.which == 13 && FTT.settings.submitShortcut ) || ( event.altKey && event.shiftKey && event.which == 83 ) ) ) {//pressed ctrl+enter or alt+shift+s
					event.preventDefault();
					FTT.postReply1(PRM,'button');
				} else if ( event.ctrlKey && event.key == 'k' ) {//pressed k (75). what follows are keyboard shortcuts that also exist in VE
					event.preventDefault();
					FTT.insertLink(FTT.focusNode, FTT.focusOffset, FTT.anchorNode, FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == 'b' ) {//pressed b (66)
					event.preventDefault();
					FTT.insertMarkup('bold','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == 'i' ) {//pressed i (73)
					event.preventDefault();
					FTT.insertMarkup('italic','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == '1' && FTT.fullPageOrSectionEdit ) {//pressed 1 (49)
					event.preventDefault();
					FTT.insertMarkup('h1','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == '2' && FTT.fullPageOrSectionEdit ) {//pressed 2 (50)
					event.preventDefault();
					FTT.insertMarkup('h2','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == '3' && FTT.fullPageOrSectionEdit ) {//pressed 3 (51)
					event.preventDefault();
					FTT.insertMarkup('h3','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == '4' && FTT.fullPageOrSectionEdit ) {//pressed 4 (52)
					event.preventDefault();
					FTT.insertMarkup('h4','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == '5' && FTT.fullPageOrSectionEdit ) {//pressed 5 (53)
					event.preventDefault();
					FTT.insertMarkup('h5','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == '6' && FTT.fullPageOrSectionEdit ) {//pressed 6 (54)
					event.preventDefault();
					FTT.insertMarkup('h6','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == '8' ) {//pressed 8 (56)
					event.preventDefault();
					FTT.insertMarkup('blockquote','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.altKey && event.shiftKey && event.key == 'v' && FTT.fullPageOrSectionEdit ) {//pressed v (86)
					event.preventDefault();
					FTT.doDiff(FTT.PRMOpened);
				} else if ( event.altKey && event.shiftKey && event.key == 'i' ) {//pressed i (73)
					FTT.UITextInputSummaryMinorLayout.toggle(true);
					FTT.UIMinorCheck.setSelected(!FTT.UIMinorCheck.isSelected());
				} else if ( event.ctrlKey && event.shiftKey && event.key == '5' ) {//pressed 5 (53)
					event.preventDefault();
					FTT.insertMarkup('struck','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.shiftKey && event.key == '6' ) {//pressed 6 (54)
					event.preventDefault();
					FTT.insertMarkup('code','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == 'u' ) {//pressed u (85)
					event.preventDefault();
					FTT.insertMarkup('underline','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == ',' ) {//pressed , (188)
					event.preventDefault();
					FTT.insertMarkup('subscript','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.key == '.' ) {//pressed . (190)
					event.preventDefault();
					FTT.insertMarkup('superscript','','','',FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
				} else if ( event.ctrlKey && event.altKey && event.key == 'f' ) {//pressed f (70)
					event.preventDefault();
					FTT.oneTimeToolsOpen();
				}
			});
		}
		if (['javascript','css','json'].includes(M1('wgPageContentModel')) || FTT.settings.monospace){
			$('#FTTUITextInput').addClass('FTTMonoSpace mw-editfont-monospace');
		}
		FTT.editSummaryPipeTrick();
		FTT.UITextInputSummary.on('enter', function() { FTT.UITextInputSummary.blur();FTT.postReply1(PRM,'summarybutton'); });
		if ( FTT.settings.bracketToForm || FTT.settings.bracketToFormT ) {
			FTT.bracketToFormFunc = function(event,type) {
//				FTT.debug('bracketToForm: last pressed key: '+FTT.lastPressedKey);
//				if(event.originalEvent){FTT.debug('bracketToForm: originalEvent (data): '+event.originalEvent.data+' (data) '+event.originalEvent.key+' (key)');}
				if ( event.originalEvent && ['[','{'].includes(event.originalEvent.data) && FTT.lastPressedKey == event.originalEvent.data && ( ! FTT.settings.bracketToFormFast || FTT.lastPressedTime+750 > new Date().getTime() ) && ( ( FTT.lastPressedKey == '[' && FTT.settings.bracketToForm ) || ( FTT.lastPressedKey == '{' && FTT.settings.bracketToFormT ) ) ) {
//					FTT.debug('bracketToForm: you entered two brackets, opening link form for type '+type);
					FTT.insertLink('linkSwitch',undefined,undefined,undefined,type);
				}
				if ( FTT.settings.bracketToFormFast ) {
					FTT.lastPressedTime = new Date().getTime();
				}
				if ( event.originalEvent ) {
					FTT.lastPressedKey = event.originalEvent.data;
				} else {
					FTT.lastPressedKey = event.data;
				}
			};
			$(FTT.BMainEl).on('input',function(event){ //we're using input here instead of the "change" event as emitted by OO.ui because we don't want the link form to open when backspacing a link
				FTT.bracketToFormFunc(event,'main');
			});
			$(FTT.BTitleEl).on('input',function(event){
				FTT.bracketToFormFunc(event,'title');
			});
			$(FTT.BSummaEl).on('input',function(event){
				FTT.bracketToFormFunc(event,'summary');
			});
			$(FTT.BAllEl).on('focus',function(event){
				delete FTT.lastPressedKey;
			});
			$(FTT.BAllEl).on('click',function(event){
				delete FTT.lastPressedKey;
			});
		}
		if ( FTT.settings.rewriteOnBlur && M1('wgPageContentModel') == 'wikitext' ) {
			$(FTT.BMainEl).on('blur', function(event,origtext,text,range,ed){
				if ( FTT.focusRunning || $('#FTTReplaceButtonBar:hover')[0] ) {
					return;
				}
				ed = FTT.getCurrentEditor();
				if ( ed == 'CodeEditor' || ed == 'CodeMirror' ) {
//					FTT.debug('selectRange would also focus the input when using CodeEditor/CodeMirror and setValue moves the caret position, both of which are unacceptable, skipping rewrite-on-blur');
					return;
				}
//				FTT.debug('main input lost focus, applying regular expressions and rewritun');
				text = FTT.getValue();
				//range = FTT.getRange(); // I think I had added this due to the selection getting lost, IIRC due to blurring by getLastActiveField, but this probably makes it impossible to unfocus the main text field?
				if ( FTT.settings.enableCIThatRun || FTT.settings.enableCIThatRunCmt ) {
					try{FTT.runCI(text);} catch (e) {FTT.notify(FTT.B1['wikieditor-toolbar-tool-replace-invalidregex'].replace(/\$1/,FTT.basicmsgs.actionfailed));}
					text = FTT.getValue();
				}
				text = FTT.safeText(text,'rewritun');
				text = FTT.safeText(text,'unsafe');
				if ( FTT.settings.runCIAgain ) {
					try{FTT.runCI(text);} catch (e) {FTT.notify(FTT.B1['wikieditor-toolbar-tool-replace-invalidregex'].replace(/\$1/,FTT.basicmsgs.actionfailed));}
				}
				if ( FTT.settings.runRewritunAgain ) {
					origtext = FTT.getValue();
					text = FTT.safeText(origtext,'rewritun');
					text = FTT.safeText(text,'unsafe');
					if ( text != origtext ) {
						FTT.setValue(text);
					}
				}
				//FTT.selectRange(range.from,range.to); // I think I had added this due to the selection getting lost, IIRC due to blurring by getLastActiveField, but this probably makes it impossible to unfocus the main text field?
			});
		}
		FTT.loadedDrafts = FTT.testValidJSON(FTT.getItemLS('FTTDrafts'));
		if ( PRM.type != 'heading' ) {
			FTT.draftID = D1(PRM.id.replace(/:[0-9]+$/,''));
		} else {
			PRM = FTT.addPageAndSectionTitleToRPL(PRM); //need sectionseq and title as draftID includes them
			FTT.draftID = D1(PRM.pageTitle+PRM.sectionTitle+PRM.sectionseq.toString());
		}
		FTT.extraDivs = '<div id="FTTCustomInserts" class="FTTCustomInserts FTTMarginHalfEm FTTNoDisplay"></div><div id="FTTCustomInsertsAutoPost" class="FTTMarginHalfEm FTTNoDisplay"></div><div id="onetimetools" class="FTTonetimetools FTTNoDisplay"></div><div id="showNewLines" class="FTTNewLinesBox FTTNoDisplay"></div><div id="FTTSettings" class="FTTSettings FTTMarginHalfEm FTTNoDisplay"><div id="FTTSettingsTop"></div></div>';
		$('#FTTReplyForm').append(FTT.extraDivs);
		if ( FTT.settings.previewDblClick ) {
			$('#FTTPreviewBox').on('dblclick',function(){
				if ( $('#FTTPreviewBox>.mw-parser-output.diff')[0] ) {
					FTT.doDiff(PRM);
				} else {
					FTT.doPreview('preview',PRM);
				}
			});
		}
		FTT.textAndPreview = document.createElement('div'); //new div to allow putting preview and textarea side by side
		FTT.textAndPreview.id = 'FTTTextAndPreview';
		FTT.insertAfter($('#FTTUITextInput')[0],FTT.textAndPreview);
		FTT.previewBoxDiv = document.createElement('div');
		FTT.previewBoxDiv.id = 'FTTPreviewBox';
		FTT.previewBoxDiv.classList.add('FTTNoDisplay');
		FTT.previewBoxDiv.innerHTML = '<div class="mw-parser-output"></div>';
		FTT.insertAfter($('#FTTTextAndPreview')[0],FTT.previewBoxDiv);
		FTT.previewBtns = document.createElement('div');
		FTT.previewBtns.id = 'FTTPreviewBtns';
		$('#FTTTextAndPreview').append($('#FTTUITextInput')[0]);
		$('#FTTTextAndPreview').append(FTT.UIVisual);
		if ( (FTT.settings.barRightAbove && window.innerWidth > FTT.settings.overlayThreshold) || (FTT.settings.MFbarRightAbove && window.innerWidth <= FTT.settings.overlayThreshold) ) {
			$('#FTTUITextInputTitle')[0].parentElement.insertBefore($('#FTTButtonBarRight')[0],$('#FTTUITextInputTitle')[0]);
		}
		if ( ! PRM.highlightRef && ! FTT.reloaded && PRM.type != 'edit' && FTT.settings.saveDraft && FTT.loadedDrafts && FTT.loadedDrafts[FTT.draftID] && FTT.loadedDrafts[FTT.draftID].time > (new Date().getTime() - 604800000) ) {
//			FTT.debug('found draft in localStorage');
			mw.loader.using(['oojs-ui-core','oojs-ui-windows']).then(function(){
				FTT.FFDarkMode();
				$('#FTTOverlay').addClass('FTTNoDisplay');
				OO.ui.confirm(FTT.msgs.restoreDraft).done( function(confirmd) {
					FTT.FFDarkMode(1);
					$('#FTTOverlay').removeClass('FTTNoDisplay');
					if (confirmd) {
						FTT.setValue(FTT.loadedDrafts[FTT.draftID].text);
						FTT.applyModules('afterPreload');
						if ( PRM.justSettings ) {
							FTT.magicScroll($('#FTTSettings')[0]);
						} else {
							FTT.focusInput('start');
						}
						FTT.undoRevisions.UITextInput = [];
						FTT.undoSave();
						if ( FTT.settings.refList && $('.FTTRef')[0] && PRM.type == 'editFullPage' ) {
							FTT.highlightRef(undefined,0,true);
						}
						FTT.maybeAceUpMySleeve(trigger);
					} else {
						FTT.saveDraft(PRM, 'remove');
						FTT.preloadWikiText(PRM, trigger);
					}
				});
			});
		} else if ( ( M1('wgRevisionId') || M1('wgCurRevisionId') ) && ! FTT.reloaded && ( PRM.type == 'edit' || PRM.type == 'editFullPage' || PRM.type == 'heading' ) ) {
			FTT.preloadWikiText(PRM, trigger);
		} else if ( ! $('#mw-content-text>.noarticletext')[0] && M1('wgCurRevisionId') == 0 && M1('wgCanonicalNamespace') == 'MediaWiki' ) {
			FTT.preloadWikiText(PRM, trigger, 1);
		} else if ( ! FTT.reloaded && (PRM.preload || PRM.preloadtitle || PRM.subtype == 'InputBox' ) && FTT.getValue() == '' && FTT.UITextInputTitle.getValue() == '' ) { //InputBox/FTT-comment-link preload. PRM.int would be undefined when launching from action=edit InputBox link
//			FTT.debug('openReplyForm: preload InputBox params');
			if ( typeof PRM.int != 'undefined' ) { //PRM.int would be undefined on section=new pages where no actual InputBox could exist, parameters are extracted from the URL there
//				FTT.debug('openReplyForm: get title from InputBox. Not always known on page load as the user hadn\'t entered it yet');
				try{PRM.preloadtitle = (FTT.processElementArray[PRM.int].querySelectorAll('input[name="preloadtitle"]')[0].value || PRM.preloadtitle);} catch (e) {} // replace preloadTitle with title the user entered in text field or replace it with itself (no change)
			}
			if ( PRM.preloadtitle ) {
//				FTT.debug('openReplyForm: preloading title');
				FTT.UITextInputTitle.setValue(PRM.preloadtitle);
			}
			if ( PRM.preload ) {
				FTT.disableForm(true); //disable form until preload is done
//				FTT.debug('openReplyForm: preloading page specified by InputBox');
					api.get( {
						action: 'query', export: 'true', format: 'json', titles: PRM.preload,
					} ).then( function ( data ) {
//						FTT.debug(data);
						FTT.wikiTextForEdit = FTT.getWikitextFromExport(data.query.export["*"]);
						if ( typeof PRM.preloadparams == 'object' ) {
							if ( ! Array.isArray(PRM.preloadparams) ) {
								PRM.preloadparams = JSON.parse(D2(PRM.preloadparams).replace(/\&quot\;/g, '"'));
							}
							for (IBParamsInt = 0; IBParamsInt < PRM.preloadparams.length; IBParamsInt++) {
								if ( FTT.extInputBox[PRM.int] && FTT.extInputBox[PRM.int]['TIW'+IBParamsInt] ) {
									FTT.preloadReplacement = FTT.extInputBox[PRM.int]['TIW'+IBParamsInt].getValue();
								} else {
									FTT.preloadReplacement = PRM.preloadparams[IBParamsInt];
								}
//								FTT.debug('openReplyForm: replacing preloadparam ' + (IBParamsInt + 1));
								FTT.replacePreloadVar = new RegExp('\\$' + ( IBParamsInt + 1) + '([^0-9]|$)', 'g');
								FTT.wikiTextForEdit = FTT.wikiTextForEdit.replace(FTT.replacePreloadVar, FTT.escapeReplacement(FTT.preloadReplacement) + '$1');
							}
						}
						FTT.onlyIncludeMatches = FTT.wikiTextForEdit.match(/<onlyinclude>(([^<]|<(?!\/onlyinclude>))*)<\/onlyinclude>/g);
						if ( FTT.onlyIncludeMatches ) {
							FTT.wikiTextForEdit = '';
							for (FTT.preloadOnlyIncludeInt=0;FTT.preloadOnlyIncludeInt<FTT.onlyIncludeMatches.length;FTT.preloadOnlyIncludeInt++){
								FTT.wikiTextForEdit = FTT.wikiTextForEdit + FTT.onlyIncludeMatches[FTT.preloadOnlyIncludeInt].replace(/^<onlyinclude>/,'').replace(/<\/onlyinclude>/,'');
							}
						} else {
							FTT.wikiTextForEdit = FTT.wikiTextForEdit.replace(/<noinclude>(([^<]|<(?!\/noinclude>))*)<\/noinclude>/g,'').replace(/<noinclude[ ]?\/>/g,'').replace(/<includeonly>(([^<]|<(?!\/includeonly>))*)<\/includeonly>/g,'$1');
							if ( FTT.wikiTextForEdit.match(/[\s]*~~~~[\s]*/) ) {
								FTT.forceSign = 1;
							}
							FTT.wikiTextForEdit = FTT.wikiTextForEdit.replace(/[\s]*~~~~[\s]*/,'');
						}
						FTT.setValue(FTT.wikiTextForEdit);
						if ( FTT.extInputBox[PRM.int] && FTT.extInputBox[PRM.int].autopost ) {
							FTT.postReply1(PRM,'button');
							return;
						}
						FTT.undoRevisions.UITextInput = [];
						FTT.undoSave();
						FTT.applyModules('afterPreload');
						FTT.disableForm(false);
						if ( FTT.UITextInputTitle.isVisible() == false ) {
//							FTT.debug('openReplyForm: loaded requested preload page, focus input field');
							FTT.focusInput();
						}
						FTT.maybeAceUpMySleeve();
					}, function ( code, data ) { FTT.APIError(code, data);
					});
			} else {
				FTT.maybeAceUpMySleeve();
			}
		} else {
			FTT.maybeAceUpMySleeve();
		}
		delete FTT.reloaded;
		if ( PRM.type == 'newsection' ) {
			window.FTTUITextInputTitle.scrollIntoView(FTT.smoothScroll);
		}
		if ( FTT.settings.tosNag ) {
			FTT.insertToSnag();
		}
		if ( ['pbFloatF','pbBarF','pbBarCenterF'].includes(FTT.settings.previewBtns) ) {
			FTT.prvBtnsFrame = false;
		} else {
			FTT.prvBtnsFrame = true;
		}
		FTT.UIPreviewToggle = new OO.ui.ButtonWidget( {
			label:new OO.ui.HtmlSnippet('<span class="FTTSVG FTTSVGChevronIcon FTTToggleIn FTTPreviewChevron"></span>'),
			id:'FTTPreviewToggle',
			title:FTT.msgs.previewSide,
			framed:FTT.prvBtnsFrame
		});
		FTT.UIPreviewToggle.$button.attr('aria-label',FTT.msgs.previewApart);
		FTT.UIPreviewToggle.on('click',function(){FTT.movePreview();});
		FTT.UILiveToggle = new OO.ui.ButtonWidget( {
			label:new OO.ui.HtmlSnippet('<span class="FTTSVG FTTSVGLiveOn FTTToggleIn"></span>'),
			id:'FTTliveToggle',
			title:FTT.msgs.liveOff,
			framed:FTT.prvBtnsFrame
		});
		FTT.UILiveToggle.$button.attr('aria-label',FTT.msgs.liveOff);
		FTT.UILiveToggle.on('click',function(){FTT.livePreviewToggle('btn');});
		FTT.UIRefreshBtn = new OO.ui.ButtonWidget( {
			label:new OO.ui.HtmlSnippet('<span class="FTTSVG FTTSVGRefresh FTTToggleIn"></span>'),
			id:'FTTRefreshBtn',
			title:FTT.B1.preview,
			framed:FTT.prvBtnsFrame
		});
		FTT.UIRefreshBtn.$button.attr('aria-label',FTT.B1.preview);
		FTT.UIRefreshBtn.on('click',function(){FTT.doPreview('preview',PRM);});
		FTT.previewButtonBar = new OO.ui.ButtonGroupWidget( {
			items: [FTT.UIRefreshBtn,FTT.UILiveToggle,FTT.UIPreviewToggle],
		} );
		$('#FTTPreviewBox').prepend(FTT.previewBtns);
		$('#FTTPreviewBox').addClass(FTT.settings.previewBtns);
		$('#FTTPreviewBtns').append(FTT.previewButtonBar.$element);
		if ( M1('wgPageContentModel') == 'wikitext' && FTT.enableLivePreview ) { //&& ['comment','edit','newheading','newsection'].includes(FTT.PRMOpened.type)
			$('#FTTPreviewBox').removeClass('FTTNoDisplay');
			FTT.UIPreviewButton.toggle(false);
			FTT.livePreviewToggle();
		}
		if ( FTT.settings.autoPostAbove ) {
			$('#FTTReplyForm')[0].parentElement.insertBefore($('#FTTCustomInsertsAutoPost')[0],$('#FTTReplyForm')[0]);
		}
		FTT.movePreviewAbove();
		FTT.customInsertButton = {};
		FTT.cISummaries = {};
		for (ORFint = 0; ORFint < FTT.settings.cI.length; ORFint++) {
//			FTT.debug('process custom inserts');
			if ( FTT.settings.cI && FTT.settings.cI[ORFint] && FTT.settings.cI[ORFint] != "" ) { //non-empty insert found
//				FTT.debug('openReplyForm: adding button for custom insert #' + ORFint);
				FTT.CIbuttonFlags = [];
				if ( D2(FTT.settings.cI[ORFint]).match(/\:<<([^\>]*)\>\>/) ) {
					FTT.cILabel = '';
					FTT.cILabelArr = D2(FTT.settings.cI[ORFint]).replace(/.*\:<<([^\>]*)\>\>/, '$1').split(/\|/);
					for (FTT.cIParamsInt=0;FTT.cIParamsInt<FTT.cILabelArr.length;FTT.cIParamsInt++) {
						if ( FTT.cILabelArr[FTT.cIParamsInt] == 'AUTOPOST' ) {
							FTT.CIbuttonFlags = ['progressive'];
						} else if ( FTT.cILabelArr[FTT.cIParamsInt].match(/^SUMMARY=/) ) {
							FTT.cISummaries[ORFint] = FTT.cILabelArr[FTT.cIParamsInt].slice(8);
						} else if ( FTT.cILabelArr[FTT.cIParamsInt] == 'NONCMT' && FTT.PRMOpened.type == 'comment' ) {
							FTT.cILabel = ''; //don't show button
						} else if ( FTT.cILabelArr[FTT.cIParamsInt] == 'CMT' && FTT.PRMOpened.type != 'comment' ) {
							FTT.cILabel = ''; //don't show button
						} else {
							FTT.cILabel = FTT.cILabelArr[FTT.cIParamsInt];
						}
					}
				} else {
					FTT.cILabel = D2(FTT.settings.cI[ORFint]);
				}
				if ( FTT.cILabel.length > 14 ) {
					FTT.cILabel = FTT.cILabel.slice(0,10) + '..';
				}
				FTT.customInsertButton[ORFint] = new OO.ui.ButtonWidget( {
					label: FTT.cILabel,
					classes: [ 'FTTMarginHalfEm' ],
					flags: FTT.CIbuttonFlags
				});
				if ( FTT.CIbuttonFlags[0] == 'progressive' && FTT.cILabel != '' ) {
					$('#FTTCustomInsertsAutoPost').removeClass('FTTNoDisplay');
					$('#FTTCustomInsertsAutoPost').append(FTT.customInsertButton[ORFint].$element);
				} else if ( FTT.cILabel != '' ) {
					$('#FTTCustomInserts').removeClass('FTTNoDisplay');
					$('#FTTCustomInserts').append(FTT.customInsertButton[ORFint].$element);
				}
			} //end non-empty insert found
		} //end for
		FTT.insertCustomInsert = function(num) {
//			FTT.debug('insertCustomInsert: insert custom insert #' + num);
			FTT.activeInput = FTT.getLastActiveField();
			FTT.activatedCI = D2(FTT.settings.cI[num]).replace(/\:<<[^\>]*\>\>$/, '');
			if ( FTT.activatedCI.match(FTT.insertIsRegExpRegExp) ) {
				FTT.customInsertRegExpNowParts = FTT.activatedCI.match(FTT.insertIsRegExpRegExp);
				FTT.customInsertRegExpNow = new RegExp(FTT.customInsertRegExpNowParts[1], FTT.customInsertRegExpNowParts[3]);
				FTT.setValue(FTT.getValue().replace(FTT.customInsertRegExpNow, FTT.customInsertRegExpNowParts[2].replace(/[\\]n/g,'\n').replace(/\\\//g,'/')));
				FTT.undoSave();
			} else {
				if ( FTT.activatedCI.match('FTTCRT') ) {
					FTT.activatedCIStart = FTT.activatedCI.split('FTTCRT')[0].replace(/[\\]n/g,'\n');
					FTT.activatedCIEnd = FTT.activatedCI.split('FTTCRT')[1].replace(/[\\]n/g,'\n');
				} else {
					FTT.activatedCIStart = FTT.activatedCI.replace(/[\\]n/g,'\n');
					FTT.activatedCIEnd = '';
				}
				FTT.insertMarkup('cI', '', FTT.activatedCIStart, FTT.activatedCIEnd,FTT.focusNode,FTT.focusOffset,FTT.anchorNode,FTT.anchorOffset);
			}
			if ( typeof FTT.cISummaries[num] == 'string' ) {
				FTT.UITextInputSummary.setValue(FTT.UITextInputSummary.getValue() + FTT.cISummaries[num].replace(/FTTPIPE/g,'|'));
			}
			if ( D2(FTT.settings.cI[num]).match(/\:<<[^\>]*AUTOPOST\|[^\>]*\>\>$/) ) {
//				FTT.debug('insertCustomInsert: AUTOPOST activated');
				FTT.postReply1(FTT.PRMOpened,'autopost');
			}
		};
		FTT.insBtn = function(insNum) {
			FTT.customInsertButton[insNum].on('click', function() { FTT.insertCustomInsert(insNum); });
		};
		for (FTT.CIButtonInt = 0; FTT.CIButtonInt < 50; FTT.CIButtonInt++) {
			if ( typeof FTT.customInsertButton[FTT.CIButtonInt] == 'object' ) {
				FTT.insBtn(FTT.CIButtonInt);
			}
		}
		if ( FTT.settings.limitWidth ) {
			$('.FTTForm').addClass('FTTLimitWidth');
			FTT.UIVisual.classList.add('FTTLimitWidth');
			$('FTTUITextInput').removeClass('FTTUnsetLimitWidth');
			$('FTTUITextInput').addClass('FTTLimitWidth');
		}
		if ( PRM.type == 'comment' || PRM.type == 'edit' || PRM.type == 'FCL' || PRM.type == 'editFullPage' || PRM.type == 'heading' ) {
//			FTT.debug('openReplyForm: hide title input and make it read-only');
			FTT.UITextInputTitle.setValue('');
			FTT.UITextInputTitle.setReadOnly(true);
			FTT.UITextInputTitle.toggle(false);
		}
		PRM = FTT.addPageAndSectionTitleToRPL(PRM);
		if ( !PRM.justSettings && PRM.pageTitle == M1('wgPageName') && M1('wgIsProbablyEditable') == false ) {
			FTT.showProtectedWarning(PRM.pageTitle);
			FTT.disableForm(true);
			FTT.UICancelButton.setDisabled(false);
		}
		if ( !PRM.justSettings && ( FTT.settings.editNotice || PRM.editIntro ) && M1('wgPageContentModel') == 'wikitext' ) { // load editnotice. if there isn't any it'll just be blank
			FTT.loadEditNotice();
		}
		if ( FTT.settings.checkNewComments && M1('wgArticleId') != 0 && FTT.PRMOpened.type == 'comment' ) {
			if ( FTT.sectionToCheckForComments != PRM.sectionTitle + PRM.sectionseq ){
//				FTT.debug('openReplyForm: first form you opened since loading the page or section differs from the section in which the last form was opened');
				FTT.sectionToCheckForComments = PRM.sectionTitle + PRM.sectionseq;
				delete FTT.pageRevisionSinceLastCheck[PRM.pageTitle];
				delete FTT.pageRevisionIDSinceLastCheck[PRM.pageTitle];
			}
			FTT.checkForNewComments(PRM, 'openform');
		}
//		FTT.debug('openReplyForm: scroll reply button into view');
		FTT.UIReplyButton.scrollElementIntoView();
		if ( PRM.type == 'newsection' ) {
//			FTT.debug('focus input title and scroll it into view');
			FTT.UITextInputTitle.focus();
			FTT.UITextInputTitle.scrollElementIntoView();
			FTT.UITextInputTitle.on('change',function() { FTT.checkIfTitleUnique(); });
			FTT.checkIfTitleUnique();
		} else if ( ! FTT.PRMOpened.highlightRef ) {
//			FTT.debug('openReplyForm: focus text input');
			FTT.focusInput();
		}
		FTT.UISettingsButton.on('click', FTT.openSettings);
		if ( trigger == 'reload' ) {
//			FTT.debug('openReplyForm: form opened by FTT.reloadForm, scroll FTTUITextInput into view');
			window.FTTUITextInput.scrollIntoView(FTT.smoothScroll);
		}
		if ( FTT.UIReplyButton.isElementAttached() && M1('wgAction') == 'edit' && M2('section') == 'new' ) {
			$('#editform,#wikiPreview').addClass('FTTNoDisplay');
			if ( $('html.ve-activated')[0] ) { //section=new in Visual Editor
				var DelayVETakeOver = setInterval(function () {
					if ( ! FTT.UIReplyButton.isElementAttached() ) { //Factotum was closed
						clearInterval(DelayVETakeOver);
					} else if ( ! $('body.oo-ui-windowManager-modal-active')[0] && $('html.ve-active')[0] ) {
						clearInterval(DelayVETakeOver);
						$('#content>.ve-init-target .ve-ui-toolbar,#content>.ve-init-target #bodyContent .ve-init-mw-desktopArticleTarget-uneditableContent,.ve-init-mw-desktopArticleTarget-originalContent #mw-content-text>p:first-child,.ve-init-mw-desktopArticleTarget-originalContent .ve-init-mw-desktopArticleTarget-sectionTitle,.mw-body-content>.ve-ce-surface').addClass('FTTNoDisplay');
						$('#mw-content-text.ve-init-mw-desktopArticleTarget-editableContent').addClass('VEhidden').removeClass('ve-init-mw-desktopArticleTarget-editableContent');
					}
				},500);
			}
		}
		FTT.syncToVisual();
		if ( FTT.isMobile && FTT.settings.MFAdj ) {
			if ( FTT.settings.MFAdjSumma ) {
				FTT.UITextInputSummaryMinorLayout.toggle(false);
			}
			if ( FTT.settings.MFAdjCI ) {
				$('#FTTCustomInserts,#FTTCustomInsertsAutoPost').addClass('FTTNoDisplay');
			}
			if ( FTT.settings.MFAdjMarkup ) {
				FTT.markUpDropDown.toggle(false);
			}
			if ( FTT.settings.MFAdjLink ) {
				FTT.UIMarkupLinkButton.toggle(false);
			}
			if ( FTT.settings.MFAdjPing && FTT.cITSbuttonMenu ) {
				FTT.cITSbuttonMenu.toggle(false);
			}
			if ( FTT.settings.MFAdjAdv ) {
				FTT.UIoneTimeToolsButton.toggle(false);
			}
			if ( FTT.settings.MFAdjSwitch && FTT.UISwitchEditorButton ) {
				FTT.UISwitchEditorButton.toggle(false);
			}
			if ( FTT.settings.MFAdjDiff ) {
				FTT.UIDiffButton.toggle(false);
			}
			if ( FTT.settings.MFAdjReflist && FTT.HLRefDropDownL ) {
				FTT.HLRefDropDownL.toggle(false);
			}
		}
		FTT.shiftToOverlay();
		FTT.openingFormInProgress = new Date().getTime();
		FTT.applyModules('afterOpenForm');
		if ( PRM.justSettings ) { //form was opened by entering ctrl+shift+/
			$('.FTTForm>*:not(#FTTReplyForm),#FTTReplyForm>*:not(#FTTSettings)').addClass('FTTNoDisplay');
			FTT.UITextInputTitle.$input.blur();
			FTT.openSettings();
		}
	} catch (e) {
		FTT.JSError(e);
	}
	}); //end mw.loader.using
	} //end there is no reply form yet, create one
}; //end openReplyForm
FTT.keyOpenReplyForm = Object.keys(FTT)[Object.values(FTT).indexOf(FTT.openReplyForm)]; //used for insertion in onclick stuff, so the key mangler (AJSJSM) won't break that
if ( FTT.settings.shortcuts ) {
	$('body').keydown(function(eventEsc,genLinkInt,foundLinkLayout){
		if ( eventEsc.which == 27 ) {//pressed escape
//			FTT.debug('pressedEscape: try MediaViewer');
			if ( $('.mw-mmv-overlay')[0] ) {
				return; //not my business
			}
//			FTT.debug('pressedEscape: try floating ToC');
			if ( FTT.settings.floatingToC && $('#toctogglecheckbox')[0] && ! $('#toctogglecheckbox')[0].checked ) { //floating ToC
				$('#toctogglecheckbox').click();
				return;
			}
//			FTT.debug('pressedEscape: try dry run preview');
			if ( FTT.openDryRun ) {
				FTT.closeDryRun();
				return;
			}
//			FTT.debug('pressedEscape: try link form within FTT');
			if ( FTT.UIinsertLinkForm && FTT.UIinsertLinkForm.isVisible() ) {
				FTT.UIinsertLinkForm.toggle(false);
				FTT.focusInput();
				return;
			}
//			FTT.debug('pressedEscape: try onetimetools');
			if ( $('#onetimetools')[0] && ! $('#onetimetools').hasClass('FTTNoDisplay') ) {
				$('#onetimetools').addClass('FTTNoDisplay');
				FTT.focusInput();
				return;
			}
//			FTT.debug('pressedEscape: try emptying settings filter within FTT');
			if ( FTT.filterSettingsInput && FTT.filterSettingsInput.isElementAttached() && FTT.filterSettingsInput.getValue() != '' && ! $('#FTTSettings').hasClass('FTTNoDisplay') ) {
				FTT.filterSettingsInput.setValue('');
				return;
			}
//			FTT.debug('pressedEscape: try settings within FTT');
			if ( FTT.UITextInput && FTT.UITextInput.isElementAttached() && ! $('#FTTSettings').hasClass('FTTNoDisplay') ) {
				FTT.closeSettings();
				return;
			}
//			FTT.debug('pressedEscape: try permalink forms');
			FTT.permaLinkLayoutKeys = Object.keys(FTT.permaLinkLayout);
			for (genLinkInt=0;genLinkInt<FTT.permaLinkLayoutKeys.length;genLinkInt++) {
				if ( FTT.permaLinkLayout[FTT.permaLinkLayoutKeys[genLinkInt]].isVisible() ) {
					FTT.permaLinkLayout[FTT.permaLinkLayoutKeys[genLinkInt]].toggle(false);
//					FTT.debug('pressedEscape: toggled link form '+FTT.permaLinkLayoutKeys[genLinkInt]);
					foundLinkLayout = true;
				}
			}
			if ( foundLinkLayout ) {
				$('.FTTGenLink.FTTNoDisplay').removeClass('FTTNoDisplay');
				return;
			}
//			FTT.debug('pressedEscape: try FTT form');
			if ( $('#FTTUITextInput')[0] ) {
				FTT.escPageXOffset = window.pageXOffset;
				FTT.escPageYOffset = window.pageYOffset;
				FTT.cancelReply('esc');
			}
		}
	});
}
FTT.openSettings = function(openClose) {
	if ( FTT.settings.useLocator && FTT.hiddenLocatorSetting != true ) {
		mw.util.addCSS('#FTTPrefuseLocatorFieldLayout {display:none}'); //you really, really shouldn't disable locators. It's a leap forward. They are so much easier to work with. Using FTT without locators isn't routinely tested, can result in a buggier experience due to inherent limitations of legacy timestamps and if we ever manage to phase out legacy timestamps we can improve performance. If you insist on turning this off you better know how to open the browser console
		FTT.hiddenLocatorSetting = true;
	}
	mw.loader.using( [ 'oojs-ui-core','oojs-ui-widgets' ] ).then( function () {
		FTT.applyModules('beforeOpenSettings');
		FTT.UISettingsButton.blur();
		if ( document.getElementById('saveSettingsButton') && ! $('#FTTSettings').hasClass('FTTNoDisplay') && openClose != 'open' ) {
//			FTT.debug('settings are already opened. hiding them');
			FTT.closeSettings();
			return;
		} else if ( document.getElementById('saveSettingsButton') && $('#FTTSettings').hasClass('FTTNoDisplay') ) {
//			FTT.debug('settings were opened previously but currently hidden. unhiding them');
			if ( !FTT.sideBySide && !FTT.previewAbove && FTT.UIPreviewButton.isVisible() ) {
				$('#FTTPreviewBox').addClass('FTTNoDisplay');
			}
			$('#FTTSettings').removeClass('FTTNoDisplay');
			FTT.magicScroll($('#FTTSettings')[0]);
			return;
		}
		if ( !FTT.sideBySide && !FTT.previewAbove && FTT.UIPreviewButton.isVisible() ) {
			$('#FTTPreviewBox').addClass('FTTNoDisplay');
		}
		$('#FTTSettings').removeClass('FTTNoDisplay');
		FTT.sEl = {};
		FTT.applyModules('afterSettingElement');
		FTT.makeFieldL = function(setting) {
			FTT.sEl[setting+'FieldLayout'] = new OO.ui.FieldLayout( FTT.sEl[setting], { label: FTT.msgs[setting], align: 'inline', classes: [ 'FTTOOuiFieldLayout' ] } );
		};
		FTT.makeTIW = function(TIWname,placeholder) {
//			FTT.debug('create TextInputWidget for '+TIWname);
			FTT.sEl[TIWname] = new OO.ui.TextInputWidget( {
				placeholder: placeholder,
				value: FTT.settings[TIWname],
				classes: [ 'FTTMarginHalfEmTop', 'FTTMarginHalfEm' ],
			} );
			FTT.makeFieldL(TIWname);
		};
		FTT.makeNIW = function(NIWname,min,max) {
//			FTT.debug('create NumberInputWidget for '+NIWname);
			FTT.sEl[NIWname] = new OO.ui.NumberInputWidget( {
				value: FTT.settings[NIWname],
				min: min,
				max: max,
				classes: [ 'FTTMarginHalfEmTop', 'FTTMarginHalfEm', 'maxSubsSize' ],
			} );
			FTT.makeFieldL(NIWname);
		};
		FTT.sEl.blacklist = new OO.ui.MultilineTextInputWidget( {
			autosize: true,
			value: FTT.settings.blacklist,
		});
		FTT.makeFieldL('blacklist');
		FTT.makeNIW('outdent',5,30);
		FTT.makeNIW('dbgLimit',1,999999);
		FTT.makeNIW('stalkInterval',1,240);
		FTT.makeNIW('overlayThreshold',0,99999);
		FTT.makeNIW('stalkMaxSubsSize',1,127);
		FTT.sEl.stalkMaxSubsSize.$input.on('blur',function(size){
			size = Number(FTT.sEl.stalkMaxSubsSize.getValue());
			if ( size > 127 ) {
				FTT.sEl.stalkMaxSubsSize.setValue('127');
			} else if ( isNaN(size) || size == 0 ) {
				FTT.sEl.stalkMaxSubsSize.setValue('64');
			} 
		});
		FTT.sEl.outdent.$input.on('blur',function(size){
			size = Number(FTT.sEl.outdent.getValue());
			if ( isNaN(size) || size < 6 ) {
				FTT.sEl.outdent.setValue('5');
			} 
		});
		FTT.sEl.previewBtns = new OO.ui.DropdownInputWidget( {
			options: [
				{ data: 'pbOverlayTrans', label: FTT.msgs.pbOverlayTrans },
				{ data: 'pbOverlayHidden', label: FTT.msgs.pbOverlayHidden },
				{ data: 'pbBarCenter', label: FTT.msgs.pbBarCenter },
				{ data: 'pbBarCenterF', label: FTT.msgs.pbBarCenterF },
				{ data: 'pbBar', label: FTT.msgs.pbBar },
				{ data: 'pbBarF', label: FTT.msgs.pbBarF },
				{ data: 'pbFloat', label: FTT.msgs.pbFloat },
				{ data: 'pbFloatF', label: FTT.msgs.pbFloatF },
				{ data: 'pbDisabled', label: FTT.msgs.pbDisabled },
				],
			value: FTT.settings.previewBtns,
			classes: [ 'FTTDropDownMenu' ],
		} );
		FTT.makeFieldL('previewBtns');
		FTT.sEl.editor = new OO.ui.DropdownInputWidget( {
			options: [
				{ data: 'source', label: FTT.msgs.editorSource },
				{ data: '2010wikitext', label: FTT.msgs.editor2010 },
				{ data: 'visualLight', label: FTT.msgs.editorVisualLight },
				{ data: 'lastused', label: FTT.msgs.editorLastUsed },
				],
			value: FTT.settings.editor,
			classes: [ 'FTTDropDownMenu' ],
		} );
		FTT.makeFieldL('editor');
		FTT.sEl.watchlist = new OO.ui.DropdownInputWidget( {
			options: [
				{ data: 'nochange', label: FTT.msgs.nochange },
				{ data: 'preferences', label: FTT.B1.preferences },
				{ data: 'unwatch', label: FTT.B1.unwatch },
				{ data: 'watch', label: FTT.B1.watch },
				],
			value: FTT.settings.watchlist,
			id: 'UIwatchlist',
			classes: [ 'FTTDropDownMenu' ],
		} );
		FTT.makeFieldL('watchlist');
		FTT.expireLabel = function(days) {
			if ( days.slice(0,1) == '+' ) {
				return FTT.msgs.watchlistexpiryplusdays.replace('DAYS', days.slice(1));
			} else {
				return FTT.msgs.watchlistexpirydays.replace('DAYS', days);
			}
		};
		FTT.watchExpOpts = [];
		FTT.watchExpOptsDays = ['+7','+14','+30','+90','14','30','90','180'];
		for ( FTT.wEOI=0;FTT.wEOI<FTT.watchExpOptsDays.length;FTT.wEOI++) {
			FTT.watchExpOpts.push({ data: FTT.watchExpOptsDays[FTT.wEOI]+' days', label: FTT.expireLabel(FTT.watchExpOptsDays[FTT.wEOI]) });
		}
		FTT.watchExpOpts.push({ data: 'indefinite', label: FTT.expireLabel('∞') });
		FTT.watchExpNewOpts = [{ data: 'same', label: FTT.msgs.same }].concat(FTT.watchExpOpts);
		FTT.sEl.watchlistexpiry = new OO.ui.DropdownInputWidget( {
			options: FTT.watchExpOpts,
			value: FTT.settings.watchlistexpiry,
			classes: [ 'FTTDropDownMenu' ],
		} );
		FTT.makeFieldL('watchlistexpiry');
		FTT.sEl.watchlistexpirynew = new OO.ui.DropdownInputWidget( {
			options: FTT.watchExpNewOpts,
			value: FTT.settings.watchlistexpirynew,
			classes: [ 'FTTDropDownMenu' ],
		} );
		FTT.makeFieldL('watchlistexpirynew');
		FTT.stalkInterLabel = function(days) {
			return FTT.B1['notification-timestamp-ago-minutes'].replace(/\{\{PLURAL:\$1\|(.*)\}\}$/,'$1').replace('$1', days);
		};
		FTT.sEl.UIfontSize = new OO.ui.DropdownInputWidget( {
			options: [
				{ data: '0.875em', label: FTT.msgs.UIfontSize0875em },
				{ data: 'x-small', label: FTT.msgs.UIfontSizeTiny },
				{ data: 'small', label: FTT.msgs.UIfontSizeSmall },
				{ data: 'medium' , label: FTT.msgs.UIfontSizeMedium },
				{ data: 'large' , label: FTT.msgs.UIfontSizeLarge },
				{ data: 'x-large', label: FTT.msgs.UIfontSizeHuge },
				],
			value: FTT.settings.UIfontSize,
			id: 'UIfontSize',
			classes: [ 'FTTDropDownMenu' ],
		} );
		FTT.makeFieldL('UIfontSize');
		FTT.sEl.afterPost = new OO.ui.DropdownInputWidget( {
			options: [
				{ data: 'reload', label: FTT.msgs.reloadafter },
				{ data: 'parsepage', label: FTT.msgs.parsepage },
				{ data: 'parsecmtonly', label: FTT.msgs.parsecmtonly },
				{ data: 'parse', label: FTT.msgs.parse },
				{ data: 'link', label: FTT.msgs.linkafter },
				],
			value: FTT.settings.afterPost,
			id: 'FTTAfterPost',
			classes: [ 'FTTDropDownMenu' ],
		} );
		FTT.makeFieldL('afterPost');
		FTT.sEl.saveOptionsList = [ { data: 'browser', label: FTT.msgs.saveToBrowser }, { data: 'accountprefs', label: FTT.msgs.saveToAccountPrefs } ];
		if ( FTT.projectIsSULWiki ) {
//			FTT.debug('This appears to be a Wikimedia SUL wiki. Add option to save preferences in global preferences');
			FTT.sEl.saveOptionsList.push({ data: 'globalprefs', label: FTT.msgs.saveToGlobalPrefs });
		}
		if ( FTT.userName == null ) { //anon can't save to account preferences
			FTT.sEl.saveOptionsList[1].disabled = true;
			FTT.sEl.saveOptionsList[2].disabled = true;
		}
		FTT.sEl.saveTo = new OO.ui.DropdownInputWidget( {
			options: FTT.sEl.saveOptionsList,
			value: FTT.settings.saveTo,
			id: 'FTTSaveTo',
			classes: [ 'FTTDropDownMenu' ],
		} );
		FTT.makeFieldL('saveTo');
		FTT.makeTIW('pingText',FTT.msgs.pingTextPlaceholder);
		FTT.makeTIW('pingTextInSection',FTT.msgs.pingTextInSectionPlaceholder);
		FTT.makeTIW('neverPing',FTT.msgs.neverPingPlaceHolder);
		FTT.makeTIW('customBackground',FTT.B1['version-entrypoints-header-url']);FTT.sEl.customBackground.$element[0].id = 'FTTCustomBGSetting';
		FTT.bgbg = function(c){
			$('#FTTCustomBGSetting input')[0].style['background-color'] = c;
		};
		FTT.sEl.customBackground.on('change',function(val,isLast){
			isLast = new Date().getTime();
			FTT.BGisLast = isLast;
			var DelayBGCheck = setInterval(function () {
				clearInterval(DelayBGCheck);
				if ( isLast == FTT.BGisLast ) {
					val = FTT.sEl.customBackground.getValue();
					if ( val != '' ) {
						$.ajax( {	url: val, cache: true, type: 'GET', success: function(img,i,bg){
							FTT.bgHeader = img.slice(0,100);
							FTT.imgFormats = ['svg','SVG','JFIF','PNG','GIF89a','GIF87a','WEBP'];
							for (i=0;i<FTT.imgFormats.length;i++){
								if ( FTT.bgHeader.match(FTT.imgFormats[i]) ) {
									FTT.bgbg('rgba(0,255,0,0.3)');
									return;
								}
							}
							FTT.bgbg('rgba(255,255,0,0.3)');
						}, error: function(){
							FTT.bgbg('rgba(255,0,0,0.3)');
						}
						});
					}
				}
			},1000);
		});
		FTT.makeTIW('rewritunOther',FTT.msgs.rewritunOtherPlaceholder);
		FTT.makeTIW('runCIOther',FTT.msgs.rewritunOtherPlaceholder);
		FTT.makeTIW('AWBtyposOther',FTT.msgs.rewritunOtherPlaceholder);
		FTT.makeTIW('AWBtyposCustomTitle');
		FTT.makeTIW('exportSettings');
		FTT.makeTIW('importSettings');
		FTT.sEl.exportSettings.setReadOnly(true);
		FTT.sEl.exportSettings.setValue(JSON.stringify(FTT.customizedSettings));
		FTT.sEl.exportSettings.$input.on('click',function(){FTT.copyToClipBoard(0,'export');});
		FTT.sEl.cI = new OO.ui.MultilineTextInputWidget( {
			autosize: true,
			placeholder: FTT.msgs.cI,
			classes: ['FTTcIInput'],
			value: D2(FTT.settings.cI.join('\n'))
		});
		FTT.makeFieldL('cI');
		FTT.sEl.cIThatRun = new OO.ui.MultilineTextInputWidget( {
			autosize: true,
			placeholder: FTT.msgs.cIThatRun,
			classes: ['FTTcIInput'],
			value: D2(FTT.settings.cIThatRun.join('\n'))
		});
		FTT.makeFieldL('cIThatRun');
		FTT.sEl.cIThatRunCmt = new OO.ui.MultilineTextInputWidget( {
			autosize: true,
			classes: ['FTTcIInput'],
			value: D2(FTT.settings.cIThatRunCmt.join('\n'))
		});
		FTT.makeFieldL('cIThatRunCmt');
		FTT.UIversionDateLabel = new OO.ui.HtmlSnippet(FTT.VERSIONDATE + ' (' + (FTT.timestampInitEnd - FTT.timestampInit) + 'ms init/' + FTT.loadTimeTotal + FTT.maybeSkullNBones + 'ms total/<a class="triggerBugLink">' + FTT.msgs.reportBug + '</a>)');
		FTT.sEl.UIversionDate = new OO.ui.LabelWidget( {
			id: 'FTTUIversionDate',
			label: FTT.UIversionDateLabel,
			classes: [ 'FTTSmallerText' ]
		} );
		FTT.sEl.saveSettingsButton = new OO.ui.ButtonWidget( {
			id: 'saveSettingsButton',
			flags: [ 'primary', 'progressive' ],
		} );
		FTT.sEl.resetPreferencesButtonUnlock = new OO.ui.ButtonWidget( {
			label: FTT.msgs.resetPreferencesButtonUnlock,
			flags: [ 'primary', 'destructive' ],
		} );
		FTT.sEl.resetFTTSubs = new OO.ui.ButtonWidget( {
			label: FTT.msgs.resetLocalStorFTTSubs,
			flags: [ 'primary', 'destructive' ],
			disabled: true
		} );
		FTT.sEl.resetFTTSubs.on('click', function() { FTT.rmItemLS('FTTSubs');FTT.deleteSubsFromPrefs();mw.notify(FTT.B1.actioncomplete); });
		FTT.sEl.resetPreferencesButtonLocalStor = new OO.ui.ButtonWidget( {
			label: FTT.msgs.resetPreferencesButtonLocalStor,
			flags: [ 'primary', 'destructive' ],
			disabled: true
		} );
		FTT.sEl.resetPreferencesButtonLocalStor.on('click', function() { FTT.clearLocalStorage(); });
		FTT.sEl.resetPreferencesButton = new OO.ui.ButtonWidget( {
			flags: [ 'primary', 'destructive' ],
			disabled: true
		} );
		FTT.sEl.resetPreferencesButtonUnlock.on('click', function() { FTT.sEl.resetPreferencesButtonLocalStor.setDisabled(false);FTT.sEl.resetFTTSubs.setDisabled(false);FTT.sEl.resetPreferencesButton.setDisabled(false);FTT.sEl.resetPreferencesButtonUnlock.setDisabled(true);});
		FTT.sEl.resetPrefsButtonBar = new OO.ui.HorizontalLayout( {
			items: [
				FTT.sEl.resetFTTSubs,
				FTT.sEl.resetPreferencesButtonLocalStor,
				FTT.sEl.resetPreferencesButton,
				FTT.sEl.resetPreferencesButtonUnlock,
			],
			classes: [ 'FTTMarginHalfEmTop' ],
		} );
		FTT.sEl.resetPrefsButtonBarFieldLayout = FTT.sEl.resetPrefsButtonBar;//so loop picks it up
		FTT.togglePrefButtonLabel = function() {
			if ( FTT.sEl.saveTo.getValue() == 'browser' ) {
				FTT.sEl.resetPreferencesButton.setLabel(FTT.B1.restoreprefs + FTT.msgs.prefLabelBrowser);
				FTT.sEl.saveSettingsButton.setLabel(FTT.B1.saveprefs + FTT.msgs.prefLabelBrowser);
			} else if ( FTT.sEl.saveTo.getValue() == 'accountprefs' ) {
				FTT.sEl.resetPreferencesButton.setLabel(FTT.B1.restoreprefs + FTT.msgs.prefLabelAccountPrefs);
				FTT.sEl.saveSettingsButton.setLabel(FTT.B1.saveprefs + FTT.msgs.prefLabelAccountPrefs);
			} else if ( FTT.sEl.saveTo.getValue() == 'globalprefs' ) {
				FTT.sEl.resetPreferencesButton.setLabel(FTT.B1.restoreprefs + FTT.msgs.prefLabelGlobalPrefs);
				FTT.sEl.saveSettingsButton.setLabel(FTT.B1.saveprefs + FTT.msgs.prefLabelGlobalPrefs);
			}
		};
		FTT.togglePrefButtonLabel();
		FTT.sEl.saveTo.on('change', FTT.togglePrefButtonLabel );
		FTT.sEl.cancelSettingsButton = new OO.ui.ButtonWidget( {
			id: 'cancelSettingsButton',
			label: FTT.B1.cancel,
			flags:[ FTT.cancelFlag ]
		} );
		FTT.sEl.scrollUpSettingsButton = new OO.ui.ButtonWidget( {
			label: new OO.ui.HtmlSnippet(FTT.svgFTTChevronBlackIcon),
			id: 'scrollUpSett',
			classes: ['FTTFloatRight','FTTSVGChevronIcon180'],
			title:FTT.msgs.backToTop,
			framed: 0
		} );
		FTT.sEl.scrollUpSettingsButton.$button.attr('aria-label',FTT.msgs.backToTop);
		FTT.sEl.scrollUpSettingsButton.on('click',function(){
			$('#FTTSettings')[0].scrollIntoView({behavior:'smooth'});
			FTT.sEl.scrollUpSettingsButton.$button.blur();
		});
		FTT.sEl.scrollDownSettingsButton = new OO.ui.ButtonWidget( {
			label: new OO.ui.HtmlSnippet(FTT.svgFTTChevronBlackIcon),
			classes: ['FTTFloatRight'],
			title:FTT.msgs.backToBottom,
			framed: 0
		} );
		FTT.sEl.scrollDownSettingsButton.$button.attr('aria-label',FTT.msgs.backToBottom);
		FTT.sEl.scrollDownSettingsButton.on('click',function(){
			$('#FTTSettings')[0].scrollIntoView({behavior:'smooth',block:'end'});
			FTT.sEl.scrollDownSettingsButton.$button.blur();
		});
		$('#FTTSettingsTop').append(FTT.sEl.scrollDownSettingsButton.$element);
		FTT.sEl.saveSettingsButton.on('click', FTT.saveSettings );
		FTT.sEl.resetPreferencesButton.on('click', FTT.resetPreferences );
		FTT.sEl.cancelSettingsButton.on('click', FTT.closeSettings );
		FTT.sEl.SettingsButtonBar = new OO.ui.HorizontalLayout( {
			id: 'FTTSettingsButtonBar',
			items: [
				FTT.sEl.saveSettingsButton,
				FTT.sEl.cancelSettingsButton,
				FTT.sEl.scrollUpSettingsButton
			],
			classes: [ 'FTTMarginHalfEmTop' ],
		} );
		FTT.settingTabs = new OO.ui.IndexLayout({
			classes: ['FTTSettingTabs'],
			framed: false,
			expanded: false,
			autoFocus:false
		});
		FTT.prefCount = Object.keys(FTT.defaultSettings).length;
		FTT.tabs = {};
		if ( !FTT.msgsProcessed ) {
			for (FTT.prefint2 = 0; FTT.prefint2 < FTT.prefCount; FTT.prefint2++) {
				FTT.processKey = Object.keys(FTT.defaultSettings)[FTT.prefint2];
				if ( typeof FTT.msgs[FTT.processKey] == 'string' ) {
					if ( FTT.msgs[FTT.processKey].match(/^DUPLICATE:/) ) { //duplicated message
								FTT.msgs[FTT.processKey] = 'DUPE:'+FTT.msgs[FTT.msgs[FTT.processKey].slice(10)];
					}
				}
			}
			for (FTT.prefint2 = 0; FTT.prefint2 < FTT.prefCount; FTT.prefint2++) {
				FTT.processKey = Object.keys(FTT.defaultSettings)[FTT.prefint2];
				if ( typeof FTT.msgs[FTT.processKey] == 'string' ) {
					FTT.msgIsDupe = FTT.msgs[FTT.processKey].match(/^DUPE:/);
					if ( FTT.msgIsDupe ) {
						FTT.msgs[FTT.processKey] = FTT.msgs[FTT.processKey].slice(5).replace(/NODUPE[A-Za-z]+/g,''); //remove "NODUPExxx" from duplicated message as these are only to be expanded in the original
					}
					FTT.msgDUPE = (FTT.msgs[FTT.processKey].match(/(YES|NO)DUPE[A-Za-z]+/g) || [] );
					//look for messages within the message that need to be replaced with another message, e.g. replace "NODUPEbarRightAbove" with FTT.msgs.barRightAbove
					for (FTT.dupeInt=0;FTT.dupeInt<FTT.msgDUPE.length;FTT.dupeInt++) {
						FTT.msgs[FTT.processKey] = FTT.msgs[FTT.processKey].replace(FTT.msgDUPE[FTT.dupeInt],FTT.msgs[FTT.msgDUPE[FTT.dupeInt].replace(/(YES|NO)DUPE/,'')]);
					}
				}
			}
		}
		for (FTT.prefint = 0; FTT.prefint < FTT.prefCount; FTT.prefint++) {
			FTT.processKey = Object.keys(FTT.defaultSettings)[FTT.prefint];
//			FTT.debug('process/create element ' + (FTT.prefint + 1) + '/' + FTT.prefCount + ': ' + FTT.processKey);
			if ( typeof FTT.settings[FTT.processKey] == 'string' && FTT.settings[FTT.processKey] == 'TAB' ) {
//				FTT.debug('make new tab');
				FTT.processingTab = FTT.processKey;
				FTT.tabs[FTT.processingTab + 'Tab'] = new OO.ui.TabPanelLayout( FTT.processingTab, { label: FTT.msgs[FTT.processKey], expanded:false,classes:['FTTSettingTabs'] } );
				FTT.settingTabs.addTabPanels( [ FTT.tabs[FTT.processingTab + 'Tab'] ] );
				FTT.sEl['filterLabel'+FTT.processKey] = new OO.ui.LabelWidget( {
					label: new OO.ui.HtmlSnippet('<h2>'+FTT.msgs[FTT.processKey]+'</h2>'),
					classes: [ 'FTTFilterLabel','FTTNoDisplay' ]
				} );
				FTT.tabs[FTT.processingTab + 'Tab'].$element.append(FTT.sEl['filterLabel'+FTT.processKey].$element);
			//} else if ( typeof FTT.settings[FTT.processKey] != 'object' ) { //do not process the CI/runCI objects
			} else {
				if ( ! FTT.sEl[FTT.processKey] ) {
//					FTT.debug('make checkbox');
					if ( !FTT.msgsProcessed ) {
						if ( typeof FTT.B1[FTT.processKey] == 'string' ) { //for messages imported from MediaWiki which are stored in a different object
							FTT.msgs[FTT.processKey] = FTT.B1[FTT.processKey];
						}
						if ( typeof FTT.msgs[FTT.processKey] == 'string' && FTT.msgs[FTT.processKey].match(/<a/) ) {
							FTT.msgs[FTT.processKey] = new OO.ui.HtmlSnippet(FTT.msgs[FTT.processKey]);
						}
					}
					FTT.sEl[FTT.processKey] = new OO.ui.CheckboxInputWidget( {
						id: 'FTTPref' + FTT.processKey,
						selected: FTT.settings[FTT.processKey],
						classes: [ 'FTTMarginHalfEm' ],
					} );
					FTT.sEl[ FTT.processKey + 'FieldLayout' ] = new OO.ui.FieldLayout( FTT.sEl[FTT.processKey], { id: 'FTTPref' + FTT.processKey + 'FieldLayout', label: FTT.msgs[FTT.processKey], align: 'inline', classes: [ 'FTTOOuiFieldLayout' ] } );
					if ( FTT.processKey.match(/(noticeNeverPopup|stalkAddCycle|dateLinksIconAlt|dateLinksIconSectExtra|dateLinksLocalTime.|collapArticle|autoCollapse|collapIcons|submitShortcut|editorSwitchSkip|smartLivePreview|aggressiveLivePreview|discussionActivityTitleOnly|firstHeading.*|reverseCollapToC|redoBtn|undoBtn|undoShortcuts|onetimetools.|MFAdj.|editFullSH|autonum.)/) || FTT.processKey == 'AWBtypoPreview' ) {
						FTT.sEl[ FTT.processKey + 'FieldLayout' ].$element.addClass('FTTIndented');
					}
				}
			}
			if ( FTT.sEl[ FTT.processKey + 'FieldLayout' ] ) {
				FTT.tabs[FTT.processingTab + 'Tab'].$element.append(FTT.sEl[ FTT.processKey + 'FieldLayout' ].$element);
			}
		} //end for
		FTT.msgsProcessed = 1;
		FTT.tabs[FTT.processingTab + 'Tab'].$element.append(FTT.sEl.exportSettingsFieldLayout.$element);
		FTT.tabs[FTT.processingTab + 'Tab'].$element.append(FTT.sEl.importSettingsFieldLayout.$element);
		if ( FTT.userName == null ) { //anon can't save to account preferences
			FTT.sEl.stalkStoreInPrefs.setSelected(false);
			FTT.sEl.stalkStoreInPrefs.setDisabled(true);
		}
		FTT.showUnpopular = ['editRefs','scrollTop','scrollPrev','scrollNext','editLinks','replySecLink','nSecLink','firstHeadingAdd','nSecBottomLink','sectionIsNewTO','hideArchived','hideArchivedAll','editFullSHide','dateLinksIconAlt','hideToC','cureDTBlueStreak','markup','markupLink','onetimetoolsBlock','onetimetoolsList','redirAfterMove','clearEditFullPage','checkNewComments','previewAboveFull','previewAboveOther','previewBtns','overlayThreshold','markupAbove','barRightAbove','floatReturn','customSummary','spellcheck','bracketToFormFast','autoPing','quoteSelect','AWBtyposCustomTitle','autoPostAbove','enableCIThatRunCmt','cIThatRunCmt','saveDraft','pingText','stalkTackOnMail','stalkInterval','mobileMWCollapsible','showRisky','showSuperRisky','HLreply','HLCmtClick','editCmtDblClick','replyDblClick','reparseConfirm','blacklist'];
		FTT.showRisky = ['anoneditwarn','hideDT','hideDTStats','hideDTSub','afterPost','afterPostReload','watchlist','inputBoxTO','mwuibuttonTO','hideArchived','cureDTBlueStreak','limitWidth','warnExit','customBackground','markdown','sumSnippet','pingTextInSection','rewritun','rewritunOther','runCIOther','AWBtyposOther','hideAdvFE','loadMinervaD','extendedSigDetect','RLmasq','RLmasqSect','outdent','2010wikitextDefault','2010codepageDefault','2010templateDefault','preventDoubleHashtag','cancelDestructive','blacklist','blacklistMain','swapIcons','filterDirMarks','runCIAgain','runRewritunAgain','importSettings','exportSettings','stalkMaxSubsSize'];
		FTT.showSuperRisky = ['stalkWatchListCmts','theStranger','ninjaLoader','ninjaMobile','killswitch','useLocator','enableOnDiffOldId','methodLegacy','methodLocator','dryRun','editTheUneditable','recombineNowiki','rewriteOnBlur','2010codeMirror2023','loadMinervaM'];
		FTT.markRisky = function(int) {
			for(int=0;int<FTT.showUnpopular.length;int++){
				FTT.sEl[FTT.showUnpopular[int]+'FieldLayout'].$element.addClass('FTTUnpopular');
			}
			for(int=0;int<FTT.showRisky.length;int++){
				FTT.sEl[FTT.showRisky[int]+'FieldLayout'].$element.addClass('FTTRisky');
			}
//			FTT.sEl.debugFieldLayout.$element.addClass('FTTRisky');//FTT.debug
//			FTT.sEl.dbgLimitFieldLayout.$element.addClass('FTTRisky');//FTT.debug
			for(int=0;int<FTT.showSuperRisky.length;int++){
				FTT.sEl[FTT.showSuperRisky[int]+'FieldLayout'].$element.addClass('FTTSuperRisky');
			}
		};
		FTT.markRisky();
		FTT.toggleExtraOptions = function(int) {
//			FTT.debug('toggle extra options');
			FTT.sEl.dryRun.setDisabled(!FTT.sEl.onetimetools.isSelected());
			if ( ! FTT.sEl.collapsible.isSelected() ) {
				FTT.sEl.autoCollapse.setSelected(false);
				FTT.sEl.collapArticle.setSelected(false);
				FTT.sEl.collapArticleDefault.setSelected(false);
				FTT.sEl.collapArticleDefaultMF.setSelected(false);
			}
			FTT.sEl.stalkMarkReadScroll.setDisabled( ! FTT.sEl.markNewCmts.isSelected() && ! FTT.sEl.markNewCmtsSubbed.isSelected() );
			for(int=0;int<FTT.showUnpopular.length;int++){
				FTT.sToToggle[FTT.showUnpopular[int]+'FieldLayout'] = 'showUnpopular';
			}
			for(int=0;int<FTT.showRisky.length;int++){
				FTT.sToToggle[FTT.showRisky[int]+'FieldLayout'] = 'showRisky';
			}
//			FTT.sToToggle.debugFieldLayout = 'showRisky';//FTT.debug
//			FTT.sToToggle.dbgLimitFieldLayout = 'showRisky';//FTT.debug
			for(int=0;int<FTT.showSuperRisky.length;int++){
				FTT.sToToggle[FTT.showSuperRisky[int]+'FieldLayout'] = 'showSuperRisky';
			}
			for(FTT.settingToggleInt=0;FTT.settingToggleInt<Object.keys(FTT.sToToggle).length;FTT.settingToggleInt++){
				if ( ! FTT.filterSettingsInput || FTT.filterSettingsInput.getValue() == '' ) {
					FTT.sEl[Object.keys(FTT.sToToggle)[FTT.settingToggleInt]].toggle(FTT.sEl[Object.values(FTT.sToToggle)[FTT.settingToggleInt]].isSelected()); //hide option that can't be used without its parent from view
				}
				if ( Object.keys(FTT.sToToggle)[FTT.settingToggleInt].match(/FieldLayout/) ){ //disable option that can't be used without its parent so it'll show when filtering options but won't be enabled
					FTT.sEl[Object.keys(FTT.sToToggle)[FTT.settingToggleInt].replace(/FieldLayout/,'')].setDisabled(!FTT.sEl[Object.values(FTT.sToToggle)[FTT.settingToggleInt]].isSelected());
				}
			}
			FTT.sEl.collapArticleDefault.setDisabled(! FTT.sEl.collapArticle.isSelected());
			FTT.sEl.collapArticleDefaultMF.setDisabled(! FTT.sEl.collapArticle.isSelected());
			FTT.sEl.hideToC.setDisabled(FTT.sEl.floatingToC.isSelected());
			if ( FTT.sEl.editorSwitchSkipSource.isSelected() && FTT.sEl.editorSwitchSkip2010.isSelected() && FTT.sEl.editorSwitchSkipvisual.isSelected() ) { //enabled all three skip options, thus negating editorswitch completely
				FTT.sEl.editorSwitch.setSelected(false); //so we disable editorswitch
				FTT.sEl.editorSwitchSkipSource.setSelected(false);FTT.sEl.editorSwitchSkipvisual.setSelected(false); //enabling the default skip options as otherwise this becomes a catch-22 of being unable to enable editorswitch again
			}
			if ( FTT.userName == null ) { //anons can't ping or mail
				FTT.sEl.pingDropDown.setDisabled(true);
				FTT.sEl.pingDropDownAt.setDisabled(true);
				FTT.sEl.stalkTackOnMail.setDisabled(true);
			}
			FTT.sEl.dateLinksLocalTimeWeekday.setDisabled( !FTT.sEl.dateLinksLocalTimeWeekday.isSelected() && FTT.sEl.dateLinksLocalTimeWeekdayFull.isSelected() );
			FTT.sEl.dateLinksLocalTimeWeekdayFull.setDisabled( FTT.sEl.dateLinksLocalTimeWeekday.isSelected() && !FTT.sEl.dateLinksLocalTimeWeekdayFull.isSelected() );
			FTT.sEl.dateLinksLocalTimeNumMonth.setDisabled( !FTT.sEl.dateLinksLocalTimeNumMonth.isSelected() && FTT.sEl.dateLinksLocalTimeLongMonth.isSelected() );
			FTT.sEl.dateLinksLocalTimeLongMonth.setDisabled( FTT.sEl.dateLinksLocalTimeNumMonth.isSelected() && !FTT.sEl.dateLinksLocalTimeLongMonth.isSelected() );
			if ( FTT.sEl.undoFunc.isSelected() && !FTT.sEl.undoBtn.isSelected() && !FTT.sEl.redoBtn.isSelected() && !FTT.sEl.undoShortcuts.isSelected() ) {
				FTT.sEl.undoShortcuts.setSelected(true);
				FTT.sEl.undoFunc.setSelected(false);
			}
			if ( FTT.sEl.onetimetools.isSelected() && !FTT.sEl.onetimetoolsSearch.isSelected() && !FTT.sEl.onetimetoolsMove.isSelected() && !FTT.sEl.onetimetoolsBlock.isSelected() && !FTT.sEl.onetimetoolsList.isSelected() && !FTT.sEl.onetimetoolsArchive.isSelected() ) {
				FTT.sEl.onetimetoolsSearch.setSelected(true);
				FTT.sEl.onetimetools.setSelected(false);
			}
		};
		FTT.sToToggle = {'noticeNeverPopupFieldLayout':'editNotice','discussionActivityTitleOnlyFieldLayout':'discussionActivity','editorSwitchSkipSourceFieldLayout':'editorSwitch','editorSwitchSkip2010FieldLayout':'editorSwitch','editorSwitchSkipvisualFieldLayout':'editorSwitch','AWBtypoPreviewFieldLayout':'AWBtypos','dateLinksIconAltFieldLayout':'dateLinksIcon','dateLinksIconSectExtraFieldLayout':'dateLinksIconSection','stalkAddCycleBtnFieldLayout':'markNewCmts','stalkAddCycleBtnSubbedFieldLayout':'markNewCmtsSubbed','autoCollapseFieldLayout':'collapsible','collapArticleFieldLayout':'collapsible','collapArticleDefaultFieldLayout':'collapsible','collapArticleDefaultMFFieldLayout':'collapsible','submitShortcutFieldLayout':'shortcuts','reverseCollapToCFieldLayout':'reverseSectionOrder','collapIconsFieldLayout':'collapsible','cIFieldLayout':'enableCI','cIThatRunFieldLayout':'enableCIThatRun','cIThatRunCmtFieldLayout':'enableCIThatRunCmt','stalkWatchListCmtsFieldLayout':'showSuperRisky','undoBtnFieldLayout':'undoFunc','redoBtnFieldLayout':'undoFunc','undoShortcutsFieldLayout':'undoFunc','onetimetoolsSearchFieldLayout':'onetimetools','onetimetoolsMoveFieldLayout':'onetimetools','onetimetoolsBlockFieldLayout':'onetimetools','onetimetoolsListFieldLayout':'onetimetools','onetimetoolsArchiveFieldLayout':'onetimetools','editFullSHideFieldLayout':'editFullSection','editFullSHrefFieldLayout':'editFullSection','autonumCopyFieldLayout':'autonum','autonumPlainFieldLayout':'autonum','autonumScrollFieldLayout':'autonum'};
		FTT.MFAdjKeys = Object.keys(FTT.settings);
		for ( FTT.MFAdjInt=0;FTT.MFAdjInt<FTT.MFAdjKeys.length;FTT.MFAdjInt++) {
			FTT.MFAdjKey = Object.keys(FTT.settings)[FTT.MFAdjInt];
			if ( FTT.MFAdjKey.match(/^MFAdj./) && FTT.sEl[FTT.MFAdjKey+'FieldLayout'] ) {
				FTT.sToToggle[FTT.MFAdjKey+'FieldLayout'] = 'MFAdj';
			}
		}
		FTT.toggleExtraOptArray = ['showUnpopular','showRisky','showSuperRisky','floatingToC','onetimetools','editorSwitchSkipSource','editorSwitchSkip2010','editorSwitchSkipvisual','enableCI','enableCIThatRun','enableCIThatRunCmt','rewritun','collapArticle','collapArticleDefault','collapArticleDefaultMF','bracketToForm','dateLinksLocalTimeWeekday','dateLinksLocalTimeWeekdayFull','dateLinksLocalTimeNumMonth','dateLinksLocalTimeLongMonth','undoShortcuts','undoBtn','redoBtn','onetimetoolsSearch','onetimetoolsMove','onetimetoolsBlock','onetimetoolsList','onetimetoolsArchive','autonum'];
		FTT.toggleExtraOptArray = FTT.toggleExtraOptArray.concat(Object.values(FTT.sToToggle));
		FTT.toggleExtraOptArray = FTT.toggleExtraOptArray.filter(function(entry,pos){return FTT.toggleExtraOptArray.indexOf(entry) == pos;});
		for(FTT.toggleExtraInt=0;FTT.toggleExtraInt<FTT.toggleExtraOptArray.length;FTT.toggleExtraInt++){
			FTT.sEl[FTT.toggleExtraOptArray[FTT.toggleExtraInt]].on('change', FTT.toggleExtraOptions);
		}
//		FTT.debug('add settings button bar');
		FTT.sEl.tosNag.on('change', FTT.toggleTosNag );
		$('#FTTSettings').append(FTT.settingTabs.$element);
		if ( FTT.activeOverlay ) {
			$('#FTTSettings').prepend(FTT.sEl.SettingsButtonBar.$element);
		} else {
			$('#FTTSettings').append(FTT.sEl.SettingsButtonBar.$element);
		}
		$('#FTTSettings').append(FTT.sEl.UIversionDate.$element);
		FTT.sEl.dateLinksLocalTimeFieldLayout.setLabel(new OO.ui.HtmlSnippet(FTT.msgs.dateLinksLocalTime.replace(/\[([^\]]+)\]/,'<a id="FTTlocalDateOpt">$1</a>')));
		FTT.localDateOpt = ['dateLinksLocalTimeUserOptTZFieldLayout','dateLinksLocalTimeRelativeFieldLayout','dateLinksLocalTimeAbsoluteFieldLayout','dateLinksLocalTime12HFieldLayout','dateLinksLocalTimeNumMonthFieldLayout','dateLinksLocalTimeLongMonthFieldLayout','dateLinksLocalTimeWeekdayFieldLayout','dateLinksLocalTimeWeekdayFullFieldLayout'];
		FTT.localDateOptToggle = function(v,i) {
			for(i=0;i<FTT.localDateOpt.length;i++){
				FTT.sEl[FTT.localDateOpt[i]].toggle(v);
			}
		};
		FTT.localDateOptToggle(false); //initially hide local date customization options
		$('#FTTlocalDateOpt').on('click',function(event){
			event.stopPropagation();event.preventDefault();
			FTT.localDateOptToggle();
		});
		FTT.filterSettingsInput = new OO.ui.TextInputWidget({id:'FTTFilterSettings',title:FTT.msgs.filterTip,placeholder:FTT.B1['tag-filter-submit'],maxLength:20,classes:['FTTSearchSettingsInput']});
		FTT.filterSettingsInput.$input.attr('aria-label',FTT.msgs.filterTip);
		$('#FTTSettingsTop').prepend(FTT.filterSettingsInput.$element);
		FTT.FTTCreditTop = document.createElement('span');
		FTT.FTTCreditTop.classList = ['FTTCreditTop'];
		FTT.FTTCreditTop.innerHTML = FTT.msgs.FTTCreditLink;
		if ( FTT.activeOverlay ) {
			$('#FTTSettings').prepend(FTT.FTTCreditTop);
		} else {
			$('#FTTSettingsTop').prepend(FTT.FTTCreditTop);
		}
		$('.triggerBugLink').on('click',function(){FTT.addScrewedLink('I AM ERROR','Error triggered by user.');});
		FTT.filterSettingsInput.on('change',function(){FTT.filterSettings();});
		FTT.filterSettings = function() { // Setting filtering considers children and parents. If a child matches the term, its parent is also shown. All expert options are technically children of "show expert settings", so that one will appear quite frequently.
			$('#FTTnoresults').remove();
			if ( FTT.filterSettingsInput.getValue().length == 0 ) {
//				FTT.debug('filterSettings: restore settings to original display');
				for (FTT.restoreTabsInt=0;FTT.restoreTabsInt<Object.keys(FTT.tabs).length;FTT.restoreTabsInt++){
					FTT.tabs[Object.keys(FTT.tabs)[FTT.restoreTabsInt]].toggle(FTT.tabs[Object.keys(FTT.tabs)[FTT.restoreTabsInt]].isActive());
				}
				for (FTT.toggleAllInt=0;FTT.toggleAllInt<Object.keys(FTT.sEl).length;FTT.toggleAllInt++){
					if ( Object.keys(FTT.sEl)[FTT.toggleAllInt].match(/FieldLayout/) ) {
						FTT.sEl[Object.keys(FTT.sEl)[FTT.toggleAllInt]].toggle(true);
					}
				}
				$('.FTTFilterLabel').addClass('FTTNoDisplay');
				$('.FTTSettingTabs>.oo-ui-menuLayout-menu').removeClass('FTTNoDisplay');
				FTT.toggleExtraOptions();
			} else {
//				FTT.debug('filterSettings: toggle all tabs to true');
				for (FTT.restoreTabsInt=0;FTT.restoreTabsInt<Object.keys(FTT.tabs).length;FTT.restoreTabsInt++){
					FTT.tabs[Object.keys(FTT.tabs)[FTT.restoreTabsInt]].toggle(true);
				}
				$('.FTTFilterLabel').removeClass('FTTNoDisplay');
				$('.FTTSettingTabs>.oo-ui-menuLayout-menu').addClass('FTTNoDisplay');
				for (FTT.toggleAllInt=0;FTT.toggleAllInt<Object.keys(FTT.sEl).length;FTT.toggleAllInt++){
//					FTT.debug('filterSettings: process '+Object.keys(FTT.sEl)[FTT.toggleAllInt]);
					FTT.elementToToggleFL = FTT.sEl[Object.keys(FTT.sEl)[FTT.toggleAllInt]];
					FTT.elementToToggle = FTT.sEl[Object.keys(FTT.sEl)[FTT.toggleAllInt].replace(/FieldLayout/,'')];
					try {FTT.elementToToggleFLTest = $('#'+FTT.elementToToggle.getInputId())[0].attributes.placeholder.textContent.match(new RegExp(E1(FTT.filterSettingsInput.getValue()),'i'));} catch (e) {delete FTT.elementToToggleFLTest;}
					try {FTT.elementToToggleFLTestLabel = FTT.elementToToggleFL.getLabel().match(new RegExp(E1(FTT.filterSettingsInput.getValue()),'i'));} catch (e) {delete FTT.elementToToggleFLTestLabel;}
					if ( ! FTT.elementToToggleFLTestLabel ) {
						try {FTT.elementToToggleFLTestLabel = FTT.flattenWikiText(FTT.elementToToggleFL.getLabel().content).match(new RegExp(E1(FTT.filterSettingsInput.getValue()),'i'));} catch (e) {delete FTT.elementToToggleFLTestLabel;}
					}
					if ( Object.keys(FTT.sEl)[FTT.toggleAllInt].match(/FieldLayout/) ) { //.FTTFilterLabel gets ignored because it doesn't have a FieldLayout
						if ( FTT.elementToToggleFLTestLabel ) {
							FTT.elementToToggleFL.toggle(true);
							if(FTT.sToToggle[Object.keys(FTT.sEl)[FTT.toggleAllInt]]){
								FTT.settingElementParent = Object.values(FTT.sToToggle)[Object.keys(FTT.sToToggle).indexOf(Object.keys(FTT.sEl)[FTT.toggleAllInt])];
								FTT.sEl[FTT.settingElementParent+'FieldLayout'].toggle(true);
							}
						} else if ( FTT.elementToToggleFLTest ) {
							FTT.elementToToggleFL.toggle(true);
						} else {
							FTT.elementToToggleFL.toggle(false);
						}
					}
				}
				FTT.filterTabPanels = $('#FTTSettings .FTTSettingTabs[role=tabpanel]');
//				FTT.debug('filterSettings: hide labels for sections with no matching items');
				for (FTT.fTPInt=0;FTT.fTPInt<FTT.filterTabPanels.length;FTT.fTPInt++) {
					if ( ! $('#FTTSettings .FTTSettingTabs[role=tabpanel]:eq('+FTT.fTPInt+')>div:not(.oo-ui-element-hidden)').length ) {
						$('#FTTSettings .FTTSettingTabs[role=tabpanel]:eq('+FTT.fTPInt+')>.FTTFilterLabel').addClass('FTTNoDisplay');
					} else {
						$('#FTTSettings .FTTSettingTabs[role=tabpanel]:eq('+FTT.fTPInt+')>.FTTFilterLabel').removeClass('FTTNoDisplay');
					}
				}
				if ( ! $('.FTTFilterLabel:not(.FTTNoDisplay)')[0] ) {
					$('#FTTSettingsTop').append('<div id="FTTnoresults">'+FTT.B1['mw-widgets-mediasearch-noresults']+'</div>');
				} else {
					$('#FTTnoresults').remove();
				}
			}
		}; //end filterSettings
		FTT.toggleExtraOptions();
		FTT.adjustAutosize = function(name) {
			FTT.sEl[name].$input.on('focus',function(){
				FTT.sEl[name].adjustSize(true);
			});
		};
		FTT.adjustAutosize('cI');
		FTT.adjustAutosize('cIThatRun');
		FTT.adjustAutosize('cIThatRunCmt');
		FTT.adjustAutosize('blacklist');
		FTT.riskyPrepend = document.createElement('span');
		FTT.riskyPrepend.classList.add('FTTRiskyBlock');
		FTT.riskyPrepend.innerHTML = '&#x25B6;'; // ▶ BLACK RIGHT-POINTING TRIANGLE
		$('#FTTSettings .FTTRisky label,#FTTSettings .FTTSuperRisky label').prepend(FTT.riskyPrepend);
		FTT.applyModules('afterOpenSettings');
		FTT.magicScroll($('#FTTSettings')[0]);
	});
}; //end function opensettings
FTT.oneTimeToolsOpen = function() {
	if ( $('#onetimetools')[0] && $('#onetimetools')[0].classList.contains('FTTTools') && $('#onetimetools')[0].classList.contains('FTTNoDisplay') ) {
//		FTT.debug('oneTimeTools: was previously opened, unhiding');
		$('#onetimetools').removeClass('FTTNoDisplay');
		FTT.UITextInputSummaryMinorLayout.toggle(true);
		FTT.oTT.replaceThis.focus();
		return;
	} else if ( $('#onetimetools')[0] && $('#onetimetools')[0].classList.contains('FTTTools') ) {
//		FTT.debug('oneTimeTools: hiding');
		$('#onetimetools').addClass('FTTNoDisplay');
		if ( ! FTT.summaryVisible ) {
			FTT.UITextInputSummaryMinorLayout.toggle(false);
		}
		FTT.focusInput();
		return;
	}
	$('#onetimetools').addClass('FTTTools').removeClass('FTTNoDisplay');
	FTT.oTT = {};
	FTT.oTT.replaceThis = new OO.ui.TextInputWidget( {
		placeholder: 'Lorem | /([Ll])orem/g',
		classes: [ 'FTTMarginHalfEmTop', 'FTTMarginHalfEm' ],
		title: FTT.B1['wikieditor-toolbar-tool-replace-search']
	} );
	FTT.oTT.replaceThis.$input.attr('aria-label',FTT.B1['wikieditor-toolbar-tool-replace-search']);
	FTT.oTT.replaceWith = new OO.ui.TextInputWidget( {
		placeholder: 'Ipsum | $1psum',
		classes: [ 'FTTMarginHalfEmTop', 'FTTMarginHalfEm' ],
		title: FTT.B1['wikieditor-toolbar-tool-replace-replace']
	} );
	FTT.oTT.replaceWith.$input.attr('aria-label',FTT.B1['wikieditor-toolbar-tool-replace-replace']);
	FTT.oTT.chevron = new OO.ui.HtmlSnippet(FTT.svgFTTChevronIcon);
	FTT.oTT.findButton = new OO.ui.ButtonWidget( {
		id: 'FTTFindBtn',
		label: FTT.oTT.chevron,
		title: FTT.B1['wikieditor-toolbar-tool-replace-button-findnext'],
		flags: [ 'primary' ],
	} );
	FTT.oTT.findButton.$button.attr('aria-label',FTT.B1['wikieditor-toolbar-tool-replace-button-findnext']);
	FTT.oTT.findPrevButton = new OO.ui.ButtonWidget( {
		id: 'FTTFindPrevBtn',
		label: FTT.oTT.chevron,
		title: FTT.msgs.findPrev,
		flags: [ 'primary' ],
	} );
	FTT.oTT.findPrevButton.$button.attr('aria-label',FTT.msgs.findPrev);
	FTT.oTT.replaceButton = new OO.ui.ButtonWidget( {
		label: FTT.B1['wikieditor-toolbar-tool-replace-button-replace'],
		flags: [ 'primary' ],
	} );
	FTT.oTT.replaceAllButton = new OO.ui.ButtonWidget( {
		label: FTT.B1['wikieditor-toolbar-tool-replace-button-replaceall'],
		flags: [ 'primary' ],
	} );
	FTT.oTT.AWBtyposButton = new OO.ui.ButtonWidget( {
		label: FTT.msgs.AWBtyposButton,
		flags: [ 'primary' ],
	} );
	if ( M1('wgPageContentModel') != 'wikitext' ) {
		FTT.oTT.AWBtyposButton.toggle(false);
	}
	FTT.UIDryRunButton = new OO.ui.ButtonWidget( {
		id: 'FTTUIDryRunButton',
		label: 'Dry run',
		flags: [ 'primary', 'destructive' ],
	} );
	FTT.UIDryRunButton.on('click', function() { FTT.dryRunOnce = true;FTT.postReply1(FTT.PRMOpened,'dryrunbutton'); });
	if ( ! FTT.settings.dryRun ) {
		FTT.UIDryRunButton.toggle(false);
	}
	FTT.oTT.replaceButtonBar = new OO.ui.HorizontalLayout( {
		items: [FTT.oTT.findPrevButton,FTT.oTT.findButton,FTT.oTT.replaceButton,FTT.oTT.replaceAllButton,FTT.oTT.AWBtyposButton,FTT.UIDryRunButton],
		id: 'FTTReplaceButtonBar',
		classes: [ 'FTTMarginHalfEmTop' ]
	} );
	FTT.oTT.searchResults = new OO.ui.LabelWidget({
		label:''
	});
	FTT.oTT.searchResults.toggle(false);
	FTT.searchAndReplace = function(mode) {
//		FTT.oTT.SaRStart = new Date().getTime();//FTT.debug
		FTT.oTT.searchResults.toggle(true);
		if ( FTT.getRange().to > FTT.getRange().from ) {
			FTT.oTT.realStart = FTT.getRange().from;
			FTT.oTT.realEnd = FTT.getRange().to;
		} else {
			FTT.oTT.realStart = FTT.getRange().to;
			FTT.oTT.realEnd = FTT.getRange().from;
		}
		FTT.oTT.select = 0;
		FTT.oTT.isRegEx = 0;
		if ( FTT.oTT.replaceThis.getValue().match(/^\/.+\/[gmi]{0,3}$/) ) {
			FTT.oTT.replaceThisVal = new RegExp(FTT.oTT.replaceThis.getValue().match(/^\/(.*)\/([gmi]{0,3})$/)[1],FTT.oTT.replaceThis.getValue().match(/^\/(.*)\/([gmi]{0,3})$/)[2]);
			FTT.oTT.replaceWithVal = FTT.oTT.replaceWith.getValue();
			FTT.oTT.isRegEx = 1;
		} else if ( mode == 'all' ) {
			FTT.oTT.replaceThisVal = new RegExp(E1(FTT.oTT.replaceThis.getValue()), 'g');
			FTT.oTT.replaceWithVal = FTT.escapeReplacement(FTT.oTT.replaceWith.getValue());
		} else { //single replace or find
			FTT.oTT.replaceThisVal = FTT.oTT.replaceThis.getValue();
			FTT.oTT.replaceWithVal = FTT.escapeReplacement(FTT.oTT.replaceWith.getValue());
			FTT.oTT.select = 1;
		}
		if ( FTT.oTT.select ) {
			FTT.oTT.matchAll = new RegExp(E1(FTT.oTT.replaceThisVal),'g');
		} else if ( FTT.oTT.isRegEx ) {
			FTT.oTT.matchAll = new RegExp(FTT.oTT.replaceThisVal.source,FTT.oTT.replaceThisVal.flags.replace('g','')+'g');
		} else if ( mode == 'all' ) {
			FTT.oTT.matchAll = FTT.oTT.replaceThisVal;
		}
//		FTT.oTT.SaREnd = new Date().getTime();//FTT.debug
//		FTT.debug('searchAndReplace: took '+(FTT.oTT.SaREnd-FTT.oTT.SaRStart)+'ms to init');
//		FTT.oTT.SaRStartNew = new Date().getTime();//FTT.debug
		FTT.oTT.selectedText = FTT.getValue().slice(FTT.oTT.realStart,FTT.oTT.realEnd);
		FTT.oTT.selectedTextMatch = ( FTT.oTT.selectedText.match(FTT.oTT.replaceThisVal) || FTT.oTT.selectedText.match(FTT.oTT.replaceWithVal) );
		if ( FTT.oTT.select || mode == 'find' || mode == 'findPrev' ) {
			if ( FTT.oTT.selectedTextMatch ) {
//				FTT.debug('searchAndReplace: current selection matches search term');
				if ( mode == 'findPrev' ) {
					FTT.oTT.prevEndMatches = FTT.getValue().slice(0,FTT.oTT.realStart).match(FTT.oTT.matchAll);
					FTT.oTT.prevEndSplit = FTT.getValue().slice(0,FTT.oTT.realStart).split(FTT.oTT.matchAll);
					if ( ! FTT.oTT.prevEndMatches ) {
						FTT.oTT.lastMatch = FTT.getValue().match(FTT.oTT.matchAll);
						FTT.oTT.lastMatch = FTT.oTT.lastMatch[FTT.oTT.lastMatch.length -1].length;
						FTT.oTT.lastSplit = FTT.getValue().split(FTT.oTT.matchAll);
						FTT.oTT.lastSplit = FTT.oTT.lastSplit[FTT.oTT.lastSplit.length -1].length;
						FTT.oTT.selectEnd = FTT.getValue().length - FTT.oTT.lastSplit;
						FTT.oTT.selectStart = FTT.oTT.selectEnd - FTT.oTT.lastMatch;
						FTT.oTT.prevEndSplit = FTT.getValue().slice(0,FTT.oTT.realStart).split(FTT.oTT.matchAll);
					} else {
						FTT.oTT.prevEnd = FTT.oTT.prevEndSplit[FTT.oTT.prevEndSplit.length-1];
						FTT.oTT.prevEndMatch = FTT.oTT.prevEndMatches[FTT.oTT.prevEndMatches.length-1]; //get length of matches content
						FTT.oTT.selectEnd = (FTT.getValue().slice(0,FTT.oTT.realStart).length - FTT.oTT.prevEnd.length);
						FTT.oTT.selectStart = FTT.oTT.selectEnd - FTT.oTT.prevEndMatch.length;
					}
				} else {
					FTT.oTT.selectStart = FTT.oTT.realEnd + FTT.getValue().slice(FTT.oTT.realEnd).split(FTT.oTT.replaceThisVal)[0].length;
					if ( FTT.oTT.selectStart == FTT.getValue().length ) {
						FTT.oTT.selectStart = FTT.getValue().split(FTT.oTT.replaceThisVal)[0].length;
					}
				}
			} else {
				FTT.oTT.selectStart = FTT.getValue().split(FTT.oTT.replaceThisVal)[0].length;
			}
			if ( mode == 'find' ) {
				FTT.oTT.selectEnd = FTT.oTT.selectStart + FTT.getValue().match(FTT.oTT.replaceThisVal)[0].length;
			} else if ( mode != 'findPrev' ) {
				FTT.oTT.selectEnd = FTT.oTT.selectStart + FTT.oTT.replaceWithVal.length;
			}
		}
//		FTT.oTT.SaREnd = new Date().getTime();//FTT.debug
//		FTT.debug('searchAndReplace: took '+(FTT.oTT.SaREnd-FTT.oTT.SaRStartNew)+'ms for find');
//		FTT.oTT.SaRStartNew = new Date().getTime();//FTT.debug
		if ( mode != 'find' && mode != 'findPrev' ) {
			if ( FTT.oTT.selectedText.match(FTT.oTT.replaceThisVal) ) {
				FTT.oTT.selectStart = FTT.oTT.realStart;
				FTT.oTT.selectEnd = FTT.oTT.selectStart + FTT.oTT.selectedText.replace(FTT.oTT.replaceThisVal,FTT.oTT.replaceWithVal).length;
				FTT.setValue(
					FTT.getValue().slice(0,FTT.oTT.realStart) +
					FTT.oTT.selectedText.replace(FTT.oTT.replaceThisVal,FTT.oTT.replaceWithVal) +
					FTT.getValue().slice(FTT.oTT.realEnd)
				);
			} else if ( FTT.getValue().slice(FTT.oTT.realEnd).match(FTT.oTT.replaceThisVal) ) {
					FTT.setValue(FTT.getValue().slice(0,FTT.oTT.realEnd)+FTT.getValue().slice(FTT.oTT.realEnd).replace(FTT.oTT.replaceThisVal,FTT.oTT.replaceWithVal));
			} else {
				FTT.setValue(FTT.getValue().replace(FTT.oTT.replaceThisVal,FTT.oTT.replaceWithVal));
			}
		}
//		FTT.oTT.SaREnd = new Date().getTime();//FTT.debug
//		FTT.debug('searchAndReplace: took '+(FTT.oTT.SaREnd-FTT.oTT.SaRStartNew)+'ms for notfind');
//		FTT.oTT.SaRStartNew = new Date().getTime();//FTT.debug
		if ( FTT.oTT.select ) {
//			FTT.debug('oneTimeToolsOpen: call selectRange');
			FTT.selectRange(FTT.oTT.selectStart,FTT.oTT.selectEnd,1);
		}
//		FTT.oTT.SaREnd = new Date().getTime();//FTT.debug
//		FTT.debug('searchAndReplace: took '+(FTT.oTT.SaREnd-FTT.oTT.SaRStartNew)+'ms to select');
//		FTT.oTT.SaRStartNew = new Date().getTime();//FTT.debug
			FTT.oTT.total = FTT.getValue().match(FTT.oTT.matchAll);
			if ( ! FTT.oTT.total ) {
				FTT.oTT.searchResults.setLabel(FTT.B1['wikieditor-toolbar-tool-replace-nomatch']);
				return;
			}
		if ( FTT.oTT.total ) {
			FTT.oTT.totalBefore = FTT.getValue().slice(0,FTT.oTT.selectStart).match(FTT.oTT.matchAll);
			if ( FTT.oTT.totalBefore && FTT.oTT.selectedTextMatch ) {
				FTT.oTT.totalBeforeNum = (FTT.oTT.totalBefore.length+1).toString();
			} else {
				FTT.oTT.totalBeforeNum = '1';
			}
			FTT.oTT.searchResults.setLabel(FTT.oTT.totalBeforeNum + ' / ' + FTT.oTT.total.length);
		}
//		FTT.oTT.SaREnd = new Date().getTime();//FTT.debug
//		FTT.debug('searchAndReplace: took '+(FTT.oTT.SaREnd-FTT.oTT.SaRStartNew)+'ms for totals');
//		FTT.debug('searchAndReplace: took '+(FTT.oTT.SaREnd-FTT.oTT.SaRStart)+'ms');
		FTT.undoSave();
	};
	FTT.oTT.replaceThis.on('enter',function(){FTT.searchAndReplace('find');});
	FTT.oTT.replaceWith.on('enter',function(){FTT.searchAndReplace();});
	FTT.oTT.findButton.on('click',function() { FTT.searchAndReplace('find');});
	FTT.oTT.findPrevButton.on('click',function() { FTT.searchAndReplace('findPrev');});
	FTT.oTT.replaceButton.on('click',function() { FTT.searchAndReplace();});
	FTT.oTT.replaceAllButton.on('click',function() { FTT.searchAndReplace('all');});
	FTT.oTT.AWBtyposButton.on('click',function() { FTT.RETF(FTT.getValue(),'onetime');});
	FTT.genLinksList = function(listType) {
		if ( $('.mw-changeslist-diff')[0] ) {//todo: some special pages need another selector
			FTT.genLinksListElements = $('.mw-changeslist-diff');
			FTT.genLinksPageType = 'contribs';
		} else {
			FTT.genLinksListElements = $('#mw-content-text a:not(.external,.new,.ext-discussiontools-init-section-subscribe-link)');
			FTT.genLinksPageType = 'generic';
		}
		FTT.genLinksListCode = '\n';
		FTT.genLinksListArray = [];
		for (FTT.genLinksListElementsInt=0;FTT.genLinksListElementsInt<FTT.genLinksListElements.length;FTT.genLinksListElementsInt++) {
			delete FTT.genLinksProcessLink;
			if ( FTT.genLinksPageType == 'contribs' ) {
				FTT.genLinksProcessLink = FTT.genLinksListElements[FTT.genLinksListElementsInt].href;
			} else if ( FTT.genLinksListElements[FTT.genLinksListElementsInt].href.match(M1('wgArticlePath').replace('$1','')) ) {
				FTT.genLinksProcessLink = '[[' + FTT.genLinksListElements[FTT.genLinksListElementsInt].title + ']]';
			}
			if ( FTT.genLinksProcessLink && FTT.genLinksProcessLink != '[[]]' && FTT.genLinksListArray.indexOf(FTT.genLinksProcessLink) == -1 ) {
//				FTT.debug('oneTimeTools: adding ' + FTT.genLinksProcessLink + ' to list');
				FTT.genLinksListArray.push(FTT.genLinksProcessLink);
				FTT.genLinksListCode = FTT.genLinksListCode + '\n' + listType + FTT.genLinksProcessLink;
			}
		}
		if ( FTT.genLinksPageType == 'contribs' ) {
			FTT.genLinksListCode = FTT.rewritunUrUrlz(FTT.genLinksListCode).replace(/#.*/,'');
		}
		if ( FTT.genLinksListElements.length > 0 ) {
			FTT.setValue(FTT.getValue() + FTT.genLinksListCode);
		}
	};
	FTT.oTT.bulletedListButton = new OO.ui.ButtonWidget( {
		label: FTT.msgs.genBulletedList,
		flags: [ 'primary' ],
	} );
	FTT.oTT.numberedListButton = new OO.ui.ButtonWidget( {
		label: FTT.msgs.genNumberedList,
		flags: [ 'primary' ],
	} );
	FTT.oTT.bulletedListButton.on('click',function() { FTT.genLinksList('*');});
	FTT.oTT.numberedListButton.on('click',function() { FTT.genLinksList('#');});
	FTT.oTT.genListButtonBar = new OO.ui.HorizontalLayout( {
		items: [FTT.oTT.bulletedListButton,FTT.oTT.numberedListButton],
		classes: [ 'FTTMarginHalfEmTop' ],
	} );
	FTT.oTT.moveContentToPage = new OO.ui.ComboBoxInputWidget( {
		id:'FTTMoveToPage',
		options: [],//{data:'Option 1',label: 'Option One'}
		placeholder: FTT.msgs.moveContentToPage,
		title: FTT.msgs.moveContentToPage,
		spellcheck: false,
		autocomplete: false,
		menu: {filterFromInput: true},
		classes: [ 'FTTMarginHalfEm' ],
	} );
	FTT.oTT.moveContentToPage.$input.attr('aria-label',FTT.msgs.moveContentToPage);
	FTT.oTT.moveContentToPage.on('change',function() {
		FTT.canonicalLink = FTT.canonicalToLocal(FTT.oTT.moveContentToPage.getValue());
		if ( FTT.canonicalLink != FTT.oTT.moveContentToPage.getValue() ) {
			FTT.oTT.moveContentToPage.setValue(FTT.canonicalLink);
		}
		FTT.linkFieldChange('contentmove');
		if ( FTT.oTT.moveContentToPage.getValue() != '' ) {
			FTT.disableForm(false);FTT.oTT.allowPageCreation.setDisabled(false);
			FTT.oTT.moveContentToPage.setValue(FTT.rewritunUrUrlz(FTT.oTT.moveContentToPage.getValue(),'contentmove').replace(/^\[\[(:)?(.*)\]\]$/,'$2'));
			FTT.moveLabel = ' → ' + FTT.oTT.moveContentToPage.getValue();
			FTT.oTT.setSubmitLabel();
		} else {
			FTT.moveLabel = '';
			FTT.oTT.setSubmitLabel();
		}
	});
	FTT.oTT.allowPageCreation = new OO.ui.CheckboxInputWidget({ disabled:true });
	FTT.oTT.allowPageCreationFieldLayout = new OO.ui.FieldLayout( FTT.oTT.allowPageCreation, { label: FTT.msgs.allowPageCreation, align: 'inline'} );
	FTT.oTT.allowPageCreation.on('change',function() {
		FTT.linkFieldChange('contentmove');
		if ( FTT.oTT.allowPageCreation.isSelected() ) {
			FTT.UIReplyButton.setDisabled(false);
		}
	});
	FTT.oTT.moveContentToPageHorizontalLayout = new OO.ui.HorizontalLayout( {
		items: [FTT.oTT.moveContentToPage,FTT.oTT.allowPageCreationFieldLayout],
		classes: [ 'FTTMarginHalfEmTop' ],
	} );
	FTT.oTT.archiveSection = new OO.ui.CheckboxInputWidget({});
	FTT.oTT.archiveSectionFieldLayout = new OO.ui.FieldLayout( FTT.oTT.archiveSection, { label: FTT.msgs.archiveSection, align: 'inline', classes: [ 'FTTOOuiFieldLayout' ] } );
	FTT.moveLabel = '';
	FTT.archiveLabel = '';
	FTT.blockLabel = '';
	FTT.oTT.setSubmitLabel = function() {
		FTT.UIReplyButton.setLabel(FTT.B1['htmlform-submit'] + FTT.moveLabel + FTT.blockLabel + FTT.archiveLabel);
	};
	FTT.oTT.archiveSection.on('change',function(){
		if ( FTT.oTT.archiveSection.isSelected() ) {
			FTT.archiveLabel = ' + '+FTT.msgs.archiveSectionLabel;
		} else {
			FTT.archiveLabel = '';
		}
		FTT.oTT.setSubmitLabel();
	});
	FTT.oTTFormArray = [FTT.oTT.replaceThis,FTT.oTT.replaceWith,FTT.oTT.replaceButtonBar,FTT.oTT.searchResults,FTT.oTT.genListButtonBar,FTT.oTT.moveContentToPageHorizontalLayout,FTT.oTT.archiveSectionFieldLayout];
	FTT.UITextInputSummaryMinorLayout.toggle(true);
	if ( FTT.PRMOpened.type != 'comment' ) {
		FTT.oTT.archiveSectionFieldLayout.toggle(false);
	}
	FTT.oTT.replaceThis.toggle(FTT.settings.onetimetoolsSearch);
	FTT.oTT.replaceWith.toggle(FTT.settings.onetimetoolsSearch);
	FTT.oTT.replaceButtonBar.toggle(FTT.settings.onetimetoolsSearch);
	FTT.oTT.archiveSectionFieldLayout.toggle(FTT.settings.onetimetoolsArchive);
	FTT.oTT.genListButtonBar.toggle(FTT.settings.onetimetoolsList);
	if ( FTT.settings.onetimetoolsBlock && ! FTT.ownTalk && M1('wgCanonicalNamespace') == 'User_talk' && M1('wgTitle') == M1('wgRelevantUserName') && ( FTT.settings.debug || M1('wgUserGroups').includes('sysop') ) ) {
		FTT.oTT.blockModoptions = [];
		FTT.oTT.blockModipboptions = JSON.parse('{"' + FTT.B1.ipboptions.replace(/:([^,]*),/g,'":"$1","').replace(/:([^,]*)$/,'":"$1"}').replace(/ /g,'\ '));
		for(FTT.blockModperiodInt=0;FTT.blockModperiodInt<Object.keys(FTT.oTT.blockModipboptions).length;FTT.blockModperiodInt++){
			FTT.oTT.blockModoptions.push({data:Object.values(FTT.oTT.blockModipboptions)[FTT.blockModperiodInt],label:Object.keys(FTT.oTT.blockModipboptions)[FTT.blockModperiodInt]});
		}
		FTT.oTT.blockModoptions.push({data:'unblock',label:FTT.B1.unblock},{data:'',label:FTT.B1['checkbox-none']});
		FTT.oTT.blockMod = new OO.ui.ComboBoxInputWidget( {
			classes: [ 'FTTMarginHalfEm','FTTpermaLinkText'],
			placeholder: FTT.B1['log-action-filter-block-reblock'] + ' (' + FTT.B1.English + ')',
			options: FTT.oTT.blockModoptions,//{data:'Option 1',label: 'Option One'}
			spellcheck: false,
		} );
		FTT.oTT.blockModWarn = function() {
			if ( FTT.oTT.blockMod.getValue() != '' && FTT.oTT.blockMod.getValue() != 'unblock' ) {
				FTT.UIReplyButton.clearFlags();FTT.UIReplyButton.setFlags(['primary','destructive']);
				$('.FTTForm').addClass('FTTRedBG');
				FTT.oTT.blockModmail.setDisabled(false);
				FTT.oTT.blockModtalk.setDisabled(false);
				FTT.blockLabel = ' + '+FTT.B1.block+' ('+M1('wgRelevantUserName')+')';
				FTT.oTT.setSubmitLabel();
			} else {
				FTT.UIReplyButton.clearFlags();FTT.UIReplyButton.setFlags(['primary','progressive']);
				$('.FTTForm').removeClass('FTTRedBG');
				FTT.oTT.blockModmail.setDisabled(true);
				FTT.oTT.blockModtalk.setDisabled(true);
				FTT.blockLabel = '';
				FTT.oTT.setSubmitLabel();
			}
			if ( FTT.oTT.blockMod.getValue() == 'unblock' ) {
				FTT.blockLabel = ' + ' + FTT.B1.unblock + ' (' + M1('wgRelevantUserName') + ')';
				FTT.oTT.setSubmitLabel();
			}
		};
		FTT.oTT.blockMod.on('change',FTT.oTT.blockModWarn);
		FTT.oTT.blockModmail = new OO.ui.CheckboxInputWidget({disabled:true});
		FTT.oTT.blockModmailFieldLayout = new OO.ui.FieldLayout( FTT.oTT.blockModmail, { label: FTT.B1.ipbemailban, align: 'inline' } );
		FTT.oTT.blockModtalk = new OO.ui.CheckboxInputWidget({disabled:true});
		FTT.oTT.blockModtalkFieldLayout = new OO.ui.FieldLayout( FTT.oTT.blockModtalk, { label: FTT.B1['ipb-disableusertalk'], align: 'inline' } );
		FTT.oTT.blockModhorizontal = new OO.ui.HorizontalLayout( {
			items: [FTT.oTT.blockMod,FTT.oTT.blockModmailFieldLayout,FTT.oTT.blockModtalkFieldLayout],
			classes: [ 'FTTMarginHalfEmTop' ],
		} );
		FTT.oTTFormArray.push(FTT.oTT.blockModhorizontal);
	}
	FTT.oTTForm = new OO.ui.FormLayout( {
		items: FTT.oTTFormArray,
		id: 'oneTimeToolsForm',
		classes: ['FTTReplyForm'],
	} );
	$('#onetimetools').append(FTT.oTTForm.$element);
	$('#FTTReplaceButtonBar').append('<div id="FTTMatchedRETF"></div>');
	$('#FTTMoveToPage').on('click',function(){
		if ( ! FTT.moveToDefault && FTT.oTT.moveContentToPage.getValue() == '' ) {
			FTT.moveToDefault = true;
			if ( $('#FTTMoveToDefault')[0] ) {
				FTT.oTT.moveContentToPage.setValue($('#FTTMoveToDefault')[0].dataset.page.replace(/_/g,' '));
			}
		}
	});
	FTT.oTT.replaceThis.focus();
	if ( ! ( FTT.settings.onetimetoolsMove && ( FTT.PRMOpened.type == 'editFullPage' || ( typeof FTT.PRMOpened.sectionTitle == 'string' && FTT.PRMOpened.sectionTitle != '' ) ) ) ) {
		FTT.oTT.moveContentToPageHorizontalLayout.toggle(false);
	}
	$('#oneTimeToolsForm')[0].scrollIntoView(FTT.smoothScroll);
	if ( FTT.oTT.replaceThis && FTT.oTT.replaceThis.isVisible() ) {
		FTT.oTT.replaceThis.focus();
	}
};
FTT.getSectionByNum = function(wikiTextSectByNum, num, splitInt) {
//	FTT.debug('get section #' + num);
	if ( FTT.safedMarker && wikiTextSectByNum.match(new RegExp(FTT.safedMarker)) ) {
//		FTT.debug('wikitext has already been safed, skip safing');
		FTT.wikiTextSectByNumSafed = wikiTextSectByNum;
	} else {
//		FTT.debug('wikitext has not been safed yet, safing now');
		FTT.wikiTextSectByNumSafed = FTT.safeText(wikiTextSectByNum,undefined,'skiplinks');
	}
	FTT.sectionByNum = FTT.wikiTextSectByNumSafed.replace(/(^|\n)=/g,'$1FTTSECTION=');
	FTT.sectionByNumRegExp = new RegExp('FTTSECTION=.+=[ ]*(?:FTTSAFED[0-9]{4}HTMLCOMMENT[ ]*)*(?:$|\n)','g');
	FTT.sectionByNumSplit = FTT.sectionByNum.split(FTT.sectionByNumRegExp);
	FTT.sectionByNumMatch = FTT.sectionByNum.match(FTT.sectionByNumRegExp);
	FTT.sectionByNumMatch.unshift(''); //the lede has no section so we add an empty element at the start to sync these up
	if ( FTT.sectionByNumSplit[num] ) {
		FTT.sectionByNumHeaderLevel = FTT.sectionByNumMatch[num].trim().match(/FTTSECTION([\=]*)/)[1].length;
		FTT.sectionByNumWikiText = FTT.sectionByNumMatch[num] + FTT.sectionByNumSplit[num];
		for (splitInt = 0; splitInt < FTT.sectionByNumMatch.length; splitInt++) {
			if ( splitInt >= num && FTT.sectionByNumMatch[splitInt].trim().match(/FTTSECTION([\=]*)/)[1].length > FTT.sectionByNumHeaderLevel ) {
//				FTT.debug('add subsection #' + splitInt);
				FTT.sectionByNumWikiText = FTT.sectionByNumWikiText + FTT.sectionByNumMatch[splitInt] + FTT.sectionByNumSplit[splitInt];
			} else if ( splitInt > num && FTT.sectionByNumMatch[splitInt].trim().match(/FTTSECTION([\=]*)/)[1].length <= FTT.sectionByNumHeaderLevel ) {
				break;
			}
		}
		FTT.sectionByNumWikiTextTrim = FTT.sectionByNumWikiText.trim();
		FTT.wikiTextSectByNumSafedMarked = FTT.sectionByNum.replace(FTT.sectionByNumWikiTextTrim,'FTTGETSECTIONBYNUMSTART'+FTT.semiRandom+FTT.sectionByNumWikiTextTrim+'FTTGETSECTIONBYNUMSTART'+FTT.semiRandom);
		FTT.wikiTextSectByNumUnsafeRegExp = new RegExp('FTTGETSECTIONBYNUMSTART'+FTT.semiRandom+'([^]*)'+'FTTGETSECTIONBYNUMSTART'+FTT.semiRandom);
		FTT.wikiTextSectByNumUnsafe = FTT.safeText(FTT.wikiTextSectByNumSafedMarked,'unsafe').match(FTT.wikiTextSectByNumUnsafeRegExp)[1];
		return FTT.wikiTextSectByNumUnsafe.replace(/FTTSECTION=/g,'=');
	} else {
		return null;
	}
};
FTT.repairWikiText = function(text) {
//	FTT.debug('look for a time (like 12:00) followed by more than one newline followed by an indented message and reduce the number of newlines to one. Otherwise replies to that first post will go right under that post which is strictly speaking correct behavior (which is why I refuse to correct THAT), but not desired. Also: fuck you DiscussionTools.');
	return text.replace(/([0-9]{1,2}:[0-5][0-9].*)[\n]{2,}([\\:\\*\\#])/g, '$1\n$2').replace(/([^=])[ ]+\n/g,'$1\n'); //also remove spaces at the end of lines. Why people insert these only god knows. Maybe the mobile editor is bugged or something
};
FTT.extractUsernames = function(text,EUInt) {
	FTT.extractUsernamesPingTextRegExp = new RegExp('(' + E1(FTT.pingText).replace('PINGUSER', '([^\\]\\}]*)') + '|\\[\\[[:]?User:[^\\|\\]]*|\\[\\[[:]?' + FTT.NS[2] + ':[^\\|\\]]*|\{\{[Pp]ing\\|[^}]*|\{\{[Rr]eply to\\|[^}]*)', 'g'); //todo: *#!% gendered namespaces
	FTT.extractedUsernames = text.match(FTT.extractUsernamesPingTextRegExp);
	FTT.extractedUsernamesClean = [];
	if ( FTT.extractedUsernames ) {
		for (EUInt = 0; EUInt < FTT.extractedUsernames.length; EUInt++) {
			FTT.cleanUserPing = new RegExp('(\\[\\[:?[Uu]ser:|\\[\\[:?' + FTT.NS[2] + ':|\{\{[Pp]ing\\||\{\{[Rr]eply to\\||\\|\\]\\])', 'g');
			if ( ! FTT.extractedUsernames[EUInt].match(/\//) ) {
				FTT.extractedUsernamesClean.push(FTT.extractedUsernames[EUInt].replace(FTT.cleanUserPing, '').replace(/[\|\]].*/,''));
			}
		}
	}
	return FTT.extractedUsernamesClean;
};
FTT.pingFix = function(origText,newText,PFInt) {
//	FTT.debug('getting usernames mentioned in this comment (WP:PINGFIX)');
	FTT.usernamesInEditedComment = FTT.extractUsernames(origText);
	FTT.usernamesInUneditedComment = FTT.extractUsernames(newText);
	FTT.userMentionInSummary = '';
	for (PFInt = 0; PFInt < FTT.usernamesInEditedComment.length; PFInt++) {
//		FTT.debug('checking ' + FTT.usernamesInEditedComment[PFInt]);
		if ( FTT.usernamesInEditedComment[PFInt] && FTT.usernamesInUneditedComment.indexOf(FTT.usernamesInEditedComment[PFInt]) == -1 && ! FTT.userMentionInSummary.match(FTT.usernamesInEditedComment[PFInt] + '\\|') ) {
			FTT.userMentionInSummary = FTT.userMentionInSummary + ', [[' + FTT.NS[2] + ':' + FTT.usernamesInEditedComment[PFInt] + '|' + FTT.usernamesInEditedComment[PFInt] + ']]';
		}
	}
	return FTT.userMentionInSummary;
};
FTT.repairTagImbalance = function(text,imbalanceInt) {
//	FTT.debug('check for unbalanced \'\', \'\'\', <i>, <b>, <s> and <span> and return an appropriate set of closing tags.');
	if ( text && FTT.PRMOpened.type != 'edit' ) {
		FTT.balancingTags = '';
		FTT.balancingTextCleaned = text.replace(/<[n]owiki\>[^]*<\/[n]owiki\>/gm, '').replace(/\{\{([^\{\}]|[\{\}][^\{\}])*\}\}/gm, '').replace(/\{\{([^\{\}]|[\{\}][^\{\}])*\}\}/gm, '').replace(/\{\{([^\{\}]|[\{\}][^\{\}])*\}\}/gm, '').replace(/\{\{([^\{\}]|[\{\}][^\{\}])*\}\}/gm, '').replace(/\{\{([^\{\}]|[\{\}][^\{\}])*\}\}/gm, '');
		for (imbalanceInt = 0; imbalanceInt < 10; imbalanceInt++) { //removing complete tag combinations in the right order. times 10 to also catch nested tags
			FTT.balancingTextCleaned = FTT.balancingTextCleaned.replace(/<(?![\/]?[bis]\>|[\/]?span( [^>]*)?)/g, '').replace(/<span( [^\>]*)?\>([^<]|<[\/]?[bis]\>)*<\/span\>/gmi, '$2').replace(/<b( [^\>]*)?\>([^<]*|<[\/]?span[^\>]*\>|<[\/]?[is]\>)*<\/b\>/gmi, '$2').replace(/<i( [^\>]*)?\>([^<]*|<[\/]?[bs]\>|<[\/]?span[^\>]*\>)*<\/i\>/gmi, '$2').replace(/<s( [^\>]*)?\>([^<]*|<[\/]?[bi]\>|<[\/]?span[^\>]*\>)*<\/s\>/gmi, '$2');
		}
//		FTT.debug('text that will be checked for unbalanced tags:\n' + FTT.balancingTextCleaned);
		FTT.balancingOperations = 0; //prevent infinite loops in case something is screwed up
		FTT.balancingTextTwoSingleQuotes = FTT.balancingTextCleaned.match(/(^|[^\'])\'\'($|[^\'])/g);
		if ( FTT.balancingTextTwoSingleQuotes ) {FTT.balancingTextTwoSingleQuotes = FTT.balancingTextTwoSingleQuotes.length;}
		FTT.balancingTextThreeSingleQuotes = FTT.balancingTextCleaned.match(/(^|[^\'])\'\'\'($|[^\'])/g);
		if ( FTT.balancingTextThreeSingleQuotes ) {FTT.balancingTextThreeSingleQuotes = FTT.balancingTextThreeSingleQuotes.length;}
		if ( FTT.balancingTextTwoSingleQuotes / 2 != (FTT.balancingTextTwoSingleQuotes / 2).toFixed() ) {
			FTT.balancingOperations = FTT.balancingOperations + 1;
//			FTT.debug('adding closing \'\' tag (' + FTT.balancingOperations + ' tags added)');
			FTT.balancingTags = FTT.balancingTags + '\'\'';
		}
		if ( FTT.balancingTextThreeSingleQuotes / 2 != (FTT.balancingTextThreeSingleQuotes / 2).toFixed() ) {
			FTT.balancingOperations = FTT.balancingOperations + 1;
//			FTT.debug('adding closing \'\'\' tag (' + FTT.balancingOperations + ' tags added)');
			FTT.balancingTags = FTT.balancingTags + '\'\'\'';
		}
		FTT.balancingTextOpenSpan = FTT.balancingTextCleaned.match(/<span( [^\>]*)?\>/gi);
		FTT.balancingTextOpenB = FTT.balancingTextCleaned.match(/<b( [^\>]*)?\>/gi);
		FTT.balancingTextOpenI = FTT.balancingTextCleaned.match(/<i( [^\>]*)?\>/gi);
		FTT.balancingTextOpenS = FTT.balancingTextCleaned.match(/<s( [^\>]*)?\>/gi);
		if ( FTT.balancingTextOpenSpan ) {FTT.balancingTextOpenSpan = FTT.balancingTextOpenSpan.length;}
		if ( FTT.balancingTextOpenB ) {FTT.balancingTextOpenB = FTT.balancingTextOpenB.length;}
		if ( FTT.balancingTextOpenI ) {FTT.balancingTextOpenI = FTT.balancingTextOpenI.length;}
		if ( FTT.balancingTextOpenS ) {FTT.balancingTextOpenS = FTT.balancingTextOpenS.length;}
		while ( FTT.balancingOperations < 100 && 0+FTT.balancingTextOpenSpan > 0 ) {
			FTT.balancingOperations = FTT.balancingOperations + 1;
//			FTT.debug('adding closing span tag (' + FTT.balancingOperations + ' tags added)');
			FTT.balancingTextOpenSpan = FTT.balancingTextOpenSpan - 1;
			FTT.balancingTags = FTT.balancingTags + '</span>';
		}
		while ( FTT.balancingOperations < 100 && 0+FTT.balancingTextOpenB > 0 ) {
			FTT.balancingOperations = FTT.balancingOperations + 1;
//			FTT.debug('adding closing B tag (' + FTT.balancingOperations + ' tags added)');
			FTT.balancingTextOpenB = FTT.balancingTextOpenB - 1;
			FTT.balancingTags = FTT.balancingTags + '</b>';
		}
		while ( FTT.balancingOperations < 100 && 0+FTT.balancingTextOpenI > 0 ) {
			FTT.balancingOperations = FTT.balancingOperations + 1;
//			FTT.debug('adding closing I tag (' + FTT.balancingOperations + ' tags added)');
			FTT.balancingTextOpenI = FTT.balancingTextOpenI - 1;
			FTT.balancingTags = FTT.balancingTags + '</i>';
		}
		while ( FTT.balancingOperations < 100 && 0+FTT.balancingTextOpenS > 0 ) {
			FTT.balancingOperations = FTT.balancingOperations + 1;
//			FTT.debug('adding closing S tag (' + FTT.balancingOperations + ' tags added)');
			FTT.balancingTextOpenS = FTT.balancingTextOpenS - 1;
			FTT.balancingTags = FTT.balancingTags + '</s>';
		}
//		FTT.debug(FTT.balancingOperations +	' balancing tags added');
		return FTT.balancingTags;
	} else {
//		FTT.debug('no text input to balance');
		return '';
	}
};
FTT.getMostPopularIndent = function(fullPageText, sectionTextGetIndent) {
	if ( $('#useindenttype')[0] && typeof $('#useindenttype')[0].innerText != 'undefined' && $('#useindenttype')[0].innerText.match(/^[\\:\\*\\#$]/) && FTT.PRMOpened.pageTitle == M1('wgPageName').replace(/ /g,'_') ) {
//		FTT.debug('set indentation type by template #useindenttype');
		return $('#useindenttype')[0].innerText;
	}
	if ( sectionTextGetIndent ) {
		FTT.textSearchIndentation = sectionTextGetIndent;
	} else {
		FTT.textSearchIndentation = fullPageText;
	}
//	FTT.debug('find the most popular indentation type');
	FTT.countHashtagsRegExp = new RegExp('^\#.*' + FTT.signDateRegExpLocalMonths.source, 'gm');
	FTT.countColonsRegExp = new RegExp('^\:.*' + FTT.signDateRegExpLocalMonths.source, 'gm');
	FTT.countAsterisksColonsRegExp = new RegExp('^[\\*].*' + FTT.signDateRegExpLocalMonths.source, 'gm');
	FTT.textSearchIndentationCleaned = FTT.cleanTimestamp(FTT.textSearchIndentation);
	if ( FTT.textSearchIndentation.match(FTT.countHashtagsRegExp) ) {
		FTT.countHashtags = FTT.textSearchIndentationCleaned.match(FTT.countHashtagsRegExp).length; } else { FTT.countHashtags = 0;
	}
	if ( FTT.textSearchIndentation.match(FTT.countColonsRegExp) ) {
		FTT.countColons = FTT.textSearchIndentationCleaned.match(FTT.countColonsRegExp).length; } else { FTT.countColons = 0;
	}
	if ( FTT.textSearchIndentation.match(FTT.countAsterisksColonsRegExp) ) {
		FTT.countAsterisks = FTT.textSearchIndentationCleaned.match(FTT.countAsterisksColonsRegExp).length; } else { FTT.countAsterisks = 0;
	}
	FTT.countCombined = FTT.countHashtags + FTT.countColons + FTT.countAsterisks;
//	FTT.debug('total: ' + FTT.countCombined + ', hashtags: ' + FTT.countHashtags + ', colons: ' + FTT.countColons + ', asterisks: ' + FTT.countAsterisks);
	if ( FTT.countAsterisks >= FTT.countColons && FTT.countAsterisks >= FTT.countHashtags && FTT.countCombined > 0 ) {
//		FTT.debug('the asterisks have it');
		return '*';
	} else if ( FTT.countColons >= FTT.countAsterisks && FTT.countColons >= FTT.countHashtags && FTT.countCombined > 0 ) {
//		FTT.debug('the colons have it');
		return ':';
	} else if ( FTT.countHashtags > FTT.countColons || FTT.countHashtags > FTT.countAsterisks && FTT.countCombined > 0 ) {
//		FTT.debug('the hashtags have it');
		return '#';
	} else if ( sectionTextGetIndent ) {
//		FTT.debug('unable to determine indendation for section. try full page.');
		return FTT.getMostPopularIndent(fullPageText);
	} else {
		return FTT.commentTextIndentWikiDefault;
	}
};
FTT.stalkUnsubscribe = function(PRM,mode) {
	FTT.currentSubs = FTT.stalkGetSubs();
	if ( FTT.currentSubs[PRM.pageTitle.replace(/_/g,' ')] && FTT.currentSubs[PRM.pageTitle.replace(/_/g,' ')].subs && FTT.currentSubs[PRM.pageTitle.replace(/_/g,' ')].subs[PRM.sectionTitle.replace(/_/g,' ')] ) {
//		FTT.debug('unsubscribing from ' + PRM.sectionTitle);
		delete FTT.currentSubs[PRM.pageTitle.replace(/_/g,' ')].subs[PRM.sectionTitle.replace(/_/g,' ')];
		FTT.stalkSaveSubs(FTT.currentSubs);
		if ( mode == 'bellicon' ) {
			FTT.processElementArray[PRM.int].querySelectorAll('.FTTSVGBellStruckIcon')[0].classList.add('FTTSVGBellIcon');
			FTT.processElementArray[PRM.int].querySelectorAll('.FTTSVGBellStruckIcon')[0].classList.remove('FTTSVGBellStruckIcon');
			FTT.unsubcribedEl = FTT.processElementArray[PRM.int].querySelectorAll('.FTTSVGBellIcon')[0];
			FTT.unsubcribedEl.attributes.onclick.nodeValue = 'FTT.' + FTT.keyStalkSubscribe + '(FTT.' + FTT.keyPRM + '[' + PRM.int + '],\'bellicon\',null,event)';
			FTT.unsubcribedEl.title = FTT.msgs.subscribe;
			FTT.unsubcribedEl.querySelectorAll('.FTTScreenReaderLabel')[0].dataset.conetent = FTT.msgs.subscribe;
		}
	}
};
FTT.keyStalkUnsubscribe = Object.keys(FTT)[Object.values(FTT).indexOf(FTT.stalkUnsubscribe)];
FTT.stalkGetSubs = function(){
	if ( FTT.settings.stalkStoreInPrefs ) {
		FTT.currentFullSubs = FTT.testValidJSON(mw.user.options.get('userjs-FTTSubs')+(mw.user.options.get('userjs-FTTSubs2')||''));
		if ( FTT.currentFullSubs ) {
//			FTT.debug('stalkGetSubs: loaded subs from prefs');
		} else {
//			FTT.debug('init empty subs object');
			FTT.currentFullSubs = {};
		}
	} else {
		FTT.currentFullSubs = FTT.testValidJSON(FTT.getItemLS('FTTSubs'));
		if ( FTT.currentFullSubs ) {
//			FTT.debug('stalkGetSubs: loaded subs from localStorage');
		} else {
//			FTT.debug('init empty subs object');
			FTT.currentFullSubs = {};
		}
	}
	if ( FTT.currentFullSubs.FTTSubInfo ) {
		FTT.FTTSubInfo = FTT.currentFullSubs.FTTSubInfo;
		delete FTT.currentFullSubs.FTTSubInfo;
	} else {
		FTT.FTTSubInfo = 0;
	}
	return FTT.currentFullSubs;
};
FTT.stalkSaveSubs = function(subsObj){
	FTT.stalkMaxSubsSize = (Number(FTT.settings.stalkMaxSubsSize) * 1024)-1;
	FTT.currentSubs = subsObj;
	if ( ! FTT.currentSubs.FTTSubInfo ) {
		FTT.currentSubs.FTTSubInfo = FTT.FTTSubInfo;
	}
	FTT.newSubsLength = new Blob([FTT.pack(FTT.currentSubs,'base64')]).size;//depending on characters used, .length isn't the same as the byte length
	if ( FTT.newSubsLength > FTT.stalkMaxSubsSize ) {
		FTT.newSubsArr = [];
		FTT.newSubsObj = {};
		for ( FTT.newSubsInt=0;FTT.newSubsInt < Object.keys(FTT.currentSubs).length;FTT.newSubsInt++) {
			FTT.newSubsArr.push(FTT.currentSubs[FTT.newSubsInt].v);
			FTT.newSubsObj[FTT.currentSubs[FTT.newSubsInt].v] = Object.keys(FTT.currentSubs)[FTT.newSubsInt];
		}
		FTT.newSubsArrSorted = FTT.newSubsArr.sort(function(a, b){return a - b;});
	}
	while ( FTT.newSubsLength > FTT.stalkMaxSubsSize ) {
		FTT.stalkPageToUnSub = FTT.newSubsObj[FTT.newSubsArrSorted[0]];
//		FTT.debug('subscriptions JSON is greater than '+FTT.stalkMaxSubsSize+' bytes. Dumping oldest entry: ' + FTT.stalkPageToUnSub);
		delete FTT.currentSubs[FTT.stalkPageToUnSub];
		FTT.newSubsArrSorted.shift();
		FTT.newSubsLength = new Blob([FTT.pack(FTT.currentSubs,'base64')]).size;
	}
//	FTT.debug('subscriptions, under '+FTT.stalkMaxSubsSize+' bytes:');
//	FTT.debug(FTT.currentSubs);
	if ( FTT.settings.stalkStoreInPrefs ) {
		FTT.newPrefSubs = FTT.pack(FTT.currentSubs,'base64');
		FTT.queueUpdatePref('options','userjs-FTTSubs',FTT.newPrefSubs.slice(0,65035));
		FTT.queueUpdatePref('options','userjs-FTTSubs2',FTT.newPrefSubs.slice(65035));
	} else {
//		FTT.debug('stalkSaveSubs: write to localStorage');
		FTT.setItemLS('FTTSubs',FTT.pack(FTT.currentSubs,'UTF16'));
	}
};
FTT.stalkSubscribe = function(PRM,mode,wikitextID,linkElementSub) {
	delete FTT.stalkSectionID;
//	FTT.debug('subscribe to thread');
//	FTT.debug(PRM);
	FTT.stalkPageTitle = PRM.pageTitle.replace(/_/g,' ');
	if ( FTT.ownTalk && M1('wgPageName') == PRM.pageTitle.replace(/ /g,'_') ) {
//		FTT.debug('skip subscription to own talk page');
		return;
	}
	if ( wikitextID ) {
//		FTT.debug('got wikitextID as stalkSectionID from aborted run');
		FTT.stalkSectionID = wikitextID;
	} else if ( PRM.origReplyTo && PRM.origTimestamp && PRM.origTimestamp.match(/^[0-9]{13,14}$/) ) {
//		FTT.debug('use locator as stalkSectionID');
		FTT.stalkSectionID = PRM.origReplyTo + ':' + PRM.origTimestamp;
	} else if ( PRM.pageTitle && linkElementSub ) {
//		FTT.debug('search HTML for locator to use as stalkSectionID');
		FTT.stalkCheckElement = linkElementSub.target.parentElement;
		for (FTT.getStalkHeaderInt=0;FTT.getStalkHeaderInt<10;FTT.getStalkHeaderInt++) {
			FTT.stalkCheckElement = FTT.stalkCheckElement.parentElement;
			if ( FTT.stalkCheckElement.nodeName.match(/^H[1-6]$/) ) {
//				FTT.debug('found header to stalk');
				FTT.stalkSectionTitle = FTT.stalkCheckElement.childNodes[0].innerText;
				break;
			}
		}
		if ( ! FTT.stalkCheckElement.nodeName.match(/^H[1-6]$/) ) {
			FTT.addScrewedLink('subscribe section failure no header','Could not subscribe: could not locate header element.');
			return false;
		}
		for (FTT.subscribeElementsInt=0;FTT.subscribeElementsInt < 20;FTT.subscribeElementsInt++) {
//			FTT.debug('check next element ' + FTT.subscribeElementsInt + ' for locator');
			FTT.stalkCheckElement = FTT.stalkCheckElement.nextElementSibling;
//			FTT.debug(FTT.stalkCheckElement);
			if ( FTT.stalkCheckElement != null && ! FTT.stalkCheckElement.nodeName.match(/^H[1-6]$/) ) {
				FTT.stalkFindLocator = FTT.stalkCheckElement.innerHTML.match(/<span id="([^:]*:[0-9]{13,14})(:[^"]*)(" class="FTTCmt)/);
				if ( FTT.stalkFindLocator ) {
					FTT.stalkSectionID = FTT.stalkFindLocator[1];
//					FTT.debug('got stalkSectionID: ' + FTT.stalkSectionID);
					break;
				}
			} else {
//				FTT.debug('ran into next section, no locator found. will search wikitext for a timestamp instead');
				break;
			}
		}
	}
	if ( ! FTT.stalkSectionID ) {
//		FTT.debug('stalkSectionID not yet available, get a timestamp from wikitext');
		api.get( {action: 'query', export: 'true', format: 'json', titles: FTT.stalkPageTitle} ).then( function ( data ) {
//			FTT.debug(data);
			FTT.stalkWikiText = FTT.getWikitextFromExport(data.query.export["*"]);
			FTT.stalkWikiTextSection = FTT.getInsertionPointSection(PRM, FTT.stalkWikiText).sectiontext;
			if ( FTT.stalkWikiTextSection == FTT.stalkWikiText ) {
//				FTT.debug('couldn\'t locate section');
				FTT.addScrewedLink('subscribe section failure','Could not subscribe: could not locate section.');
				return false;
			} else {
				FTT.stalkWikiTextSection = FTT.cleanTimestamp(FTT.stalkWikiTextSection);
				FTT.stalkSectionSigDate = FTT.stalkWikiTextSection.match(FTT.signDateRegExpLocalMonths);
				if ( FTT.stalkSectionSigDate ) {
//					FTT.debug('found some signature timestamp in section to use as subscriptionID');
					FTT.stalkSectionIDRegExp = new RegExp('.*' + E1(FTT.stalkSectionSigDate[0]));
					FTT.stalkSectionID = FTT.stalkWikiTextSection.match(FTT.stalkSectionIDRegExp)[0];
					if ( FTT.stalkSectionID.length > ( FTT.stalkSectionSigDate[0].length + 30 ) ) {
						FTT.stalkSectionID = FTT.stalkSectionID.slice(0,30).replace(/ [^ ]*$/,'') + 'LEGACYTIMESTAMP' + FTT.stalkSectionSigDate[0];
					} else {
						FTT.stalkSectionID = FTT.stalkSectionID;
					}
					FTT.stalkSubscribe(PRM,mode,FTT.stalkSectionID);
					return;
				}
			}
			if ( ! FTT.stalkSectionID ) {
//				FTT.debug('no timestamp found to subscribe to');
				FTT.addScrewedLink('subscribe failure no timestamp','Could not subscribe: could not locate any timestamp within section.');
				return;
			}
		} );
		return false;
	}
	FTT.currentSubs = FTT.stalkGetSubs();
	if ( ! FTT.currentSubs[FTT.stalkPageTitle] ) {
		FTT.currentSubs[FTT.stalkPageTitle] = {};
	}
	if ( ! FTT.currentSubs[FTT.stalkPageTitle].c ) {
		FTT.currentSubs[FTT.stalkPageTitle].c = FTT.timestampInit;
	}
	if ( ! FTT.currentSubs[FTT.stalkPageTitle].subs ) {
		FTT.currentSubs[FTT.stalkPageTitle].subs = {};
	}
	FTT.stalkNewSub = FTT.currentSubs[FTT.stalkPageTitle];
	if ( typeof PRM.sectionTitle == 'string' ) {
		FTT.stalkSectionTitle = PRM.sectionTitle;
	} else if ( FTT.PRMOpened.type == 'newsection' ) {
		FTT.stalkSectionTitle = FTT.flattenWikiText(FTT.UITextInputTitle.getValue());
	} else if ( typeof FTT.stalkSectionTitle != 'string' ) {
		FTT.addScrewedLink('subscribe failure no sectiontitle','Could not subscribe: could not find the name of this section.');
		return;
	}
	if ( typeof FTT.stalkNewSub.subs == 'undefined' ) {
		FTT.stalkNewSub.subs = {};
	}
	if ( typeof FTT.stalkNewSub.subs[FTT.stalkSectionTitle] == 'undefined' ) {
		FTT.stalkNewSub.subs[FTT.stalkSectionTitle] = { 'id':FTT.stalkSectionID,'u':FTT.timestampInit}; //is not strictly the correct value for u (lastupdate), but it matters not as we just want to know if any comment was placed since the last time we viewed the page
		FTT.currentSubs[FTT.stalkPageTitle] = FTT.stalkNewSub;
//		FTT.debug('setting new subscriptions for page:');
//		FTT.debug(FTT.currentSubs[FTT.stalkPageTitle]);
		FTT.stalkSaveSubs(FTT.currentSubs);
		if ( mode == 'bellicon' ) {
			FTT.processElementArray[PRM.int].querySelectorAll('.FTTSVGBellIcon')[0].classList.add('FTTSVGBellStruckIcon');
			FTT.processElementArray[PRM.int].querySelectorAll('.FTTSVGBellIcon')[0].classList.remove('FTTSVGBellIcon');
			FTT.subcribedEl = FTT.processElementArray[PRM.int].querySelectorAll('.FTTSVGBellStruckIcon')[0];
			FTT.subcribedEl.attributes.onclick.nodeValue = 'FTT.' + FTT.keyStalkUnsubscribe + '(FTT.' + FTT.keyPRM + '[' + PRM.int + '],\'bellicon\')';
			FTT.subcribedEl.title = FTT.msgs.unsubscribe;
			FTT.subcribedEl.querySelectorAll('.FTTScreenReaderLabel')[0].dataset.conetent = FTT.msgs.unsubscribe;
		}
	}
};
FTT.keyStalkSubscribe = Object.keys(FTT)[Object.values(FTT).indexOf(FTT.stalkSubscribe)];
FTT.queryLV = function(arg1,LVWLInt,LVNSInt,newLVInt,LVstamp) { // get lastviewed timestamp from localStorage.FTTLV
	FTT.lastViewedObj = ( FTT.testValidJSON(FTT.getItemLS('FTTLV')) || {updated:0,LV:{} } );
	if ( ! FTT.LVThisPage ) {
		FTT.LVThisPage = ( FTT.lastViewedObj.LV[M1('wgPageName').replace(/_/g,' ')] || new Date().getTime() );
	}
	FTT.newLVLength = Object.values(FTT.lastViewedObj.LV).length;
	FTT.maxLVCached = 500;
	if ( FTT.newLVLength > FTT.maxLVCached * 1.1 ) {
		FTT.newLVValSort = Object.values(FTT.lastViewedObj.LV).sort().reverse();
		FTT.lastViewedObjKeys = Object.keys(FTT.lastViewedObj.LV); //page titles
		for ( newLVInt=0;newLVInt<FTT.newLVLength;newLVInt++) {
			if ( FTT.newLVValSort.indexOf(Number(FTT.lastViewedObj.LV[FTT.lastViewedObjKeys[newLVInt]])) > FTT.maxLVCached ) { // selecting pages that haven't been visited the longest to remove
//				FTT.debug('queryLV: tossing '+FTT.lastViewedObj.LV[FTT.lastViewedObjKeys[newLVInt]]+', last viewed '+ new Date(Number(FTT.lastViewedObj.LV[FTT.lastViewedObjKeys[newLVInt]])).toString());
				delete FTT.lastViewedObj.LV[FTT.lastViewedObjKeys[newLVInt]];
			}
		}
	}
	if ( FTT.userName && FTT.lastViewedObj.updated < new Date().getTime() - 600000 && typeof arg1 != 'string' ) { //if it's been more than 10 minutes the user may have been using another device. If T317153 gets resolved this code can be massively simplifed. Check FTT.userName because anons have no watchlist and result in kitty
		FTT.LVNS = M1('wgExtraSignatureNamespaces').toString().replace(/\,/g,'|');
		for (LVNSInt=0;LVNSInt<Object.keys(FTT.NS).length;LVNSInt++) { //collecting all uneven namespacenumbers as those are the talk namespaces
			if ( Object.keys(FTT.NS)[LVNSInt] != '-1' && [1,3,5,7,9].includes(Number(Object.keys(FTT.NS)[LVNSInt].slice(-1))) ) {
				FTT.LVNS = FTT.LVNS + '|'+Object.keys(FTT.NS)[LVNSInt];
			}
		}
		api.get( {action:'query',format:'json',list:'watchlist',wlshow:'unread',wlnamespace:FTT.LVNS,wlprop:'title|notificationtimestamp',wllimit:'250',wltype:'edit',wlend:new Date(new Date().getTime() -1209600000).toISOString(),wlexcludeuser:'Alexis Jazz'} ).then( function ( data ) { //last two weeks
			FTT.LVWLLength = data.query.watchlist.length;
			for ( LVWLInt=0;LVWLInt<FTT.LVWLLength;LVWLInt++ ) {
//				FTT.debug('queryLV: write notificationtimestamp from watchlist data for '+data.query.watchlist[LVWLInt].title+' to FTTLV in localStorage');
				FTT.lastViewedObj.LV[data.query.watchlist[LVWLInt].title] = new Date(data.query.watchlist[LVWLInt].notificationtimestamp).getTime();
			}
			FTT.LVtoLS();
		}, function ( code, data ) { FTT.APIError(code, data);
		});
	}
	if ( typeof arg1 == 'string' ) { //passed page title as argument
		LVstamp = ( FTT.lastViewedObj.LV[arg1] || new Date().getTime() );
	} else { // queryLV called from searchNodeContentsLoop
		LVstamp = FTT.LVThisPage;
	}
	FTT.LVtoLS(arg1);
	return LVstamp; //if the page title isn't found in FTTLV it's not watchlisted/too old/has no unread changes. Either way, by returning new Date() nothing gets highlighted
};
FTT.LVtoLS = function(arg) { //write FTTLV object to localStorage and update lastviewed timestamp for the current page
	FTT.lastViewedObj.updated = new Date().getTime();
	if ( arg == 'reparse' || arg == true ) { //called by searchNodeContentsLoop, so you're viewing the page as it is right now, so update the lastviewed timestamp
		FTT.lastViewedObj.LV[M1('wgPageName').replace(/_/g,' ')] = FTT.timestampInit; //update lastviewed for this page for autocollapse and subscription purposes
	}
	FTT.setItemLS('FTTLV',FTT.pack(FTT.lastViewedObj,'UTF16'));
};
FTT.stalkCheckSubscriptions = function() {
	FTT.getMonthNames();
	FTT.checkExistingSubs = FTT.stalkGetSubs();
	FTT.stalkSearchForMissingSectionActive = false;
	if ( FTT.settings.stalkWatchListCmts && typeof FTT.checkExistingSubs.unreadCmts != 'undefined' && M1('wgCanonicalSpecialPageName') == 'Watchlist' || ( M1('wgTitle') == 'FTTSubs' && M1('wgNamespaceNumber') == -1 ) ) {
//		FTT.debug('show you the unread comments you haven\'t acked yet');
		FTT.stalkWatchList();
		return;
	} else if ( FTT.settings.stalkWatchListCmts && typeof FTT.checkExistingSubs.unreadCmts != 'undefined' ) {
//		FTT.debug('you already have unread comments waiting for you. Not checking again until you ack those');
		mw.notify($('<a href="' + M1('wgArticlePath').replace('$1','Special:FTTSubs') + '">' + FTT.msgs.readYaCmts + '</a>'));
		return;
	}
	FTT.stalkTitlesToCheck = Object.keys(FTT.checkExistingSubs);
	FTT.stalkTitlesToCheckString = '';
	FTT.maxTitlesToCheck = 50;
	if ( FTT.stalkTitlesToCheck.length > FTT.maxTitlesToCheck ) {
//		FTT.debug('get check date for '+FTT.maxTitlesToCheck+'th page sorted by lastchecked so we only check the page that were checked the longest ago');
		FTT.lastCheckedArr = [];
		for(FTT.stalkTitlesToCheckInt=0;FTT.stalkTitlesToCheckInt<FTT.stalkTitlesToCheck.length;FTT.stalkTitlesToCheckInt++){
			FTT.lastCheckedArr.push(FTT.checkExistingSubs[FTT.stalkTitlesToCheck[FTT.stalkTitlesToCheckInt]].c);
		}
		FTT.lastCheckedArrOldCutOff = FTT.lastCheckedArr.sort()[FTT.maxTitlesToCheck];
	}
	for(FTT.stalkTitlesToCheckInt=0;FTT.stalkTitlesToCheckInt<FTT.stalkTitlesToCheck.length;FTT.stalkTitlesToCheckInt++){
		if ( typeof FTT.checkExistingSubs[FTT.stalkTitlesToCheck[FTT.stalkTitlesToCheckInt]].subs == 'object' && Object.keys(FTT.checkExistingSubs[FTT.stalkTitlesToCheck[FTT.stalkTitlesToCheckInt]].subs).length > 0 ) {
			if ( FTT.stalkTitlesToCheck.length <= FTT.maxTitlesToCheck || FTT.checkExistingSubs[FTT.stalkTitlesToCheck[FTT.stalkTitlesToCheckInt]].c < FTT.lastCheckedArrOldCutOff ) {
//				FTT.debug(FTT.stalkTitlesToCheck[FTT.stalkTitlesToCheckInt] + ' has subscriptions, adding to title list for api request');
				FTT.stalkTitlesToCheckString = FTT.stalkTitlesToCheck[FTT.stalkTitlesToCheckInt] + '|' + FTT.stalkTitlesToCheckString;
			}
		} else {
//			FTT.debug(FTT.stalkTitlesToCheck[FTT.stalkTitlesToCheckInt] + ' appears to have no subscriptions. In the past this was used to track what sections to collapse/which comments to mark as new, but as queryLV handles that now it\'s virtually useless');
			delete FTT.checkExistingSubs[FTT.stalkTitlesToCheck[FTT.stalkTitlesToCheckInt]];
		}
	}
	FTT.stalkTitlesToCheckString = FTT.stalkTitlesToCheckString.replace(/\|$/,'');
	FTT.stalkUnreadComments = {};
	if ( FTT.stalkTitlesToCheckString == '' ) {
//		FTT.debug('no titles to check?');
		return;
	}
	api.get( {action: 'query', format: 'json', prop:'revisions', titles: FTT.stalkTitlesToCheckString, rvprop:'timestamp', rvslots:'*'} ).then( function ( data ) {
//		FTT.debug('got revision data for subs:');
//		FTT.debug(data);
		FTT.getUserFromWikitext = new RegExp('\\[\\[[:]?(' + FTT.userNSWikitextRegExpPart + '|' + FTT.escapeRegExp(FTT.B1.Contributions) + '\/|Special:Contributions\/)([^\\]\\|#]*)');
		FTT.getUserFromWikitextGlobal = new RegExp(FTT.getUserFromWikitext.source,'g');
		FTT.stalkPagesWithChanges = [];
		FTT.tackOnEchoCmtsArray = [];
		for(FTT.stalkCheckUpdateInt=0;FTT.stalkCheckUpdateInt < Object.keys(data.query.pages).length;FTT.stalkCheckUpdateInt++){
			if ( Object.keys(data.query.pages)[FTT.stalkCheckUpdateInt] > 0 ) {
				FTT.stalkProcessTitle = data.query.pages[Object.keys(data.query.pages)[FTT.stalkCheckUpdateInt]].title;
				FTT.stalkLastUpdate = new Date(data.query.pages[Object.keys(data.query.pages)[FTT.stalkCheckUpdateInt]].revisions[0].timestamp).getTime();
				FTT.stalkLastviewKnown = FTT.queryLV(FTT.stalkProcessTitle);
				if ( FTT.stalkLastUpdate > FTT.stalkLastviewKnown && FTT.stalkProcessTitle != 'unreadCmts' ) {
//					FTT.debug('adding "' + FTT.stalkProcessTitle + '" to array to obtain wikitext as the last change to the page was after the last time you viewed the page');
					FTT.stalkPagesWithChanges.push(FTT.stalkProcessTitle);
				}
			}
		}
		if ( FTT.stalkPagesWithChanges.length > 0 ) {
			FTT.stalkUnreadCommentsAll = FTT.stalkGetSubs();
			api.post( {action: 'query', format: 'json', prop:'revisions', titles: FTT.stalkPagesWithChanges, rvprop:'content', rvslots:'*'} ).then( function ( data ) {
//				FTT.debug('obtained wikitext for changed pages:');
//				FTT.debug(data);
				FTT.stuff = data;
				for(FTT.stalkCheckUpdateInt=0;FTT.stalkCheckUpdateInt < Object.keys(data.query.pages).length;FTT.stalkCheckUpdateInt++){
					if ( Object.keys(data.query.pages)[FTT.stalkCheckUpdateInt] > 0 ) { // -1 usually means missing page so check if >0
						FTT.stalkProcessTitle = data.query.pages[Object.keys(data.query.pages)[FTT.stalkCheckUpdateInt]].title;
//						FTT.debug('search "' + FTT.stalkProcessTitle + '" for subscribed sections');
						FTT.stalkProcessContent = data.query.pages[Object.keys(data.query.pages)[FTT.stalkCheckUpdateInt]].revisions[0].slots.main["*"];
						FTT.stalkProcessContentSplit = FTT.stalkProcessContent.split('\n');
						FTT.stalkSubListForPage = Object.keys(FTT.checkExistingSubs[FTT.stalkProcessTitle].subs);
						for(FTT.stalkSubListForPageInt=0;FTT.stalkSubListForPageInt < FTT.stalkSubListForPage.length;FTT.stalkSubListForPageInt++) {
//							FTT.debug('getting section ID..');
							FTT.stalkSubbedSectionTitle = FTT.stalkSubListForPage[FTT.stalkSubListForPageInt];
							FTT.stalkTrySectionID = new RegExp(E1(FTT.checkExistingSubs[FTT.stalkProcessTitle].subs[FTT.stalkSubbedSectionTitle].id).replace('LEGACYTIMESTAMP','.*').replace(/[_ ]/g,'[_ ]'));
//							FTT.debug('search wikitext for "' + FTT.stalkTrySectionID + '" to find the section to analyze');
							FTT.stalkContentSectionNum = 0;
							FTT.stalkContentSectionNumFound = false;
							for(FTT.stalkContentLineInt=0;FTT.stalkContentLineInt < FTT.stalkProcessContentSplit.length;FTT.stalkContentLineInt++) {
								if ( FTT.stalkProcessContentSplit[FTT.stalkContentLineInt].match(FTT.stalkTrySectionID) ) {
//									FTT.debug('found section identifier');
									FTT.stalkContentSectionNumFound = true;
									break;
								} else if (FTT.stalkProcessContentSplit[FTT.stalkContentLineInt].match(/^=.*=[ ]*$/) ) {
									FTT.stalkContentSectionNum++;
								}
							}
							FTT.lastKnownUpdate = FTT.checkExistingSubs[FTT.stalkProcessTitle].subs[FTT.stalkSubbedSectionTitle].u;
							if ( FTT.stalkContentSectionNumFound ) {
								FTT.stalkFoundContentSectionText = FTT.getSectionByNum(FTT.stalkProcessContent,FTT.stalkContentSectionNum);
//								FTT.debug('got sectiontext to search for new comments:');
//								FTT.debug(FTT.stalkFoundContentSectionText);
								if ( typeof FTT.stalkFoundContentSectionText != 'string' ) { //not 100% sure why this might happen, but w:en:Special:Diff/1110806808 suggests it can
//									FTT.debug('stalkCheckSubscriptions: sectiontext for section title "'+FTT.stalkSubbedSectionTitle+'" on page "'+FTT.stalkProcessTitle+'" is not a string. stalkTrySectionID: '+FTT.stalkTrySectionID.toString()+', maybe try getSectionByNum(FTT.stalkProcessContent,'+FTT.stalkContentSectionNum);
									continue;
								}
								FTT.stalkFoundContentSectionTextSplit = FTT.stalkFoundContentSectionText.split('\n');
								FTT.stalkUnreadCommentMaybe = '';
								FTT.stalkSectionAnchor = '';
								FTT.stalkLastViewedThisPage = FTT.queryLV(FTT.stalkProcessTitle);
								FTT.stalkLastCheckedThisPage = FTT.stalkUnreadCommentsAll[FTT.stalkProcessTitle.replace(/_/g,' ')].c;
								for(FTT.stalkFoundSectionSplitInt=0;FTT.stalkFoundSectionSplitInt < FTT.stalkFoundContentSectionTextSplit.length;FTT.stalkFoundSectionSplitInt++){
									FTT.stalkAnalyzeLine = FTT.stalkFoundContentSectionTextSplit[FTT.stalkFoundSectionSplitInt];
//									FTT.debug('line: "' + FTT.stalkAnalyzeLine);
									FTT.stalkTimestampMaybe = ( FTT.stalkAnalyzeLine.match(/span id="([^"]*:([0-9]{13,14}):[^"]*)" class="FTTCmt"/) || FTT.cleanTimestamp(FTT.stalkAnalyzeLine).match(FTT.signDateRegExpLocalMonths) );
									if ( FTT.stalkAnalyzeLine.match(/^=.*=$/) ) {
//										FTT.debug('that\'s a section header, not a comment');
										FTT.stalkSectionAnchor = FTT.flattenWikiText(FTT.stalkAnalyzeLine.replace(/^[= ]*(.*[^= ])[= ]*$/,'$1'));
									} else if ( FTT.stalkTimestampMaybe ) {
//										FTT.debug('line contains a timestamp');
										if ( FTT.stalkTimestampMaybe[2] ) {
//											FTT.debug('locator timestamp found');
											FTT.stalkTimestamp = FTT.stalkTimestampMaybe[2];
										} else {
											FTT.stalkTimestamp = FTT.sigDateToMachineReadable(FTT.stalkTimestampMaybe[0]);
										}
										if ( FTT.stalkTimestamp > FTT.stalkLastViewedThisPage && FTT.stalkTimestamp > FTT.stalkLastCheckedThisPage ) {
//											FTT.debug('A NEW COMMENT!! (stalkTimestamp: '+FTT.stalkTimestamp+', stalkLastViewedThisPage: ' + FTT.stalkLastViewedThisPage + ', stalkLastCheckedThisPage: ' + FTT.stalkLastCheckedThisPage);
											if ( FTT.lastKnownUpdate < FTT.stalkTimestamp ) {
												FTT.checkExistingSubs[FTT.stalkProcessTitle].subs[FTT.stalkSubbedSectionTitle].u = FTT.lastKnownUpdate;
											}
											FTT.stalkGetUserFromWikiText = FTT.stalkAnalyzeLine.match(FTT.getUserFromWikitextGlobal);
											if ( ! FTT.stalkGetUserFromWikiText ) {
//												FTT.debug('stalkCheckSubscriptions: found a line with a timestamp, and it\'s new, but no associated user could be extracted: '+FTT.stalkAnalyzeLine);
												continue;
											}
											FTT.stalkGetLastUser = FTT.stalkGetUserFromWikiText[FTT.stalkGetUserFromWikiText.length - 1].match(FTT.getUserFromWikitext)[2].replace(/_/g,' ');
											FTT.stalkNewNotification = {'date':FTT.stalkTimestamp,'at':M1('wgServerName')+M1('wgArticlePath'),'page':FTT.stalkProcessTitle,'icon':'FTT','user':FTT.stalkGetLastUser,'section':FTT.stalkSectionAnchor,'msg':'newcmt'};
											if ( FTT.stalkGetLastUser != FTT.userName ) {//don't notify me about my own comments
												FTT.tackCArrDupe = false;
												for (FTT.tackCArrI=0;FTT.tackCArrI<FTT.tackOnEchoCmtsArray.length;FTT.tackCArrI++) {
													if ( FTT.tackOnEchoCmtsArray[FTT.tackCArrI].page == FTT.stalkProcessTitle && FTT.tackOnEchoCmtsArray[FTT.tackCArrI].section == FTT.stalkSectionAnchor ) {
//														FTT.debug('stalkCheckSubscriptions: there\'s already a notification for this page+section, make existing notification plural and skip this one');
														FTT.tackOnEchoCmtsArray[FTT.tackCArrI].msg = 'newcmts';
														FTT.tackCArrDupe = true;
													}
												}
												if ( ! FTT.tackCArrDupe ) {
													FTT.tackOnEchoCmtsArray.push(FTT.stalkNewNotification);
//													FTT.debug('pushed new notification for comment by ' + FTT.stalkGetLastUser + ' into tackOnEchoCmtsArray:');
												}
											}
//											FTT.debug(FTT.tackOnEchoCmtsArray);
											while ( typeof FTT.stalkUnreadComments[FTT.stalkTimestamp] != 'undefined' ) {
												FTT.stalkTimestamp = FTT.stalkTimestamp+1;//to ensure uniqueness for our array. bad things might happen when exceeding 60000 comments/minute, but Mediawiki will collapse long before that
											}
											FTT.stalkUnreadComments[FTT.stalkTimestamp] = '[[' + FTT.stalkProcessTitle + '#' + FTT.stalkSectionAnchor + '|&nbsp;&nbsp;→&nbsp;&nbsp;]]' + FTT.stalkUnreadCommentMaybe + FTT.stalkAnalyzeLine;
											FTT.stalkUnreadCommentMaybe = '';
										} else {
//											FTT.debug('old news (' + FTT.stalkTimestamp + '), maximum age is ' + FTT.stalkLastViewedThisPage + ' (' + new Date(FTT.stalkLastViewedThisPage) + ') for v (lastviewed) and ' + FTT.stalkLastCheckedThisPage + ' (' + new Date(FTT.stalkLastCheckedThisPage) + ') for c (lastchecked)');
										}
										FTT.stalkUnreadCommentMaybe = '';
									} else {
										FTT.stalkUnreadCommentMaybe = FTT.stalkUnreadCommentMaybe + '\n' + FTT.stalkAnalyzeLine;
									}
								}
								FTT.stalkUnreadCommentsAll.unreadCmts = FTT.stalkUnreadComments;
							} else {
//								FTT.debug('subscribed section NOT FOUND, tossing subscription (if it just moved we\'ll resubscribe later)');
								FTT.lostSubLastUpdate = FTT.checkExistingSubs[FTT.stalkProcessTitle].subs[FTT.stalkSubbedSectionTitle].u;
								FTT.lostSubID = FTT.checkExistingSubs[FTT.stalkProcessTitle].subs[FTT.stalkSubbedSectionTitle].id;
								if ( FTT.lastKnownUpdate < ((new Date().getTime())-259200000) ) {
//									FTT.debug('last known update for "' + FTT.stalkSubbedSectionTitle + '" > 3 days ago, assume archived, nothing to do');
									delete FTT.checkExistingSubs[FTT.stalkProcessTitle].subs[FTT.stalkSubbedSectionTitle];
								} else {
//									FTT.debug('last known update for "' + FTT.stalkSubbedSectionTitle + '" < 3 days ago, but where did it go??');
									FTT.searchForMissingSectionID = FTT.checkExistingSubs[FTT.stalkProcessTitle].subs[FTT.stalkSubbedSectionTitle].id;
									delete FTT.checkExistingSubs[FTT.stalkProcessTitle].subs[FTT.stalkSubbedSectionTitle];
									if ( FTT.searchForMissingSectionID.match(/LEGACYTIMESTAMP/) ) {
										FTT.searchForMissingSectionTerm = 'insource:"' + FTT.searchForMissingSectionID.split('LEGACYTIMESTAMP')[0] + '" insource:"' + FTT.searchForMissingSectionID.split('LEGACYTIMESTAMP')[1] + '"';
										FTT.searchForMissingSectionRegExp = new RegExp(E1(FTT.searchForMissingSectionID.split('LEGACYTIMESTAMP')[0]) + '.*' + E1(FTT.searchForMissingSectionID.split('LEGACYTIMESTAMP')[1]));
									} else {
										FTT.searchForMissingSectionTerm = 'insource:"' + FTT.searchForMissingSectionID + '"';
										FTT.searchForMissingSectionRegExp = new RegExp(E1(FTT.searchForMissingSectionID));
									}
									FTT.searchForMissingSectionTitles = [];
									FTT.searchForMissingSectionParams = {'action':'query','format':'json','formatversion':'2','list':'search','srsearch':FTT.searchForMissingSectionTerm,'srprop':'','srnamespace':'*','srlimit':'10'};
									FTT.stalkSearchForMissingSection(FTT.searchForMissingSectionParams);
								}
								//last known update > 3 days ago: toss it. last known update < 3 days ago? track it and update the pagetitle, toss if not trackable
								//lost cause for this run, just get it next time
							}
						}//end loop stalkSubListForPageInt
						FTT.checkExistingSubs[FTT.stalkProcessTitle].c = new Date().getTime();
					} else {
//						FTT.debug('requested invalid page name (negative stalkCheckUpdateInt)');
					}
				}//end loop to analyze wikitext of one page (stalkCheckUpdateInt)
				FTT.checkExistingSubs.FTTSubInfo = FTT.timestampInit;
				FTT.stalkSaveSubs(FTT.checkExistingSubs);
				if ( typeof FTT.stalkUnreadCommentsAll.unreadCmts == 'undefined' ) {
//					FTT.debug('there are no unread comments to display');
					return;
				} else if ( FTT.settings.stalkWatchListCmts || FTT.settings.stalkTackOnEcho ) {
					if ( FTT.settings.stalkWatchListCmts ) {
//						FTT.debug('run stalkWatchList()');
						FTT.stalkWatchList();
					}
					if ( FTT.settings.stalkTackOnMail && FTT.tackOnEchoCmtsArray.length > 0 ) {
						FTT.stalkTackOnMail(FTT.tackOnEchoCmtsArray);
					}
					if ( FTT.settings.stalkTackOnEcho && FTT.tackOnEchoCmtsArray.length > 0 ) {
//						FTT.debug('add notifications for new comments to tackOnEcho');
						FTT.tackOnEchoWrite(FTT.tackOnEchoCmtsArray);
					} else {
						FTT.tackOnEchoIconHack();
					}
				}
			}, function ( code, data ) { FTT.APIError(code, data);
			});
		} else {
//			FTT.debug('no pages appear to have changed since you last viewed them');
			FTT.tackOnEchoIconHack();
		}
	}, function ( code, data ) { FTT.APIError(code, data);
	});
};
FTT.stalkTackOnMail = function(newMsgs,mailInt) {
	FTT.mailText = FTT.msgs.mailHeader+'\n';
	for(mailInt=0;mailInt<Object.keys(newMsgs).length;mailInt++) {
		FTT.mailLink = D1(newMsgs[Object.keys(newMsgs)[mailInt]].page.replace(/ /g,'_')) + '?FTTScrToUsr=' + D1(newMsgs[Object.keys(newMsgs)[mailInt]].user) + '&FTTScrToTime=' + newMsgs[mailInt].date;
		FTT.mailText = FTT.mailText+FTT.msgs.mailBody.replace(/USER/g,newMsgs[mailInt].user).replace(/DATE/g,new Date(Number(newMsgs[mailInt].date)).toLocaleString(undefined,{year:'numeric',month:'short',weekday:undefined,day:'numeric',hour:'2-digit',minute:'2-digit',hour12:false})).replace(/LINK/g,'https://' + newMsgs[mailInt].at.replace('$1',FTT.mailLink));
	}
	FTT.mailText = FTT.mailText + '\n'+FTT.msgs.mailFooter;
	api.postWithEditToken( {format: 'json', action: 'emailuser', target:FTT.userName, subject:FTT.msgs.mailSubject, text:FTT.mailText } ).then( function ( data ) {
//		FTT.debug('stalkTackOnMail: mail delivered');
	}, function ( code, data ) {
		if ( code == 'internal_api_error_ThrottledError' ) {
			mw.notify(FTT.B1.mailerror.replace('$1',code));
			return;
		}
		FTT.APIError(code, data);
	});
};
FTT.stalkSearchForMissingSection = function(params) {
	if ( FTT.stalkSearchForMissingSectionActive ) {
		var DelayStalkSearchForMissingSection = setInterval(function () {
			clearInterval(DelayStalkSearchForMissingSection);
			FTT.stalkSearchForMissingSection(params);
		},3000);
		return;
	}
	FTT.stalkSearchForMissingSectionActive = true;
	api.get(FTT.searchForMissingSectionParams).then(function(data) {
		if ( data.query.searchinfo.totalhits > 0 ) {
			for (FTT.searchMissingSectionInt=0;FTT.searchMissingSectionInt<data.query.search.length;FTT.searchMissingSectionInt++) {
				if ( ! FTT.searchForMissingSectionTitles.includes(data.query.search[FTT.searchMissingSectionInt].title) ) {
					FTT.searchForMissingSectionTitles.push(data.query.search[FTT.searchMissingSectionInt].title);
				}
			}
			FTT.searchForMissingSectionResultsParams = {'action':'query','format':'json','prop':'revisions','rvprop':'content','titles':FTT.searchForMissingSectionTitles};
			api.get(FTT.searchForMissingSectionResultsParams).then(function(data) {
//				FTT.debug('obtained titles: ' + FTT.searchForMissingSectionResultsParams.titles.toString());
				for ( FTT.searchForMissingSectionPageTitleInt=0;FTT.searchForMissingSectionPageTitleInt<Object.keys(data.query.pages).length;FTT.searchForMissingSectionPageTitleInt++) {
					FTT.searchForMissingSectionPageTitle = data.query.pages[Object.keys(data.query.pages)[FTT.searchForMissingSectionPageTitleInt]].title;
//					FTT.debug('searching ' + FTT.searchForMissingSectionPageTitle + ' for the missing section with ID ' + FTT.searchForMissingSectionID);
					FTT.searchForMissingSectionPageWikitext = data.query.pages[Object.keys(data.query.pages)[FTT.searchForMissingSectionPageTitleInt]].revisions[0]['*'];
					FTT.searchForMissingSectionPageWikitextSplit = FTT.searchForMissingSectionPageWikitext.split(/\n/);
					for ( FTT.searchForMissingSectionPageWikitextSplitInt=0;FTT.searchForMissingSectionPageWikitextSplitInt<FTT.searchForMissingSectionPageWikitextSplit.length;FTT.searchForMissingSectionPageWikitextSplitInt++) {
//						FTT.debug('search line #' + FTT.searchForMissingSectionPageWikitextSplitInt + ': ' + FTT.searchForMissingSectionPageWikitextSplit[FTT.searchForMissingSectionPageWikitextSplitInt]);
						if ( FTT.searchForMissingSectionPageWikitextSplit[FTT.searchForMissingSectionPageWikitextSplitInt].match(/^=.*=[ ]*$/) ) {
							FTT.searchForMissingTitleOfSection = FTT.flattenWikiText(FTT.searchForMissingSectionPageWikitextSplit[FTT.searchForMissingSectionPageWikitextSplitInt].replace(/^[= ]*(.*[^=])[= ]*$/,'$1'));
//							FTT.debug('found a section named ' + FTT.searchForMissingTitleOfSection + ' on ' + FTT.searchForMissingSectionPageTitle);
						}
						if (FTT.searchForMissingSectionPageWikitextSplit[FTT.searchForMissingSectionPageWikitextSplitInt].match(FTT.searchForMissingSectionRegExp)) {
//							FTT.debug('recovered the subscribed section! re-subscribing now');
							if ( typeof FTT.checkExistingSubs[FTT.searchForMissingSectionPageTitle] == 'undefined' ) {
//								FTT.debug('adding ' + FTT.searchForMissingSectionPageTitle + ' to subs');
								FTT.checkExistingSubs[FTT.searchForMissingSectionPageTitle] = { 'v':0,'subs':{} };
							}
							if ( typeof FTT.checkExistingSubs[FTT.searchForMissingSectionPageTitle].subs == 'undefined' ) {
								FTT.checkExistingSubs[FTT.searchForMissingSectionPageTitle].subs = {};
							}
							FTT.checkExistingSubs[FTT.searchForMissingSectionPageTitle].subs[FTT.searchForMissingTitleOfSection] = {'id':FTT.lostSubID,'u':FTT.lostSubLastUpdate};
						}
					}
				}
				FTT.stalkSearchForMissingSectionActive = false;
			}, function ( code, data ) { FTT.stalkSearchForMissingSectionActive = false;FTT.APIError(code, data);
			});
		} else {
			FTT.stalkSearchForMissingSectionActive = false;
		}
	}, function ( code, data ) { FTT.stalkSearchForMissingSectionActive = false;FTT.APIError(code, data);
	});
};
FTT.stalkWatchList = function(data) {
	//use data from stalkCheckSubscriptions to generate wikitext overview of new comments
//	FTT.debug('add box with new comments to watchlist');
	FTT.unreadCmtsContainer = document.createElement('div');
	FTT.unreadCmtsContainer.id = 'FTTUnreadCmts';
	$('#content').prepend(FTT.unreadCmtsContainer);
	FTT.stalkSeenItLink = '<div id="FTTstalkSeenIt" style="text-align:center">' + FTT.msgs.subsSeenIt + '</div>';
	if ( typeof data == 'undefined' && typeof FTT.checkExistingSubs.unreadCmts != 'undefined' ) {
		FTT.unreadCmtsContainer.className = 'FTTSubsDiv';
		$('#FTTUnreadCmts')[0].innerHTML = FTT.checkExistingSubs.unreadCmts;
		$('#FTTUnreadCmts').append(FTT.stalkSeenItLink);
		$('#FTTstalkSeenIt').on('click',function(){FTT.stalkSeenIt();});
		return;
	}
	if ( M1('wgCanonicalSpecialPageName') == 'Watchlist' || ( M1('wgTitle') == 'FTTSubs' && M1('wgNamespaceNumber') == -1 ) ) {
		FTT.stalkWikitextUnreadCmts = '';
		for(FTT.stalkWikitextUnreadCmtsInt=0;FTT.stalkWikitextUnreadCmtsInt < Object.keys(FTT.stalkUnreadCommentsAll.unreadCmts).length;FTT.stalkWikitextUnreadCmtsInt++){
		FTT.stalkWikitextUnreadCmts = FTT.stalkWikitextUnreadCmts + '\n' + FTT.stalkUnreadCommentsAll.unreadCmts[Object.keys(FTT.stalkUnreadCommentsAll.unreadCmts)[FTT.stalkWikitextUnreadCmtsInt]] + '\n----';
		}
//		FTT.debug('parse new comments');
		api.post( {action: 'parse', disablelimitreport: true, disableeditsection: true, contentmodel:'wikitext', format: 'json', pst: '1',formatversion: '2',prop: 'text',text: FTT.stalkWikitextUnreadCmts} ).then( function ( data ) {
			FTT.stalkDataAddParsed = FTT.stalkGetSubs();
			FTT.stalkDataAddParsed.unreadCmts = data.parse.text;
			FTT.stalkSaveSubs(FTT.stalkDataAddParsed);
			FTT.unreadCmtsContainer.className = 'FTTSubsDiv';
			$('#FTTUnreadCmts')[0].innerHTML = data.parse.text;
			$('#FTTUnreadCmts').append(FTT.stalkSeenItLink);
			$('#FTTstalkSeenIt').on('click',function(){FTT.stalkSeenIt();});
		}, function ( code, data ) { FTT.APIError(code, data);
		});
	}
};
FTT.stalkSeenIt = function() {
//	FTT.debug('stalkSeenIt');
	FTT.stalkDataRemoveParsed = FTT.stalkGetSubs();
	delete FTT.stalkDataRemoveParsed.unreadCmts;
	FTT.stalkSaveSubs(FTT.stalkDataRemoveParsed);
	$('#FTTUnreadCmts').remove();
};
FTT.tackOnEchoRead = function() {
	//can we push real echo notifications to ourselves? Atm, alas no: T306211. Fake it till you make it!
	FTT.tackOnEchoNotifications = FTT.testValidJSON(mw.user.options.get('userjs-FTTTackOnEchoGlobal'));
	if ( FTT.tackOnEchoNotifications ) {
		if ( M1('skin') == 'minerva' ) {
			FTT.tackOnEchoDelay = 2000;
		} else {
			FTT.tackOnEchoDelay = 700;
		}
		var DelayMarkAsReadButton = setInterval(function () {
		clearInterval(DelayMarkAsReadButton);
			if ( M1('skin') == 'minerva' ) {
				$('.header-action *').removeClass('oo-ui-element-hidden');
			} else {
				$('.mw-echo-ui-notificationBadgeButtonPopupWidget-popup:not(.oo-ui-element-hidden) .mw-echo-ui-notificationsWidget-markAllReadButton').removeClass('oo-ui-element-hidden');
				FTT.markAsReadButtonElement = $('.mw-echo-ui-notificationBadgeButtonPopupWidget-popup:not(.oo-ui-element-hidden) .mw-echo-ui-notificationsWidget-markAllReadButton:not(.oo-ui-element-hidden) A')[0];
				if ( FTT.markAsReadButtonElement ) {
					FTT.markAsReadButtonElement.addEventListener('click',function(){FTT.tackOnEchoClear();$('.FTTTackOnEcho').remove();});
				}
			}
			FTT.tackOnEchoHTML = {};
			for(FTT.tackOnEchoNotificationsInt=0;FTT.tackOnEchoNotificationsInt<Object.keys(FTT.tackOnEchoNotifications).length;FTT.tackOnEchoNotificationsInt++) {
				FTT.tackOnEchoRenderKey = Object.keys(FTT.tackOnEchoNotifications)[FTT.tackOnEchoNotificationsInt];
				if ( M2('FTTScrToTime') == FTT.tackOnEchoRenderKey ) { //we clicked a notice, that's why we are here, so this notice is queued for removal so let's not show it
					continue;
				}
				FTT.tackOnEchoRenderKeyDate = FTT.tackOnEchoRenderKey.replace(/_.*/,'');
				FTT.tackOnEchoHTML[FTT.tackOnEchoRenderKey] = document.createElement('a');
				FTT.tackOnEchoHTML[FTT.tackOnEchoRenderKey].id = 'FTTTackOnEchoEntry'+FTT.tackOnEchoRenderKey;
				FTT.tackOnEchoHTML[FTT.tackOnEchoRenderKey].classList = 'FTTTackOnEcho oo-ui-widget oo-ui-widget-enabled mw-echo-ui-notificationItemWidget mw-echo-ui-notificationItemWidget-initiallyUnseen mw-echo-ui-notificationItemWidget-unread';
				FTT.tackOnEchoHideUser = '';
				FTT.tackOnMsgType = FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].msg;
				if ( ['newcmt','newcmts'].includes(FTT.tackOnMsgType) ) {
					FTT.tackOnLink = FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].page + '?FTTScrToUsr=' + D1(FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].user) + '&FTTScrToTime=' + FTT.tackOnEchoRenderKey + '#' + FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].section.replace(/ /g,'_');
					FTT.tackOnPage = FTT.tackOnLink.replace(/^([^\?\#]*)(\?[^\#]*)/,'$1');
					if ( FTT.tackOnMsgType == 'newcmt' ) {
						FTT.tackOnMsg = FTT.msgs.tackOnNewCmt.replace('SECTION',FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].section);
					} else if ( FTT.tackOnMsgType == 'newcmts' ) {
						FTT.tackOnMsg = FTT.msgs.tackOnNewCmts.replace('SECTION',FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].section);
						FTT.tackOnEchoHideUser = ' style="display:none !important"';//hides user link
					}
				} else {
					FTT.tackOnMsg = FTT.tackOnMsgType;
					FTT.tackOnLink = FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].link;
					if ( typeof FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].page == 'undefined' ) {
						FTT.tackOnPage = FTT.tackOnLink.replace(/^([^\?\#]*).*$/,'$1');
					} else {
						FTT.tackOnPage = FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].page;
					}
				}
				if ( FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].at ) {
					FTT.tackOnEchoHTML[FTT.tackOnEchoRenderKey].href = 'https://' + FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].at.replace('$1',FTT.tackOnLink);
				}
				if ( FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].icon == 'FTT' ) {
					FTT.tackOnIcon = '<span class="FTTSVGLargeIcon"></span>';
				} else if ( FTT[FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].icon] ) {
					FTT.tackOnIcon = FTT[FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].icon].replace(/width="[^"]*"/,'width="2em"');
				} else {
					FTT.tackOnIcon = '<span></span>';
				}
				if ( (new Date().getTime() - FTT.tackOnEchoRenderKeyDate) < 60000 ) {
//					FTT.debug('event was less than 60s ago');
					FTT.tackOnTime = FTT.B1['notification-timestamp-ago-seconds'].replace(/\{\{PLURAL\:\$1\|(.*)\}\}/,'$1').replace('$1',Math.round((new Date().getTime() - FTT.tackOnEchoRenderKeyDate) / 60000));
				} else if ( (new Date().getTime() - FTT.tackOnEchoRenderKeyDate) < 3600000 ) {//less
//					FTT.debug('event was less than 60m ago');
					FTT.tackOnTime = FTT.B1['notification-timestamp-ago-minutes'].replace(/\{\{PLURAL\:\$1\|(.*)\}\}/,'$1').replace('$1',Math.round((new Date().getTime() - FTT.tackOnEchoRenderKeyDate) / 60000));
				} else if ( (new Date().getTime() - FTT.tackOnEchoRenderKeyDate) < 86400000 ) {
//					FTT.debug('event was less than 24hr ago');
					FTT.tackOnTime = FTT.B1['notification-timestamp-ago-hours'].replace(/\{\{PLURAL\:\$1\|(.*)\}\}/,'$1').replace('$1',Math.round((new Date().getTime() - FTT.tackOnEchoRenderKeyDate) / 3600000));
				} else {
//					FTT.debug('event was more than 24hr ago');
					FTT.tackOnTime = FTT.B1['notification-timestamp-ago-days'].replace(/\{\{PLURAL\:\$1\|(.*)\}\}/,'$1').replace('$1',Math.round((new Date().getTime() - FTT.tackOnEchoRenderKeyDate) / 86400000));
				}
				if ( FTT.projectIsSULWiki ) {
					FTT.tackOnEchoAction = 'globalpreferences';
				} else {
					FTT.tackOnEchoAction = 'options';
				}
				FTT.tackOnUserLinkClass = 'oo-ui-widget oo-ui-widget-enabled oo-ui-labelElement oo-ui-optionWidget oo-ui-buttonElement oo-ui-buttonElement-frameless oo-ui-iconElement oo-ui-buttonOptionWidget mw-echo-ui-menuItemWidget mw-echo-ui-menuItemWidget-prioritized';
				FTT.tackOnEchoHTML[FTT.tackOnEchoRenderKey].innerHTML = '<div class="mw-echo-ui-notificationItemWidget-icon">' + FTT.tackOnIcon + '</div>'+
				'<div class="mw-echo-ui-notificationItemWidget-content"><span id="FTTMarkAsReadBubble'+FTT.tackOnEchoRenderKey+'" class="mw-echo-ui-notificationItemWidget-markAsReadButton oo-ui-widget oo-ui-widget-enabled oo-ui-buttonElement oo-ui-buttonElement-frameless oo-ui-iconElement oo-ui-buttonWidget mw-echo-ui-toggleReadCircleButtonWidget">'+
				'<a onclick="event.preventDefault();FTT.tackOnEchoClear(\''+FTT.tackOnEchoRenderKey+'\');$(\'#FTTTackOnEchoEntry'+FTT.tackOnEchoRenderKey+'\').removeClass(\'mw-echo-ui-notificationItemWidget-unread\');$(\'#FTTMarkAsReadBubble'+FTT.tackOnEchoRenderKey+'\').addClass(\'FTTPendingBlink\');'+
				'var DelayRemoveBubble = setInterval(function () {console.log(\'interval\');if ( FTT.updatePrefInProgress[FTT.tackOnEchoAction] == false && FTT.prefQueue && FTT.prefQueue[FTT.tackOnEchoAction] && Object.keys(FTT.prefQueue.'+FTT.tackOnEchoAction+').length == 0 ) {console.log(\'so, clear\');clearInterval(DelayRemoveBubble);$(\'#FTTMarkAsReadBubble'+FTT.tackOnEchoRenderKey+'\').remove();$(\'#FTTTackOnEchoEntry'+FTT.tackOnEchoRenderKey+'\').blur();}},100);" '+
				'class="oo-ui-buttonElement-button" role="button" rel="nofollow" title="'+FTT.B1['echo-notification-markasread-tooltip']+'"><span class="oo-ui-labelElement-label oo-ui-labelElement-invisible">'+FTT.B1['echo-notification-markasread']+'</span><div class="mw-echo-ui-toggleReadCircleButtonWidget-circle"></div></a></span><div class="mw-echo-ui-notificationItemWidget-content-message"><div class="mw-echo-ui-notificationItemWidget-content-message-header-wrapper"><div class="mw-echo-ui-notificationItemWidget-content-message-header">' + FTT.tackOnMsg + '</div></div></div>'+
				'<div class="mw-echo-ui-notificationItemWidget-content-actions"><div class="mw-echo-ui-notificationItemWidget-content-actions-buttons oo-ui-widget oo-ui-widget-enabled oo-ui-selectWidget oo-ui-selectWidget-unpressed oo-ui-buttonSelectWidget">'+
				'<a'+FTT.tackOnEchoHideUser+' class="'+FTT.tackOnUserLinkClass+'" href="https://' + FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].at.replace('$1','User:' + D1(FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].user)) + '">'+
				'<span class="oo-ui-buttonElement-button"><span class="oo-ui-iconElement-icon oo-ui-icon-userAvatar"></span><span class="oo-ui-labelElement-label">' + FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].user + '</span></span></a>'+
				'<a href="https://' + FTT.tackOnEchoNotifications[FTT.tackOnEchoRenderKey].at.replace('$1',D1(FTT.tackOnPage)) + '" class="'+FTT.tackOnUserLinkClass+'">'+
				'<span class="oo-ui-buttonElement-button"><span class="oo-ui-iconElement-icon oo-ui-icon-userSpeechBubble"></span><span class="oo-ui-labelElement-label">' + FTT.tackOnPage.replace(/\#.*/,'') + '</span></span></a></div>'+
				'<label class="mw-echo-ui-notificationItemWidget-content-actions-timestamp oo-ui-widget oo-ui-widget-enabled oo-ui-labelElement oo-ui-labelElement-label oo-ui-labelWidget">' + FTT.tackOnTime + '</label>';
				FTT.tackOnEchoHTML[FTT.tackOnEchoRenderKey].style = 'border-bottom: 1px solid #c8ccd1';
				if ( M1('skin') == 'minerva' ) {
					$('.mw-echo-ui-notificationsListWidget:eq(0)').prepend(FTT.tackOnEchoHTML[Object.keys(FTT.tackOnEchoNotifications)[FTT.tackOnEchoNotificationsInt]]);
				} else {
					$('.mw-echo-ui-notificationBadgeButtonPopupWidget-popup:not(.oo-ui-element-hidden) .mw-echo-ui-sortedListWidget:eq(0)').prepend(FTT.tackOnEchoHTML[Object.keys(FTT.tackOnEchoNotifications)[FTT.tackOnEchoNotificationsInt]]);
				}
			}
		}, FTT.tackOnEchoDelay);
	}
};
FTT.tackOnEchoIconHack = function(noReactivate) {
	if ( $('.FTTActiveTackOnEcho')[0] ) {
//		FTT.debug('tackOnEchoIconHack: mw-echo-notifications-badge has already been hacked..');
		return;
	}
//	FTT.debug('hacking notifications icon..');
	if ( ! FTT.isMobile && M1('skin') == 'minerva' ) {
//		FTT.debug('Not loading notifications. On Minerva desktop, notification icon only opens alerts (never notifications) T306737');
		return;
	}
	FTT.tackOnEchoIconEntries = FTT.testValidJSON(mw.user.options.get('userjs-FTTTackOnEchoGlobal'));
	if ( FTT.tackOnEchoIconEntries ) {
		FTT.tackOnEchoIconAmount = Object.keys(FTT.tackOnEchoIconEntries).length;
	} else {
		FTT.tackOnEchoIconAmount = 0;
	}
	if ( FTT.tackOnEchoIconEntries && FTT.tackOnEchoIconEntries[M2('FTTScrToTime')] ) { //we clicked a notice, that's why we are here, so this notice is queued for removal so let's not count it
		FTT.tackOnEchoIconAmount = FTT.tackOnEchoIconAmount -1;
	}
	if ( FTT.tackOnEchoIconAmount > 0 ) {
//		FTT.debug('light up notification area');
		mw.util.addCSS('#pt-notifications-notice .mw-echo-notifications-badge::after{visibility:unset}');
		$('#pt-notifications-notice .mw-echo-notifications-badge,#pt-notifications-notice .mw-echo-notifications-badge-all-read').addClass('mw-echo-unseen-notifications');
		$('#pt-notifications-notice .mw-echo-notifications-badge,#pt-notifications-notice .mw-echo-notifications-badge-all-read').removeClass('mw-echo-notifications-badge-all-read');
		if ( M1('skin') == 'minerva' && $('#user-notifications .circle span')[0] ) {
//			FTT.debug('you\'re using Minerva and have real notifications');
			$('#user-notifications .circle span')[0].innerText = ( Number($('#user-notifications .circle span')[0].innerText) + FTT.tackOnEchoIconAmount);
			$('#user-notifications .circle span').addClass('FTTActiveTackOnEcho');
		} else if ( M1('skin') == 'minerva' && $('#user-notifications')[0] ) {
//			FTT.debug('you\'re using Minerva but have no native notifications');
			FTT.minervaCircle = document.createElement('div');
			FTT.minervaCircle.classList = 'circle';
			FTT.minervaCircle.innerHTML = '<span class="FTTActiveTackOnEcho">' + FTT.tackOnEchoIconAmount + '</span>';
			$('#user-notifications')[0].innerText = '';
			$('#user-notifications')[0].append(FTT.minervaCircle);
			$('#pt-notifications-alert #user-notifications').addClass('notification-unseen').addClass('notification-count').addClass('mw-echo-unseen-notifications').removeClass('mw-ui-icon').removeClass('mw-ui-icon-element').removeClass('mw-ui-icon-wikimedia-bellOutline-base20');
			$('#pt-notifications-alert #user-notifications').on('click',function(){FTT.tackOnEchoRead();});
		} else if ( M1('skin') != 'minerva' ) {
//			FTT.debug('update notification count');
			$('#pt-notifications-notice .mw-echo-notifications-badge').on('click',function(){FTT.tackOnEchoRead();});
			$('#pt-notifications-notice .mw-echo-notifications-badge').addClass('FTTActiveTackOnEcho');
			document.querySelector('#pt-notifications-notice .mw-echo-notifications-badge').dataset.counterText = ( Number(document.querySelector('#pt-notifications-notice .mw-echo-notifications-badge').dataset.counterText) + FTT.tackOnEchoIconAmount);
			if ( ! noReactivate ) {
//				FTT.debug('clicking pt-notifications-alert causes MediaWiki to destroy the onclick we just added so we need to counter that');
				$('#pt-notifications-alert .mw-echo-notifications-badge').on('click',function(){FTT.tackOnEchoReactivate(0);});
			}
		}
	}
};
FTT.tackOnEchoReactivate = function(reactivateInt) {
//	FTT.debug('tackOnEchoReactivate');
	if ( ! $('#pt-notifications-notice .mw-echo-notifications-badge.FTTActiveTackOnEcho')[0] ) {
		FTT.tackOnEchoIconHack(true);
	} else if ( reactivateInt < 5 ) {
		reactivateInt++;
		var DelayTackOnEchoReactivate = setInterval(function () {
			clearInterval(DelayTackOnEchoReactivate);
			FTT.tackOnEchoReactivate(reactivateInt);
		},500);
	}
};
FTT.tackOnEchoWrite = function(notifications) { //example: FTT.tackOnEchoWrite([{'date':new Date().getTime(),'icon':'FTT','user':'Jimbo Wales','page':'User talk:Jimbo Wales','link':'wiki/User_talk:Jimbo Wales#anchor','msg':'newcmt'},{'date':new Date().getTime(),'icon':'FTT','user':'Jimbo Wales2','page':'User talk:Jimbo Wales2','link':'wiki/User_talk:Jimbo Wales2#anchor','msg':'newcmt'}])
	if ( FTT.projectIsSULWiki ) {
		FTT.tackOnEchoAction = 'globalpreferences';
	} else {
		FTT.tackOnEchoAction = 'options';
	}
	FTT.tackOnLSPrefQueue = FTT.testValidJSON(FTT.getItemLS('FTTPrefQueue'));
	if ( ( FTT.tackOnLSPrefQueue && Object.keys(FTT.tackOnLSPrefQueue[FTT.tackOnEchoAction]).length > 0 ) || FTT.updatePrefInProgress[FTT.tackOnEchoAction] ) {
//		FTT.debug('tackOnEchoWrite: a preference is currently being written, postpone write');
		var DelayUtackOnEchoWrite = setInterval(function () { //adding notices incrementally doesn't really work 
			clearInterval(DelayUtackOnEchoWrite);
			FTT.tackOnEchoWrite(notifications);
		},200);
		return;
	}
//	FTT.debug('tackOnEchoWrite: adding notifications:');
//	FTT.debug(notifications);
	FTT.tackOnEchoWriteProcess = function() {
		if ( ! FTT.tackOnEchoMsgs ) {
			FTT.tackOnEchoMsgs = {};
		}
		for(FTT.tackOnEchoWriteInt=0;FTT.tackOnEchoWriteInt<notifications.length;FTT.tackOnEchoWriteInt++){
//			FTT.debug('tackOnEchoWrite: add ' + JSON.stringify(notifications[FTT.tackOnEchoWriteInt]) + ' to tackOnEcho');
			FTT.tackOnDate = notifications[FTT.tackOnEchoWriteInt].date;
			//delete notifications[FTT.tackOnEchoWriteInt].date; //remove date from the item to reduce redundancy as the date will be the key. No idea why but this also removes the date from mail so this line is now commented out
			FTT.checkDupeNote = FTT.tackOnEchoMsgs[FTT.tackOnDate + '_0'];
			if ( typeof FTT.checkDupeNote != 'undefined' && FTT.checkDupeNote.user == notifications[FTT.tackOnEchoWriteInt].user && FTT.checkDupeNote.msg == notifications[FTT.tackOnEchoWriteInt].msg ) { //check for duplicates due to race conditions
//				FTT.debug('detected likely duplicate notification (same time/user/msg), skipping');
				continue;
			}
			FTT.tackOnDateSeq = 0;
			while ( typeof FTT.tackOnEchoMsgs[FTT.tackOnDate + '_' + FTT.tackOnDateSeq] != 'undefined' ) {
				FTT.tackOnDateSeq++;
			}
			FTT.tackOnEchoMsgs[FTT.tackOnDate + '_' + FTT.tackOnDateSeq] = notifications[FTT.tackOnEchoWriteInt];
		}
		FTT.tackOnEchoMsgsNew = FTT.pack(FTT.tackOnEchoMsgs,'base64');
		if ( new Blob([FTT.tackOnEchoMsgsNew]).size > 65035 ) { //byte length of value must be <65535 https://www.mediawiki.org/wiki/API:Options, we check for 65035 in case of unexpected overhead. When two values are combined (like subs) this allows a 127K maximum
//			FTT.debug('tackOnEchoWrite: notifications exceed 65035 bytes, cannot write to preferences');
			return;
		}
		mw.user.options.set('userjs-FTTTackOnEchoGlobal',FTT.tackOnEchoMsgsNew);
		FTT.tackOnEchoIconHack();
		FTT.tackOnEchoChangedPrefs = ['userjs-FTTTackOnEchoGlobal=' + FTT.tackOnEchoMsgsNew];
		FTT.queueUpdatePref(FTT.tackOnEchoAction,'userjs-FTTTackOnEchoGlobal',FTT.tackOnEchoMsgsNew);
	};
	if ( new Date().getTime()-30000 < FTT.timestampInit ) {
//		FTT.debug('tackOnEchoWrite: page loaded less than 30 seconds ago, skip downloading/refreshing existing notifications from preferences');
		FTT.tackOnEchoMsgs = FTT.testValidJSON(mw.user.options.get('userjs-FTTTackOnEchoGlobal'));
		FTT.tackOnEchoWriteProcess();
	} else {
//		FTT.debug('tackOnEchoWrite: page loaded more than 30 seconds ago, refresh existing notifications from preferences');
		api.get( {format:'json',action:'query',meta:'userinfo',uiprop:'options'} ).then( function ( data ) {
//			FTT.debug('tackOnEchoWrite: obtained preferences');
			FTT.tackOnEchoMsgs = FTT.testValidJSON(data.query.userinfo.options['userjs-FTTTackOnEchoGlobal']);
			FTT.tackOnEchoWriteProcess();
		}, function ( code, data ) { FTT.APIError(code, data);
		});
	}
};
FTT.tackOnEchoClear = function(timestamp) {
	FTT.tackOnEchoClearNewNotes = FTT.testValidJSON(mw.user.options.get('userjs-FTTTackOnEchoGlobal'));
	if ( typeof timestamp != 'undefined' && FTT.tackOnEchoClearNewNotes && FTT.tackOnEchoClearNewNotes[timestamp] ) {
		delete FTT.tackOnEchoClearNewNotes[timestamp];
		FTT.tackOnEchoClearNewNotes = FTT.pack(FTT.tackOnEchoClearNewNotes,'base64');
	} else {
		FTT.tackOnEchoClearNewNotes = '';
		mw.user.options.set('userjs-FTTTackOnEchoGlobal',undefined);
	}
	if ( FTT.projectIsSULWiki ) {
		FTT.tackOnEchoAction = 'globalpreferences';
	} else {
		FTT.tackOnEchoAction = 'options';
	}
	FTT.queueUpdatePref(FTT.tackOnEchoAction,'userjs-FTTTackOnEchoGlobal',FTT.tackOnEchoClearNewNotes);
};
FTT.getStalkLastCheckDate = function(){
	FTT.currentSubsForCheckDate = FTT.stalkGetSubs();
	return FTT.FTTSubInfo;
};
FTT.getStalkLastCheckDate();
FTT.getIndentationFromHTML = function(RLPInt) {
	FTT.indentLevel = 0;
	FTT.foundParserOutput = false;
	FTT.foundParserOutputInt = 0;
	FTT.analyzeIndentationElement = FTT.processElementArray[RLPInt];
	delete FTT.highestIndentElement;
	while ( FTT.analyzeIndentationElement && FTT.foundParserOutput == false && FTT.foundParserOutputInt < 30 ) {
		FTT.foundParserOutputInt++;
		if ( ['UL','DL'].includes(FTT.analyzeIndentationElement.nodeName) ) {
			FTT.indentLevel++;
			FTT.highestIndentElement = FTT.analyzeIndentationElement;
		} else if ( FTT.analyzeIndentationElement.classList.contains('mw-parser-output') ) {
			FTT.foundParserOutput = true;
		}
		FTT.analyzeIndentationElement = FTT.analyzeIndentationElement.parentElement;
	}
	return FTT.indentLevel;
};
FTT.setDSTSetting = function(){
	if ( ['Asia/Kolkata','Asia/Rangoon','Asia/Yangon'].includes(FTT.B1.TZ) ) { //test for downright moronic DST: try half hour steps and test 2 hours before and after
//		FTT.debug('sigDateToMachineReadable: particularly moronic DST');
		FTT.machineReadableDSTInterval = 1800000;
		FTT.machineReadableDSTTestStart = -7200000;
		FTT.ordinaryDST = false;
	} else if ( ['Asia/Kathmandu'].includes(FTT.B1.TZ) ) {
//		FTT.debug('sigDateToMachineReadable: there is no sanity DST');
		FTT.machineReadableDSTInterval = 900000;
		FTT.machineReadableDSTTestStart = -7200000;
		FTT.ordinaryDST = false;
	} else { //test for typical stupid DST: try one hour sooner or later.
//		FTT.debug('sigDateToMachineReadable: ordinary stupid DST');
		FTT.machineReadableDSTInterval = 3600000;
		FTT.machineReadableDSTTestStart = -3600000;
		FTT.ordinaryDST = true;
	}
};
FTT.setDSTSetting(); //could fail at this point if you don't have basicmsgs cached yet
FTT.sigDateToMachineReadable = function(sigDateToConvert,testSigDate,monthInt) {
//	FTT.debug('sigDateToMachineReadable: convert ' + sigDateToConvert + ' into timestamp');
	sigDateToConvert = sigDateToConvert.toString();
	if ( testSigDate && sigDateToConvert.match(/^[0-9]*$/) ) {
		return Number(sigDateToConvert);
	}
	FTT.machineReadableDate = sigDateToConvert.trim().replace(/([0-2]?[0-9])\.([0-5][0-9])/,'$1:$2'); //acewiki and banwiki use a period between hours and minutes. Likely some others too
	FTT.machineReadableDate = FTT.cleanTimestamp(FTT.machineReadableDate);
//	FTT.debug('sigDateToMachineReadable: translate month to English');
	for (monthInt = 1; monthInt < 13; monthInt++) {
//		FTT.debug(FTT.machineReadableDate+', replace '+FTT.monthNames[monthInt]+' with '+FTT.EnglishMonths[monthInt]);
		FTT.machineReadableDate = FTT.machineReadableDate.replace(FTT.monthNames[monthInt], FTT.EnglishMonths[monthInt]);
	}
	FTT.machineReadableDateMatch = FTT.machineReadableDate.replace(/[\.,]([^0-9])/g, '$1').match(FTT.signDateRegExp);
	if ( FTT.machineReadableDateMatch ) {
//		FTT.debug('sigDateToMachineReadable: date matches signDateRegExp');
		FTT.machineReadableDateTrimmedTZ = FTT.machineReadableDateMatch[0].replace(/\(.*/,'');
		FTT.machineReadableDateValid = new Date(FTT.machineReadableDateTrimmedTZ);
		FTT.machineReadableDateValidTime = FTT.machineReadableDateValid.getTime();
		if ( FTT.B1.TZ == 'UTC' && ! isNaN(FTT.machineReadableDateValidTime) ) {
//			FTT.debug('timestamp is valid and wiki uses UTC timezone, skipping timezone/DST conversions');
			return new Date(FTT.machineReadableDateTrimmedTZ+' UTC').getTime();
		}
		if ( ! isNaN(FTT.machineReadableDateValidTime) ) {
//			FTT.debug('sigDateToMachineReadable: valid date found, now find the corresponding timestamp');
			/* Here's the deal. we can't pass a timezone (like Europe/Berlin or America/Montreal) with a datestring. Or I don't know how and neither does the internet.
			We CAN pass a timezone with toLocaleString. But that's the wrong way around. Using the TZ offset we can get close, but due to DST not always correct.
				1. Obtain hours (and minutes() for idiotic DST) from sig date
				2. correct using TZOffsetSeconds
				3. Subtract 1 (or 2 for idiotic DST) hours
				4. toLocaleString() with timezone using this timestamp
				5. test if getUTCHours/getUTCMinutes matches the original, if yes, accept this timestamp
				6. if not, add 1 hour (or 30 minutes for idiotic DST) and return to step 1
			This is stupid. Also possibly inaccurate in the period the clocks moves forward/backward, but screw that. Use locators if you don't want this crap. THAT's the solution.
			*/
			//Step 1 (obtain)
			FTT.machineReadableOrigDay = FTT.machineReadableDateValid.getDate();
			FTT.machineReadableOrigHours = FTT.machineReadableDateValid.getHours();
			if ( ! FTT.ordinaryDST ) {
				FTT.machineReadableOrigMins = FTT.machineReadableDateValid.getMinutes();
			}
			//Step 2 (correct)
			FTT.machineReadableDateValidTZCorrected = ( FTT.machineReadableDateValidTime - 14400000 ) + FTT.TZOffsetSecondsFlip; //honestly, I'm still not sure if this is the best way to make an educated guess for the correct time. Exact results may depend on the browser. DST must die
			//Step 3
			FTT.machineReadableTestDST = FTT.machineReadableDateValidTZCorrected + FTT.machineReadableDSTTestStart;
//			FTT.debug('sigDateToMachineReadable: timestamp corrected for timezone (but unknown DST status): '+FTT.machineReadableTestDST);
			//Step 4
			for (FTT.machineReadableTestint=0;FTT.machineReadableTestint<48;FTT.machineReadableTestint++){
//				FTT.debug('sigDateToMachineReadable: try '+FTT.machineReadableTestDST+' '+FTT.machineReadableTestint+' / 8');
				FTT.machineReadableTestDSTDate = new Date(FTT.machineReadableTestDST).toLocaleString("en", {timeZone:FTT.wikiTimezone}).toString();
				FTT.machineReadableTestDSTDay = new Date(FTT.machineReadableTestDSTDate).getDate();
				FTT.machineReadableTestDSTHours = new Date(FTT.machineReadableTestDSTDate).getHours();
				if ( ! FTT.ordinaryDST ) {
					FTT.machineReadableTestDSTMins = new Date(FTT.machineReadableTestDSTDate).getMinutes();
				}
//				FTT.debug('sigDateToMachineReadable: compare '+FTT.machineReadableTestDSTHours+' to '+FTT.machineReadableOrigHours);
				if ( FTT.machineReadableTestDSTDay == FTT.machineReadableOrigDay && FTT.machineReadableTestDSTHours == FTT.machineReadableOrigHours && FTT.machineReadableTestDSTMins == FTT.machineReadableOrigMins ) {
//					FTT.debug('sigDateToMachineReadable: day, hour and minutes match, '+FTT.machineReadableTestDST+' is the correct timestamp for '+sigDateToConvert);
					return FTT.machineReadableTestDST;
				}
				FTT.machineReadableTestDST = FTT.machineReadableTestDST+FTT.machineReadableDSTInterval;
			}
		} else {
//			FTT.debug('sigDateToMachineReadable: '+sigDateToConvert + ' could not be converted to a timestamp');
			return false;
		}
	}
};
FTT.wikiTextUpToSigRegExp = new RegExp('^=.+=[ ]*(FTTSAFED'+FTT.semiRandom+'HTMLCOMMENT)?[ ]*$','gm');
FTT.wikiTextUpToSigUnsafedRegExp = new RegExp('^=.+=[ ]*(<\!\-\-.*\-\->)?[ ]*$','gm');
FTT.trySigSplit = function() {
	FTT.wikiTextSplitByUserRegExpGroup = new RegExp('('+FTT.wikiTextSplitByUserRegExp.source+')','g'); //not useful for split but needed to append the safed marker
	FTT.wikiTextSplitByUserSigsMarked = FTT.justCurrentPageTextSafed.replace(FTT.wikiTextSplitByUserRegExpGroup,'$1FTTSAFEDSIG'+FTT.semiRandom);
	FTT.wikiTextSplitByUser = FTT.wikiTextSplitByUserSigsMarked.split(new RegExp('FTTSAFEDSIG'+FTT.semiRandom));
	FTT.wikiTextSplitByUserMatch = null;
	if ( FTT.wikiTextSplitByUser.length == 1 ) {
		FTT.wikiTextSplitByUserMatch = FTT.wikiTextSplitByUserSigsMarked.match(new RegExp('FTTSAFEDSIG'+FTT.semiRandom)); //length would also be 1 if the signature marks the end of the input
	}
};
FTT.getInsertionPointComment = function(PRM, justCurrentPageText, UNSAFE) {
//	FTT.debug('getInsertionPointComment: collect sectiontext, indentation and insertion point of original comment');
	FTT.relevantMultiline = 0;
	if ( ! UNSAFE ) {
//		FTT.debug('getInsertionPointComment: running in safe mode, excluding templates/tags/etc from section search');
		FTT.justCurrentPageTextSafed = FTT.safeText(justCurrentPageText,undefined,'skiplinks');
	} else {
//		FTT.debug('getInsertionPointComment: RUNNING IN UNSAFE MODE. TEMPLATES/TAGS/ETC ARE NOT EXCLUDED FROM SECTION SEARCH.');
		FTT.justCurrentPageTextSafed = justCurrentPageText;
	}
	FTT.origReplyTo = E1(PRM.origReplyTo).replace(/[ _]/g, '[ _]'); //some users sign with [[User:User_Name]], others with [[User:User Name]], some with [[User:user Name]]
	FTT.origTimestampEscaped = E1(PRM.origTimestamp);
	delete FTT.wikiTextSplitByUser;
	FTT.wikiTextSplitByUserMatch = null;
	if ( PRM.subtype == 'locator' ) {
//		FTT.debug('getInsertionPointComment: try locator');
//		FTT.gotInsertionPointBy = 'locator'; //FTT.debug
		FTT.wikiTextSplitByUserRegExp = new RegExp('(?:'+FTT.origReplyTo + ':' + FTT.origTimestampEscaped + ').*', 'g');
		FTT.trySigSplit();
	}
	if ( PRM.subtype == 'legacy' || ((FTT.wikiTextSplitByUser && FTT.wikiTextSplitByUser.length == 1) ) && !FTT.wikiTextSplitByUserMatch ) {
//		FTT.debug('getInsertionPointComment: try legacy sig');
//		FTT.gotInsertionPointBy = 'legacy nospan'; //FTT.debug
		// .replace(/\\\|/g, '(&#124;|\|)') takes care of pipes in the timestamp textnode which may appear as HTML entities in the wikitext. See May 2024 report by Saqib
		FTT.wikiTextSplitByUserRegExp = new RegExp('(?:[\/:]' + '[' + FTT.origReplyTo.slice(0,1).toUpperCase() + FTT.origReplyTo.slice(0,1).toLowerCase() + ']' + FTT.origReplyTo.slice(1) + '(?:(?:[^c\n\{]|c(?!lass="FTTCmt")){0,255})[^:]' + E1(PRM.origTimestampTextNode).replace(/\xa0/g,'&nbsp;').replace(/\\\|/g, '(&#124;|\|)') + '(?!<\/span>)).*', 'g');
		FTT.trySigSplit();
		if ( FTT.wikiTextSplitByUser.length == 1 && !FTT.wikiTextSplitByUserMatch ) {
//			FTT.debug('getInsertionPointComment: nothing found, try allowing trailing span tag');
//			FTT.gotInsertionPointBy = 'legacy span'; //FTT.debug
			FTT.wikiTextSplitByUserRegExp = new RegExp('(?:[\/:]' + '[' + FTT.origReplyTo.slice(0,1).toUpperCase() + FTT.origReplyTo.slice(0,1).toLowerCase() + ']' + FTT.origReplyTo.slice(1) + '(?:(?:[^c\n\{]|c(?!lass="FTTCmt")){0,255})[^:]' + E1(PRM.origTimestampTextNode).replace(/\xa0/g,'&nbsp;').replace(/\\\|/g, '(&#124;|\|)') + ').*', 'g');
			FTT.trySigSplit();
		}
		if ( FTT.wikiTextSplitByUser.length == 1 && !FTT.wikiTextSplitByUserMatch ) {
//			FTT.debug('getInsertionPointComment: nothing found, try username written with HTML entities');
//			FTT.gotInsertionPointBy = 'legacy entities'; //FTT.debug
			FTT.origReplyToHTMLEntities = FTT.origReplyTo.replace(/['"\$\&\^\(\)\*]/g, function(origchar){return '&#'+origchar.charCodeAt()+';';});
			FTT.wikiTextSplitByUserRegExp = new RegExp('(?:[\/:]' + '[' + FTT.origReplyToHTMLEntities.slice(0,1).toUpperCase() + FTT.origReplyToHTMLEntities.slice(0,1).toLowerCase() + ']' + FTT.origReplyToHTMLEntities.slice(1) + '(?:(?:[^c\n\{]|c(?!lass="FTTCmt")){0,255})[^:]' + E1(PRM.origTimestampTextNode).replace(/\xa0/g,'&nbsp;') + ').*', 'g');
			FTT.trySigSplit();
		}
		if ( FTT.wikiTextSplitByUser.length == 1 && !FTT.wikiTextSplitByUserMatch && FTT.justCurrentPageTextSafed.match(new RegExp(E1(PRM.origTimestampTextNode),'g')) && FTT.justCurrentPageTextSafed.match(new RegExp(E1(PRM.origTimestampTextNode),'g')).length == 1 ) {
//			FTT.debug('getInsertionPointComment: username+timestamp not found, but timestamp was found and unique, assuming it belongs the comment you are replying to and the user has an odd signature that got safed (see User:Lambiam)');
//			FTT.gotInsertionPointBy = 'legacy timestamponly'; //FTT.debug
			FTT.wikiTextSplitByUserRegExp = new RegExp(E1(PRM.origTimestampTextNode).replace(/\xa0/g,'&nbsp;'), 'g'); //just the timestamp
			FTT.trySigSplit();
		}
	}
	if ( typeof PRM.seq == 'number' && PRM.seq <= FTT.wikiTextSplitByUser.length && FTT.wikiTextSplitByUser.length > 1 ) {
//		FTT.debug('getInsertionPointComment: there are ' + ( FTT.wikiTextSplitByUser.length - 1 ) + ' occurences of username "' + FTT.origReplyTo + '" with timestamp ' + PRM.origTimestampTextNode + ' on this page');
		FTT.wikiTextUpToSig = '';
		for (FTT.wikitextseqint = 0; FTT.wikitextseqint <= PRM.seq; FTT.wikitextseqint++) {
			FTT.wikiTextUpToSig = FTT.wikiTextUpToSig + FTT.wikiTextSplitByUser[FTT.wikitextseqint];
		}
		if ( ! UNSAFE ) {
			FTT.wikiTextNum = FTT.wikiTextUpToSig.match(FTT.wikiTextUpToSigRegExp);
		} else {
			FTT.wikiTextNum = FTT.wikiTextUpToSig.match(FTT.wikiTextUpToSigUnsafedRegExp);
		}
		if ( FTT.wikiTextNum ) {
			FTT.sectionText = FTT.getSectionByNum(FTT.justCurrentPageTextSafed,FTT.wikiTextNum.length);
		} else {
//			FTT.debug('getInsertionPointComment: found no section headers in the text leading up to the comment');
			FTT.sectionText = 'SECTIONLESS';
			FTT.wikiTextNum = 'SECTIONLESS';
		}
		if ( ! UNSAFE ) {
			FTT.getInsertionAllHeadersUnsafe = justCurrentPageText.match(FTT.wikiTextUpToSigUnsafedRegExp).length;
			FTT.getInsertionAllHeadersSafe = FTT.justCurrentPageTextSafed.match(FTT.wikiTextUpToSigRegExp).length;
			if ( FTT.getInsertionAllHeadersUnsafe != FTT.getInsertionAllHeadersSafe ) {
//				FTT.debug('getInsertionPointComment: the number of headers for the unsafed text differs from the number for the safed text. Some headers may be encapsulated in a template or something. As we can\'t possibly know if the template invalidates the section header, we have to disable section editing now');
				FTT.sectionText = 'SECTIONLESS';
				FTT.wikiTextNum = 'SECTIONLESS';
			}
		}
		if ( ! UNSAFE ) {
			FTT.wikiTextUpToSigUnsafed = FTT.safeText(FTT.wikiTextUpToSig,'unsafe'); //todo: we should be able to do this without another unsafe operation
		} else {
			FTT.wikiTextUpToSigUnsafed = FTT.wikiTextUpToSig;
		}
		FTT.wikiTextUpToSigUnsafedSplit = FTT.wikiTextUpToSigUnsafed.split(/\n/);
		FTT.relevantPrecedingLine = FTT.wikiTextUpToSigUnsafedSplit[ FTT.wikiTextUpToSigUnsafedSplit.length - 2 ] + '\n';
		if ( FTT.relevantPrecedingLine == '\n' ) {
			FTT.relevantPrecedingLine = '';
		}
		FTT.relevantCommentNoSig = FTT.wikiTextUpToSigUnsafedSplit[ FTT.wikiTextUpToSigUnsafedSplit.length - 1 ]; //last line of wikiTextUpToSigUnsafed
		FTT.relevantCommentNoSigOrig = FTT.relevantCommentNoSig;
		if ( UNSAFE && ! FTT.relevantCommentNoSig.match(/^[:#\*]/) && FTT.PRMOpened.type == 'comment' ) {
//			FTT.debug('getInsertionPointComment: unindented comment that may be illegally residing in a template or something, who knows what it\'s encapsulated in, need to select more');
			FTT.unsafeExtraLinesRegExp = new RegExp(E1(FTT.relevantCommentNoSig)+'(\n\n|\n[^\n\*\:\#\=].*)*');
			FTT.unsafeExtraLines = justCurrentPageText.match(FTT.unsafeExtraLinesRegExp);
			if ( FTT.unsafeExtraLines ) {
				FTT.relevantCommentNoSig = FTT.unsafeExtraLines[0];
			}
		}
		if ( ! FTT.relevantCommentNoSig.match(/^[:#\*]/) && FTT.PRMOpened.type == 'edit' ) { //editing an unindented comment, presumably a new section
//			FTT.debug('getInsertionPointComment: editing an unindented comment, preloading more lines');
			for ( FTT.unindentedInt=2;FTT.unindentedInt<50;FTT.unindentedInt++) {
				FTT.unindentedPreviousLine = FTT.wikiTextUpToSigUnsafedSplit[ FTT.wikiTextUpToSigUnsafedSplit.length - FTT.unindentedInt ];
				if ( typeof FTT.unindentedPreviousLine == 'string' && ! FTT.unindentedPreviousLine.match(/^[=]/) && ! FTT.cleanTimestamp(FTT.unindentedPreviousLine).match(FTT.signDateRegExpLocalMonths) ) {
					FTT.relevantMultiline = 1;
					FTT.relevantCommentNoSig = FTT.unindentedPreviousLine + '\n' + FTT.relevantCommentNoSig;
				} else {
					break;
				}
			}
			FTT.relevantCommentNoSig = FTT.relevantCommentNoSig.trim();
			if ( FTT.relevantCommentNoSig != FTT.relevantCommentNoSigOrig && ! FTT.PRMOpened.freshcomment ) { //fresh comment may not have indentation anyway.. umm.. how do I fix this properly so you could still edit newly posted multiline comments too
				FTT.PRMOpened.multiline = true;
			}
		}
//		FTT.debug('getInsertionPointComment: returning requested info');
		return { 'sectiontext':FTT.sectionText,'sectionnum':FTT.wikiTextNum.length,'relevantIndentation':FTT.relevantCommentNoSig.replace(/([\\:\\*\\#]*)[^]*/, '$1'),'relevantComment':FTT.relevantCommentNoSig,'relevantPrecedingLine':FTT.relevantPrecedingLine,'multiline':FTT.relevantMultiline};
	} else if ( PRM.subtype == 'locator' && ! PRM.origPageTitle ) {
//		FTT.debug('getInsertionPointComment: locator that couldn\'t be found. Maybe the section was moved to another page or the whole page was moved?');
		PRM.origPageTitle = PRM.pageTitle;
		PRM.pageTitle = FTT.PRM[PRM.pageTitleInt].pageTitle;
		//[[:Special:PermanentLink/1184988046#You messed up! (I AM ERROR) 2|User talk:Alexis Jazz/Factotum (revision 1184988046)]]
//		FTT.debug('getInsertionPointComment: 1. pageTitle is '+PRM.pageTitle);
		if ( typeof PRM.pageTitle == 'undefined' || PRM.pageTitle == '' ) {
			try {
				FTT.sectionEditURL = FTT.processElementArray[FTT.PRMOpened.pageTitleInt].querySelectorAll('a')[0].href;
				FTT.PRM[FTT.PRMOpened.pageTitleInt].pageTitle = new mw.Uri(FTT.sectionEditURL).query.title;
			} catch (e) {}
			PRM.pageTitle = FTT.PRM[PRM.pageTitleInt].pageTitle;
//			FTT.debug('getInsertionPointComment: 2. pageTitle is '+PRM.pageTitle);
			if ( typeof PRM.pageTitle == 'undefined' || PRM.pageTitle == '' ) {
				PRM.pageTitle = M1('wgPageName');
			}
			FTT.PRM[PRM.int].pageTitle = PRM.pageTitle;
//			FTT.debug('getInsertionPointComment: 3. pageTitle is '+PRM.pageTitle);
			FTT.killNewReplyCheck = 1; //check for new comments seems to fail, at least for the comment in question. Some API requests for Alia_Shelesh still happened I think. Fine, skip the check, maybe I'll figure it out later, though probably not
		}
		if ( PRM.pageTitle.replace(/ /g,'_') != PRM.origPageTitle ) {
//			FTT.debug('getInsertionPointComment: section was possibly moved. trying page title found using legacy method.');
			FTT.PRMOpened = PRM;
			if ( PRM.type == 'comment' ) {
				FTT.PRM[PRM.int] = PRM;
			} else if ( PRM.type == 'edit' ) {
				FTT.PRMEdit[PRM.int] = PRM;
			}
			return 'retry';
		} else {
//			FTT.debug('getInsertionPointComment: locator not found and section not moved');
			FTT.addScrewedLink('locator not found and section doesn\'t appear to have been moved. Username+timestamp: ' + PRM.origReplyTo + '+' + PRM.origTimestamp + ', seq: ' + PRM.seq,'Locator combination not found.');
		}
	} else {
//		FTT.debug('getInsertionPointComment: something unexpected happened. seq: ' + PRM.seq + ', FTT.wikiTextSplitByUser.length: ' + FTT.wikiTextSplitByUser.length);
		FTT.unsafeMatches = justCurrentPageText.match(FTT.wikiTextSplitByUserRegExp);
		if ( FTT.unsafeMatches && FTT.unsafeMatches.length == 1 && FTT.wikiTextSplitByUser.length == 1 && ! UNSAFE ) {
//			FTT.debug('getInsertionPointComment: there is only one occurence of this username+timestamp in the unsafed wikitext and none in the safed wikitext');
			if ( FTT.justCurrentPageTextSafed.match(FTT.wikiTextUpToSigRegExp).length == justCurrentPageText.match(FTT.wikiTextUpToSigUnsafedRegExp).length ) {
//				FTT.debug('getInsertionPointComment: safing is not needed to prevent false positives for section detection and the comment may be illegally residing in a template or similar, try getting insertion point without safing');
				return FTT.getInsertionPointComment(PRM, justCurrentPageText, true);
			}
		} else {
			FTT.addScrewedLink('username+timestamp ' + PRM.origReplyTo + '+' + PRM.origTimestamp + ' not found, seq: ' + PRM.seq,'Username/timestamp combination not found.');
			return 'ERRORBAD';
		}
	}
};
FTT.getInsertionPointSection = function(PRM, justCurrentPageText) {
//	FTT.debug('getInsertionPointSection');
	FTT.justCurrentPageTextSafed = FTT.safeText(justCurrentPageText,undefined,'skiplinks');
	if ( PRM.type == 'heading' && PRM.section == 0 ) {
		return {'sectiontext':justCurrentPageText.replace(/(^|\n)=.*=[ ]*(?:FTTSAFED[0-9]{4}HTMLCOMMENT[ ]*)*\n[^]*/,''),'sectionnum':0};
	}
	FTT.justCurrentPageTextSplitByLine = FTT.justCurrentPageTextSafed.split(/\n/);
	FTT.justCurrentPageTextFlattenedTitles = '';
	for(FTT.justCurrentPageTextSplitByLineInt=0;FTT.justCurrentPageTextSplitByLineInt<FTT.justCurrentPageTextSplitByLine.length;FTT.justCurrentPageTextSplitByLineInt++){
		FTT.processLine = FTT.justCurrentPageTextSplitByLine[FTT.justCurrentPageTextSplitByLineInt];
		if ( FTT.processLine.match(/^=.*=[ ]*(?:FTTSAFED[0-9]{4}HTMLCOMMENT[ ]*)*$/) ) {
			FTT.processLine = FTT.flattenWikiText(FTT.processLine);//flattening to allow matching a section title like "==[[Bad article]] and [[other bad article]]==" with the innerText "Bad article and other bad article"
		}
		FTT.justCurrentPageTextFlattenedTitles = FTT.justCurrentPageTextFlattenedTitles + FTT.processLine + '\n';
	}
	FTT.wikiTextSplitBySectionTitleRegExp = new RegExp('((?:^|\n)[=]+(?:([ ]|FTTSAFED[0-9L]+TEMPLATE)*)' + E1(PRM.sectionTitle) + '(?:([ ]|FTTSAFED[0-9L]+TEMPLATE)*)[=]+[ ]*(?:FTTSAFED[0-9]{4}HTMLCOMMENT[ ]*)*(?:$|\n))', 'gm');
	FTT.wikiTextSplitBySectionTitleNoGroupRegExp = new RegExp('(?:^|\n)[=]+(?:([ ]|FTTSAFED[0-9L]+TEMPLATE)*)' + E1(PRM.sectionTitle) + '(?:([ ]|FTTSAFED[0-9L]+TEMPLATE)*)[=]+[ ]*(?:FTTSAFED[0-9]{4}HTMLCOMMENT[ ]*)*(?:$|\n)', 'gm');//for the split we must NOT have the section titles themselves returned or the count gets all messed up
	FTT.justCurrentPageTextWithPlaceHolder = FTT.justCurrentPageTextFlattenedTitles.replace(/([^\n])\n=/g,'$1\n\n=').replace(FTT.wikiTextSplitBySectionTitleRegExp,'FTT_MATCHING_SECTION_TITLE\n$1');
	FTT.wikiTextSplitBySectionTitle = FTT.justCurrentPageTextWithPlaceHolder.split(FTT.wikiTextSplitBySectionTitleNoGroupRegExp);
	FTT.wikiTextUpToHeader = '';
	for (FTT.wikitextseqint = 0; FTT.wikitextseqint <= PRM.sectionseq; FTT.wikitextseqint++) {
//		FTT.debug('compiling wikitext up to header #' + FTT.wikitextseqint);
		FTT.wikiTextUpToHeader = FTT.wikiTextUpToHeader + FTT.wikiTextSplitBySectionTitle[FTT.wikitextseqint];
	}
	FTT.wikiTextNum = FTT.wikiTextUpToHeader.match(/^(FTT_MATCHING_SECTION_TITLE|=.*=)[ ]*(?:FTTSAFED[0-9]{4}HTMLCOMMENT[ ]*)*$/gm);
	if ( FTT.wikiTextNum ) {
		return {'sectiontext':FTT.getSectionByNum(FTT.justCurrentPageTextSafed,FTT.wikiTextNum.length),'sectionnum':FTT.wikiTextNum.length};
	} else {
		return {'sectiontext':justCurrentPageText};
	}
};
FTT.flattenWikiText = function(text,regexes,i) {//not quite complete flattening, but generally sufficient for the purpose of flattening comments for summary snippets and section titles for anchors
//	FTT.debug('flattenWikiText');
	regexes = [
		[/<([Nn]o[Ww]iki|NOWIKI)>(.*)<\/([Nn]o[Ww]iki|NOWIKI)>/g,'$2'],
		[/\[\[[:]?([^\|\]\n]+\|)?(([^\]\|\n]|\][^\]])+)\]\]/g,'$2'],
		[/\[\[[:]?([^\:\]\n]*\:)([^\|\n]+)\|\]\]/g,'$2'],
		[/\[[a-z]{2,14}:\/\/[^ ]( ([^\]]*))?\]/,'$2'],
		[/[']{2,3}/g,''],
		[/<[^>]*>([^<]*)<\/[^>]*>/g,'$1'],
		[/<[^>]*>([^<]*)<\/[^>]*>/g,'$1'],
		[/[ ]{2,}/g,' '],
		[/\{\{[^\{\}=]*=([^\{\}]*)\}\}/g,''],
		[/\{\{[^\\|\\\}\\\{]*\|([^\\\}]*)(\|[^\}]*)*\}\}/g, ''],
		[/~{3,5}/g,''],
		[/<br([\/ ]*| .*)>/g,''],
		[/[ ]{2,}/g,' ']
	];
	for (i=0;i<regexes.length;i++) {
		text = text.replace(regexes[i][0],regexes[i][1]);
	}
	return text.trim();
};
FTT.highlightRefGetRange = function(refNum,refTestInt) {
//	FTT.debug('highlightRefGetRange');
	delete FTT.refID;
	delete FTT.refIndexStart;
	delete FTT.refIndexEnd;
	delete FTT.refIDNameMatchRef;
	//there is no sure-fire way to find a reference in wikitext, so we'll employ a few strategies
	// *get ref by refname (useless for nameless refs)
	// *find a unique external URL (useless for refs without any external URL)
	// *find a unique long (7 or more digits) number (could work if there's an ISBN or similar identifier in there, otherwise useless)
	// *find preceding textnode and search wikitext for that
	// *get ref by number (totally breaks if any ref is a result of transclusion)
	// *fall on your face
	FTT.refIDNameMatch = FTT.referenceArray[refNum].id.match(/cite_note\-(.+)\-[0-9]+$/);
	if ( FTT.refIDNameMatch ) {
		FTT.refIDName = FTT.refIDNameMatch[1];
//		FTT.debug('highlightRefGetRange: found named ref: '+FTT.refIDName);
		FTT.refIDNameRegExp = new RegExp('<[Rr][Ee][Ff][^>]*name[ ="]+'+E1(FTT.refIDName).replace(/_/g,'[_ ]')+'(>|[ "][^\/>]*>)(([^<]|<(?!\/[Rr][Ee][Ff]))*)<\/[Rr][Ee][Ff]>');
		FTT.refIDNameMatchRef = FTT.refSourceText.match(FTT.refIDNameRegExp);
	}
	if ( FTT.refIDNameMatchRef ) {
//		FTT.debug('highlightRefGetRange: ref is named and found in wikitext');
		FTT.refIndexStart = FTT.refSourceText.split(FTT.refIDNameMatchRef[0])[0].length;
		FTT.refIndexEnd = FTT.refIndexStart+FTT.refIDNameMatchRef[0].length;
	} else {
//		FTT.debug('highlightRefGetRange: ref is not named or not found in wikitext');
		delete FTT.refIDNameMatchRef;
	}
	if ( ! FTT.refIDNameMatchRef ) {
//		FTT.debug('highlightRefGetRange: look for an external URL in the ref');
		FTT.refExtUrlArr = FTT.referenceArray[refNum].querySelectorAll('a');
		for ( refTestInt=0;refTestInt<FTT.refExtUrlArr.length;refTestInt++ ) { //cycles through all links within this ref, first hit for an external URL gets used
//			FTT.debug('highlightRefGetRange: cycle through all links within this ref, first hit for an external URL gets used, refTestInt='+refTestInt);
			if ( FTT.refExtUrlArr[refTestInt].href.match(/^[a-z]*:\/\//) && FTT.refSourceText.match(FTT.refExtUrlArr[refTestInt].href) && FTT.refSourceText.match(FTT.refExtUrlArr[refTestInt].href).length == 1 ) {
				FTT.refID = FTT.refExtUrlArr[refTestInt].href;
//				FTT.debug('highlightRefGetRange: found an external URL that occurs only once in wikitext: '+FTT.refID);
				break;
			}
		}
	}
	if ( ! FTT.refID ) {
//		FTT.debug('highlightRefGetRange: look for a number of at least 7 digits in the ref');
		FTT.refLongNumArr = FTT.referenceArray[refNum].innerText.match(/[0-9]{7,}/);
		if ( FTT.refLongNumArr && FTT.refSourceText.match(FTT.refLongNumArr).length == 1 ) {
//			FTT.debug('highlightRefGetRange: found a long number that occurs only once in wikitext: '+FTT.refLongNumArr);
			FTT.refID = FTT.refLongNumArr;
		}
	}
	if ( FTT.refID && ! FTT.refIndexStart ) {
//		FTT.debug('highlightRefGetRange: try to get selection using refID '+FTT.refID);
		FTT.refIDRegExp = new RegExp('<[Rr][Ee][Ff][^\/>]*>(([^<]|<(?!\/[Rr][Ee][Ff]>))*)'+E1(FTT.refID)+'(([^<]|<(?!\/[Rr][Ee][Ff]>))*)<\/[Rr][Ee][Ff]>');
		FTT.refIDMatch = FTT.refSourceText.match(FTT.refIDRegExp);
		if ( FTT.refIDMatch ) {
//			FTT.debug('highlightRefGetRange: found refID');
			FTT.refIndexStart = FTT.refSourceText.split(FTT.refIDMatch[0])[0].length;
			FTT.refIndexEnd = FTT.refIndexStart + FTT.refIDMatch[0].length;
		}
	}
	if ( ! FTT.refIndexStart ) {
//		FTT.debug('highlightRefGetRange: find preceding textnode for ref');
		FTT.refPrevSibText = [];
		for (refTestInt=0;refTestInt<FTT.referenceArray[refNum].querySelectorAll('.mw-cite-backlink a').length;refTestInt++) {
//			FTT.debug('highlightRefGetRange: find preceding textnode for ref, refTestInt:'+refTestInt);
			FTT.refPrevSib = $(FTT.referenceArray[refNum].querySelectorAll('.mw-cite-backlink a')[refTestInt].attributes.href.value)[0];
			if ( FTT.refPrevSib && FTT.refPrevSib.previousSibling && FTT.refPrevSib.previousSibling.nodeName == '#text' ) { //todo: we could detect here the reference is preceded by another reference, skip that reference and use the node before that instead
//				FTT.debug('highlightRefGetRange: found textnode with content: "'+FTT.refPrevSib.previousSibling.nodeValue+'"');
				FTT.refPrevSibText.push(FTT.refPrevSib.previousSibling.nodeValue);
				break;
			}
		}
		for (refTestInt=0;refTestInt<FTT.refPrevSibText.length;refTestInt++) {
//			FTT.debug('highlightRefGetRange: search for unique text match for preceding text, refTestInt='+refTestInt);
			FTT.refPrevSibRegExp = new RegExp(E1(FTT.refPrevSibText[refTestInt])+'<[Rr][Ee][Ff][^\/>]*>(([^<]|<(?!\/[Rr][Ee][Ff]>))*)<\/[Rr][Ee][Ff]>','g');
			FTT.refPrevSibMatch = FTT.refSourceText.match(FTT.refPrevSibRegExp);
			if ( FTT.refPrevSibMatch && FTT.refPrevSibMatch.length == 1 ) {
//				FTT.debug('highlightRefGetRange: found unique text match for preceding text');
				FTT.refIndexStart = FTT.refSourceText.split(FTT.refPrevSibRegExp)[0].length + FTT.refPrevSibText[refTestInt].length;
				FTT.refIndexEnd = FTT.refIndexStart + ( FTT.refPrevSibMatch[0].length - FTT.refPrevSibText[refTestInt].length);
				break;
			}
		}
	}
	if ( ! FTT.refIndexStart ) {
//		FTT.debug('highlightRefGetRange: other strategies failed, get ref by number');
		FTT.refNamedRefs = {};
		FTT.refSortedRefs = [];
		FTT.refSortedDoneRefs = [];
		FTT.refSourceRefs = FTT.refSourceText.match(/(<[Rr][Ee][Ff][^\/>]*>(([^<]|<(?!\/[Rr][Ee][Ff]>))*)<\/[Rr][Ee][Ff]>|<[Rr][Ee][Ff](([^\>]|\/[^>])*)\/>)/g);
		FTT.refIDNameRegExp = new RegExp('<[Rr][Ee][Ff][^>]*name[ ="]+([^> "]*)(>|[ "][^\/>]*>)(([^<]|<(?!\/[Rr][Ee][Ff]))*)<\/[Rr][Ee][Ff]>');
		if ( FTT.refSourceRefs ) {
//			FTT.debug('highlightRefGetRange: create object with named refs');
			for ( refTestInt=0;refTestInt<FTT.refSourceRefs.length;refTestInt++ ) {
//				FTT.debug('highlightRefGetRange: creating object with named refs, refTestInt='+refTestInt);
				FTT.refNameMatch = FTT.refSourceRefs[refTestInt].match(FTT.refIDNameRegExp);
				if ( FTT.refNameMatch ) {
					FTT.refNamedRefs[FTT.refNameMatch[1].replace(/ /g,'_')] = FTT.refNameMatch[0];
//					FTT.debug('highlightRefGetRange: adding '+ FTT.refNameMatch[1].replace(/ /g,'_') +' to object with named refs');
				}
			}
//			FTT.debug('highlightRefGetRange: make array with sorted refs');
			for ( refTestInt=0;refTestInt<FTT.refSourceRefs.length;refTestInt++ ) {
//				FTT.debug('highlightRefGetRange: make array with sorted refs, refTestInt='+refTestInt);
				FTT.refTestSymLink = FTT.refSourceRefs[refTestInt].match(new RegExp('<ref[^>]*name[ =]+("[^"]*"|[^> ]*)[^]*\/>$'));
				if ( FTT.refTestSymLink && FTT.refNamedRefs[FTT.refTestSymLink[1].replace(/ /g,'_')] && ! FTT.refSortedDoneRefs.includes(FTT.refTestSymLink[1].replace(/ /g,'_')) ) {
//					FTT.debug('highlightRefGetRange: push '+FTT.refTestSymLink[1]); //this only happens if there is a symbolic link BEFORE the actual ref
					if ( FTT.refTestSymLink && ! FTT.refSortedDoneRefs.includes(FTT.refTestSymLink[1].replace(/ /g,'_')) ) {
//						FTT.debug('highlightRefGetRange: refSortedDoneRefs does not include ref yet, ref is not a symlink');
						FTT.refSortedRefs.push(FTT.refNamedRefs[FTT.refTestSymLink[1].replace(/ /g,'_')]);
						FTT.refSortedDoneRefs.push(FTT.refTestSymLink[1].replace(/ /g,'_'));
					}
				} else if ( ! FTT.refSortedRefs.includes(FTT.refSourceRefs[refTestInt]) && ! FTT.refTestSymLink && ( ! FTT.refTestSymLink || ! FTT.refSortedDoneRefs.includes(FTT.refTestSymLink[1].replace(/ /g,'_')) ) ) {
//					FTT.debug(FTT.refSourceRefs[refTestInt]);
//					FTT.debug('highlightRefGetRange: not a symlink, add to refSortedRefs');
					FTT.refSortedRefs.push(FTT.refSourceRefs[refTestInt]);
				}
			}
		}
		if ( FTT.refSortedRefs[refNum] ) {
			FTT.refIndexStart = FTT.refSourceText.split(FTT.refSortedRefs[refNum])[0].length;
			FTT.refIndexEnd = FTT.refIndexStart + FTT.refSortedRefs[refNum].length;
		}
		if ( ! FTT.refSortedRefs[refNum] || FTT.refIndexStart == FTT.refSourceText.length ) {
//			FTT.debug('highlightRefGetRange: no ref with that number could be found. maybe transcluded');
			delete FTT.refIndexStart;
			mw.notify(FTT.B1.table_pager_empty,{type:'error'});
			return;
		}
	}
};
FTT.HLSelectRef = function(RefStart,RefEnd) {
//	FTT.debug('FTT.HLSelectRef: call selectRange');
	FTT.selectRange(RefStart,RefEnd,1);
};
FTT.fuglyChevronFix = function() {
//	FTT.debug('fuglyChevronFix:');
	FTT.fuglyDiv = document.createElement('div');
	FTT.fuglyDiv.id = 'FTTUglyChevronFix';
	FTT.insertAfter($('.HLRefDropDown .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label')[0],FTT.fuglyDiv);
	$('#FTTUglyChevronFix')[0].style['background-image'] = 'linear-gradient(to '+FTT.CSSDirectionR+',rgba(0,0,0,0) 85%,'+getComputedStyle($('.HLRefDropDown .oo-ui-dropdownWidget-handle')[0])['background-color']+')';
};
FTT.highlightRef = function(e,refInt,noSelect,refTestInt,rInt) {
	//if any refs are the result of transclusion and the ref is not named and contains no URL or 7+ digit number this will not work. does not handle #tag:ref atm (and won't unless a use case is presented)
//	FTT.debug('highlightRef:');
	if ( ! FTT.referenceArray ) {
		$('#mw-content-text ol.references').append($('#mw-content-text ol.references>li style')[0]); //sometimes style gets included in innerText but not always? seems flaky, moving style element out of the way
		FTT.referenceArray = Array.from($('#mw-content-text ol.references>li'));
	}
	FTT.refEvent = e;
	if ( typeof refInt == 'undefined' && e ) {
		FTT.refNumInList = FTT.referenceArray.indexOf(e.target.parentElement);
	} else if ( refInt > -1 ) {
		FTT.refNumInList = refInt;
	} else {
		FTT.refNumInList = 0;
	}
	if ( ! $('#FTTUITextInput')[0] ) {
		FTT.PRMFPHighlightRef = $.extend( true, {highlightRef:1}, FTT.PRMFP ); // copy as = only creates a shortcut
		FTT.openReplyForm(FTT.PRMFPHighlightRef,'highlightRef');
		return;
	} else {
		FTT.refSourceText = FTT.getValue();
		if ( ! FTT.HLRefDropDown ) {
			FTT.HLRefDropDownOpts = [];
			FTT.templateRedLink = new RegExp('<a .*class="new[ "][^>]*>'+E1(FTT.NS[10])+':[^<]+<\/a>');
			for (rInt=0;rInt<FTT.referenceArray.length;rInt++) {
//				FTT.debug('highlightRef: rInt='+rInt);
				FTT.refArrLabel = '';
				try{FTT.refArrLabel = FTT.referenceArray[rInt].querySelectorAll('.reference-text')[0].innerText.split(/\n/)[0];} catch (event) {}
				if ( FTT.refArrLabel.length > 120 ) {
					FTT.refArrLabel = FTT.refArrLabel.slice(0,115)+'…';
				} else {
					FTT.refArrLabel = FTT.refArrLabel.slice(0,120);
				}
				if ( FTT.referenceArray[rInt].querySelectorAll('.cs1-visible-error')[0] || FTT.referenceArray[rInt].innerHTML.match(FTT.templateRedLink) ) {
					if ( M1('skin') == 'minerva' ) {
						FTT.refArrLabel = '! '+FTT.refArrLabel;
					} else {
						FTT.refArrLabel = new OO.ui.HtmlSnippet('<span class="FTTBadRef">'+FTT.escapeHTML(FTT.refArrLabel)+'</span>');
					}
				} else if ( M1('skin') == 'minerva' ) {
					FTT.refArrLabel = ' '+FTT.refArrLabel; //figure space
				}
				if ( FTT.refArrLabel != '' ) {
					FTT.HLRefDropDownOpts.push({data:rInt,label:FTT.refArrLabel});
				}
			}
			FTT.HLRefDropDown = new OO.ui.DropdownInputWidget( {
				id: 'HLRefDropDown',
				classes: ['HLRefDropDown'],
				options: FTT.HLRefDropDownOpts,
				value: FTT.refNumInList.toString(),
			});
			FTT.HLRefDropDown.on('change',function() {
//				FTT.debug('FTT.HLRefDropDown change event');
				FTT.highlightRefGetRange(Number(FTT.HLRefDropDown.getValue()));
				FTT.HLSelectRef(FTT.refIndexStart,FTT.refIndexEnd);
			});
			FTT.HLRefDropDownGo = new OO.ui.ButtonWidget( {
				label:FTT.B1.go,
			});
			FTT.HLRefDropDownGo.on('click',function(){
//				FTT.debug('FTT.HLRefDropDownGo click event');
				FTT.highlightRefGetRange(Number(FTT.HLRefDropDown.getValue()));
				FTT.HLSelectRef(FTT.refIndexStart,FTT.refIndexEnd);
			});
			FTT.HLRefDropDownL = new OO.ui.HorizontalLayout({id:'HLRefDropDownL',classes:['HLRefDropDownL'],items:[FTT.HLRefDropDown,FTT.HLRefDropDownGo]});
		} else {
			FTT.HLRefDropDown.setValue(FTT.refNumInList.toString());
		}
		if ( ! FTT.HLRefDropDownL.isElementAttached() ) {
			$('#FTTTextAndPreview').append(FTT.HLRefDropDownL.$element);
			FTT.insertAfter($('#FTTTextAndPreview')[0],$('#HLRefDropDownL')[0]);
			FTT.fuglyChevronFix();
		}
		if ( noSelect ) {
			return;
		}
//		FTT.debug('highlightRef: call highlightRefGetRange');
		FTT.highlightRefGetRange(FTT.refNumInList);
		$('#mw-content-text').removeClass('FTTNoDisplay');
//		FTT.debug('highlightRef: call HLSelectRef');
		FTT.HLSelectRef(FTT.refIndexStart,FTT.refIndexEnd);
	}
};
FTT.makeGradBG = function(gradInt) {
	//single solid color image for highlighting comments. Background size can't be set for color, so an image is needed.
	FTT.svgFTTBG = 'data:image/svg+xml,'+D1(FTT.svgHeader + 'width="720px" height="720px" viewBox="0 0 720 720"><rect width="720px" height="720px" fill="#a040ff" opacity="0.1"/></svg>');
	FTT.svgFTTBGGrad = [];
	for (gradInt=0;gradInt<10;gradInt++) {
//		FTT.debug('makeGradBG: gradInt='+gradInt);
		FTT.svgFTTBGGrad.push(FTT.svgFTTBG.replace('0.1','0.0'+gradInt));
		mw.util.addCSS('.FTTHLGrad'+gradInt+'{background-image:url("'+FTT.svgFTTBG.replace('0.1','0.0'+gradInt)+'");background-repeat:repeat-x;background-size:0px}');
	}
};
FTT.HLUncollapse = function(el,i) {
//	FTT.debug('HLUncollapse:');
	i=0;
	while ( FTT.sectionsAreCollapsed && el && i < 100) {
//		FTT.debug('HLUncollapse: looking for section div to check if its collapsed, int: '+i);
//		FTT.debug(el);
		if ( el.classList.contains('FTTH1SectContainer') || el.classList.contains('FTTH2SectContainer') ) {
			try{el.querySelectorAll('.FTTSVGChevronIconRot')[0].click();} catch (e) {}
			break;
		}
		i++;
		el = el.parentElement;
	}
};
FTT.HLCmt = function(cmtEl,cmtInt,openForm,rmDelay,scrollTo,cmtFadeInt,trigger) { //pass some element that is part of the comment to highlight the entire comment
	if ( ! FTT.svgFTTBGGrad ) {
		FTT.makeGradBG();
	}
	if ( $('#FTTUITextInput')[0] && trigger != 'SNH' ) {
//		FTT.debug('there is an open reply form, skip highlighting');
		return;
	}
	FTT.cmtEl = cmtEl;
	FTT.HLCmtArr = []; //if the comment consists of multiple paragraphs, all will be collected in this array
	if ( cmtInt != 1 && $('a:not(.FTTLastCmtLink):hover,.FTTForm:hover,.FTTLinks:hover')[0] ) {
//		FTT.debug('HLCmt: you clicked a link or an instance of a FTT form/link form, skip highlighting');
		return;
	}
	if ( $('#mw-content-text .FTTHighlightCmt:hover')[0] ) {
//		FTT.debug('HLCmt: you clicked an already highlighted element, removing highlight');
		FTT.UNHLCmt();
		return;
	}
	cmtInt=0;
	while ( FTT.cmtEl && ! ['DD','DL','P','OL','LI'].includes(FTT.cmtEl.nodeName) && cmtInt < 100) { //if a childnode was passed as an argument, go up the tree to find the container
//		FTT.debug('HLCmt: element is not a DD/DL/P/OL, try parentElement, int: '+cmtInt);
//		FTT.debug(FTT.cmtEl);
		cmtInt++;
		FTT.cmtEl = FTT.cmtEl.parentElement;
	}
	if ( ! FTT.cmtEl ) { //no more element
//		FTT.debug('HLCmt: element vanished, no parent was a dd/dl/p/ol/li');
		return;
	}
//	FTT.debug('HLCmt: using element:');
//	FTT.debug(FTT.cmtEl);
	FTT.cmtElHasReply = FTT.HLCmtHasReply(FTT.cmtEl);
	if ( FTT.cmtElHasReply ) {
//		FTT.debug('HLCmt: element contains reply links');
//		FTT.debug(FTT.cmtEl);
		FTT.HLCmtArr.push(FTT.cmtEl);
	} else {
//		FTT.debug('HLCmt: element does not contain reply links, maybe the nextElementSibling?');
		FTT.tryNextCmtEl = FTT.cmtEl;
		for(cmtInt=0;cmtInt<20;cmtInt++) {
//			FTT.debug('HLCmt: test element:');
//			FTT.debug(FTT.tryNextCmtEl);
			FTT.tryNextCmtEl = FTT.tryNextCmtEl.nextElementSibling;
			FTT.cmtElHasReply = FTT.HLCmtHasReply(FTT.tryNextCmtEl);
			if ( FTT.tryNextCmtEl && ['DD','DL','P','OL','LI','UL'].includes(FTT.tryNextCmtEl.nodeName) && FTT.cmtElHasReply ) {
				FTT.cmtEl = FTT.tryNextCmtEl;
//				FTT.debug('HLCmt: push found comment element');
//				FTT.debug(FTT.cmtEl);
				FTT.HLCmtArr.push(FTT.cmtEl);
				break;
			} else if ( ! FTT.tryNextCmtEl || ['H1','H2','H3','H4','H5','H6'].includes(FTT.tryNextCmtEl.nodeName) ) {
//				FTT.debug('HLCmt: no more element or encountered next section header but no reply found');
					return false;
			}
		}
	}
	if ( openForm && ( FTT.settings.editCmtDblClick || FTT.settings.replyDblClick ) && FTT.cmtElHasReply ) {
//		FTT.debug('HLCmt: requested a reply form to be opened');
		delete FTT.clickedRLP;
		FTT.dblClickEl = FTT.cmtElHasReply.querySelectorAll('.FTTEditLink a')[0];
		if ( FTT.settings.editCmtDblClick && FTT.dblClickEl ) {
//			FTT.debug(FTT.dblClickEl);
			try {FTT.clickedRLP = FTT.dblClickEl.attributes.onclick.nodeValue.match(/\[([0-9]*)\]/)[1];} catch (e) {}
			if ( FTT.clickedRLP ) {
//				FTT.debug('HLCmt: open form to edit comment');
				FTT.openReplyForm(FTT.PRMEdit[FTT.clickedRLP],'doubleclick');
				return;
			}
		}
		FTT.dblClickEl = FTT.cmtElHasReply.querySelectorAll('.FTTCmtLink')[0];
		if ( FTT.settings.replyDblClick && FTT.cmtElHasReply && FTT.dblClickEl ) {
//			FTT.debug('HLCmt: replyDblClick, cmtElHasReply');
//			FTT.debug(FTT.dblClickEl);
			try {FTT.clickedRLP = FTT.dblClickEl.parentElement.attributes.onclick.nodeValue.match(/\[([0-9]*)\]/)[1];} catch (e) {}
			if ( FTT.clickedRLP ) {
//				FTT.debug('HLCmt: open form to reply');
				document.getSelection().collapse($('body')[0]); //remove selection that double click may have caused
				FTT.openReplyForm(FTT.PRM[FTT.clickedRLP],'doubleclick');
				return;
			}
		}
	}
	if ( FTT.HLCmtArr.length == 0 ) {
//		FTT.debug('HLCmt: no comment element found');
	}
	FTT.tryPrevCmtEl = FTT.cmtEl;
	for(cmtInt=0;cmtInt<20;cmtInt++) {
//		FTT.debug('HLCmt: cmtInt='+cmtInt);
		FTT.tryPrevCmtEl = FTT.tryPrevCmtEl.previousElementSibling;
		FTT.cmtElHasReply = FTT.HLCmtHasReply(FTT.tryPrevCmtEl);
		if ( FTT.tryPrevCmtEl && ['DD','DL','P','OL','LI','UL'].includes(FTT.tryPrevCmtEl.nodeName) && ! FTT.cmtElHasReply ) {
//			FTT.debug('HLCmt: push tryPrevCmtEl');
//			FTT.debug(FTT.tryPrevCmtEl);
			FTT.HLCmtArr.push(FTT.tryPrevCmtEl);
		} else if ( ! FTT.tryPrevCmtEl || ['H1','H2','H3','H4','H5','H6'].includes(FTT.tryPrevCmtEl.nodeName) || FTT.cmtElHasReply ) {
//			FTT.debug('HLCmt: no more tryPrevCmtEl or encountered header or encountered element with another reply');
			break;
		}
	}
	if ( typeof openForm == 'undefined' ) {
		FTT.HLUncollapse(cmtEl);
		for (cmtInt=0;cmtInt<FTT.HLCmtArr.length;cmtInt++) {
//			FTT.debug('HLCmt: openform is undefined, cmtInt='+cmtInt);
			if ( typeof rmDelay == 'number' && rmDelay != 0 ) {
//				FTT.debug('HLCmt: highlighting element ('+cmtInt+')');
				FTT.HLCmtArr[cmtInt].classList.add('FTTHighlightCmt');
			}
		}
		if ( FTT.HLCmtArr.length ) {
//			FTT.debug('HLCmt: set height for element with signature in HLCmtArr');
			FTT.HLCmtSigEl = FTT.HLCmtArr[0].querySelectorAll('.FTTCmt,.LegacyCmt')[0];
			if ( FTT.HLCmtSigEl ) {
				$('.ext-discussiontools-init-targetcomment').removeClass('ext-discussiontools-init-targetcomment');
				cmtFadeInt = 0;
				var CMTGradInt = setInterval(function () {
//					FTT.debug('HLCmt: highlight fade step '+cmtFadeInt);
					for ( cmtInt=0;cmtInt<FTT.HLCmtArr.length;cmtInt++) {
//						FTT.debug('HLCmt: highlight array element #'+cmtInt+', fade step '+cmtFadeInt);
						if ( cmtFadeInt == 0 ) {
							FTT.HLCmtArr[cmtInt].classList.add('FTTHighlightCmt');
						}
						if ( cmtInt == 0 ) {
							FTT.HLCmtArr[cmtInt].style['background-size'] = (( FTT.HLCmtSigEl.offsetTop + FTT.HLCmtSigEl.getBoundingClientRect().height ) - FTT.HLCmtArr[0].offsetTop).toString() + 'px';
						} else {
							FTT.HLCmtArr[cmtInt].style['background-size'] = 'auto';
						}
						FTT.HLCmtArr[cmtInt].classList.add('FTTHLGrad'+cmtFadeInt);
						FTT.HLCmtArr[cmtInt].classList.remove('FTTHLGrad'+(cmtFadeInt -1));
						cmtFadeInt++;
					}
					if ( cmtFadeInt > 9 ) {
						clearInterval(CMTGradInt);
					}
				},35);
			}
		}
		if ( scrollTo && FTT.HLCmtArr.length ) {
			FTT.magicScroll(FTT.HLCmtArr[FTT.HLCmtArr.length -1]);
			FTT.disableRejump = true;
		}
		if ( rmDelay ) {
			var UNHLDelay = setInterval(function () {
//				FTT.debug('UNHLDelay');
				clearInterval(UNHLDelay);
				FTT.UNHLCmt();
			}, rmDelay);
		}
	}
};
FTT.UNHLCmt = function(cmtFadeOutInt,cmtInt) {
//	FTT.debug('UNHLCmt:');
	$('#mw-content-text .FTTHighlightCmt').removeClass(['FTTHighlightCmt']);
	cmtFadeOutInt = 9;
	var CMTGradInt2 = setInterval(function () {
//		FTT.debug('HLCmt: UNhighlight fade step '+cmtFadeOutInt);
		if ( cmtFadeOutInt > 0 ) {
			$('.FTTHLGrad'+cmtFadeOutInt).addClass('FTTHLGrad'+(cmtFadeOutInt-1));
		} else {
			clearInterval(CMTGradInt2);
		}
		$('.FTTHLGrad'+cmtFadeOutInt).removeClass('FTTHLGrad'+(cmtFadeOutInt));
		cmtFadeOutInt = cmtFadeOutInt -1;
	},35);
};
FTT.HLCmtHasReply = function(cmtElR,cmtInt) {
//	FTT.debug('HLCmtHasReply:');
	if ( ! cmtElR ) {
		return false;
	}
	for (cmtInt=0;cmtInt<cmtElR.children.length;cmtInt++) {
		if ( cmtElR.children[cmtInt].classList.contains('FTTCmt') || cmtElR.children[cmtInt].classList.contains('LegacyCmt')) {
			return cmtElR.children[cmtInt];
		}
	}
	return false;
};
FTT.scrollNextHeadline = function(prev,holdCtrlAlt,cmt,firstEl,hInt,elFVH,HL) {
//	FTT.debug('scrollNextHeadline:');
	if ( FTT.scrolling ) {
//		FTT.debug('scrollNextHeadline: scrolling operation in progress, abort');
		return;
	}
	delete FTT.SNHLastHeader;
	if ( holdCtrlAlt && cmt && FTT.prevSNHType == 'headline' ) {
		FTT.SNHLastHeader = $('#mw-content-text .mw-headline')[FTT.SNHLast];
		if ( FTT.SNHLastHeader ) {
			FTT.SNHLastHeader = FTT.SNHLastHeader.getBoundingClientRect().y;
		}
	}
	if ( cmt ) {
		HL = $('#mw-content-text .FTTCommentLinks:not(.FTTNoDisplay)');
		FTT.prevSNHType = 'cmt';
	} else {
		HL = $('#mw-content-text .mw-headline');
		FTT.prevSNHType = 'headline';
	}
//	FTT.debug('scrollNextHeadline: start');
	if ( FTT.SNHLastType != cmt ) {
		delete FTT.SNHLast;
	}
	FTT.SNHLastType = cmt;
	if ( holdCtrlAlt && prev && FTT.SNHLast == 0 ) {
//		FTT.debug('scrollNextHeadline: already at the top. let go of your keyboard');
		return;
	} else if ( holdCtrlAlt && prev && FTT.SNHLast ) { //at the first element (0) this evaluates to false
		FTT.SNHLast = FTT.SNHLast -1;
		if ( cmt ) {
			FTT.UNHLCmt();
			FTT.HLCmt(HL[FTT.SNHLast],undefined,undefined,1000,1,undefined,'SNH');
		} else {
			FTT.HLscroll(HL[FTT.SNHLast]);
		}
		try{HL[FTT.SNHLast].parentElement.querySelectorAll('.mw-editsection a,.FTTCmtA')[0].focus();} catch(e) {}
		return;
	} else if ( holdCtrlAlt && ! prev && typeof FTT.SNHLast == 'number' ) {
		if ( HL[FTT.SNHLast+1] ) {
			FTT.SNHLast++;
			if ( cmt ) {
				FTT.HLCmt(HL[FTT.SNHLast],undefined,undefined,1000,1,undefined,'SNH');
			} else {
				FTT.HLscroll(HL[FTT.SNHLast]);
			}
			try{HL[FTT.SNHLast].parentElement.querySelectorAll('.mw-editsection a,.FTTCmtA')[0].focus();} catch(e) {}
		}
		return;
	}
	for (hInt=0;hInt<HL.length;hInt++) {
		elFVH = HL[hInt].getBoundingClientRect();
		if ( FTT.SNHLastHeader > 0 && elFVH.y > FTT.SNHLastHeader ) {
//			FTT.debug('scrollNextHeadline: comment '+(hInt)+' is the first comment below the last header');
			FTT.HLCmt(HL[hInt],undefined,undefined,1000,1,undefined,'SNH');
			try{HL[hInt].parentElement.querySelectorAll('.mw-editsection a,.FTTCmtA')[0].focus();} catch(e) {}
			FTT.SNHLast = hInt;
			return;
		}
		if ( elFVH.width == 0 && elFVH.height == 0 ) {
//			FTT.debug('scrollNextHeadline: invisible element or something, skipping');
			continue;
		//} else if ( ! firstEl ) {
		//	firstEl = HL[hInt];
		}
		if ( prev && elFVH.y > 0 && hInt > 0 ) {
//			FTT.debug('scrollNextHeadline: header '+(hInt -1)+' is the first header above the viewport');
			if ( cmt ) {
				FTT.HLCmt(HL[hInt -1],undefined,undefined,1000,1,undefined,'SNH');
			} else {
				FTT.HLscroll(HL[hInt -1]);
			}
			try{HL[hInt -1].parentElement.querySelectorAll('.mw-editsection a,.FTTCmtA')[0].focus();} catch(e) {}
			FTT.SNHLast = hInt -1;
			return;
		} else if ( elFVH.y > window.innerHeight ) {
//			FTT.debug('scrollNextHeadline: header '+hInt+' is the first header below the viewport');
			if ( cmt ) {
				FTT.HLCmt(HL[hInt],undefined,undefined,1000,1,undefined,'SNH');
			} else {
				FTT.HLscroll(HL[hInt]);
			}
			try{HL[hInt].parentElement.querySelectorAll('.mw-editsection a,.FTTCmtA')[0].focus();} catch(e) {}
			FTT.SNHLast = hInt;
			return;
		}
	}
	//if ( firstEl ) { //this works fine, but not sure if this behavior would be desired
//	//	FTT.debug('scrollNextHeadline: scroll to first mw-headline');
	//	FTT.scrollAndCall(firstEl,null,'magic');
	//}
};
FTT.tabHeaderHotKey = function(cmt,next,int) {
	FTT.tabGetHeader = document.activeElement;
	while ( FTT.tabGetHeader && ( ! FTT.tabGetHeader.nodeName.match(/^H[1-6]$/) && ! FTT.tabGetHeader.classList.contains('FTTCommentLinks') ) ) { //keep going up, end up with either null or .mw-editsection
//		FTT.debug('tabHeaderHotKey while');
		FTT.tabGetHeader = FTT.tabGetHeader.parentElement;
	}
	if ( FTT.tabGetHeader ) { //apparently not null
		FTT.tabLinksRaw = Array.from(FTT.tabGetHeader.querySelectorAll('a:not(.FTTNoDisplay),.FTTGenLink,.FTTCmtA,.FTTSecEdit,.FTTSubscribe'));
		FTT.tabLinks = [];
		for (int=0;int<FTT.tabLinksRaw.length;int++) {
			if ( getComputedStyle(FTT.tabLinksRaw[int]).display != 'none' && getComputedStyle(FTT.tabLinksRaw[int].parentElement).display != 'none' ) {
				FTT.tabLinks.push(FTT.tabLinksRaw[int]);
			}
		}
		FTT.tabLinksIndex = FTT.tabLinks.indexOf(document.activeElement);
		if ( FTT.tabLinksIndex != -1 ) {
			if ( next ) {
				FTT.tabLinksInt = FTT.tabLinksIndex+1;
			} else {
				FTT.tabLinksInt = FTT.tabLinksIndex-1;
			}
			if ( FTT.tabLinks[FTT.tabLinksInt] ) {
				FTT.tabLinks[FTT.tabLinksInt].focus();
			} else if ( next ) {
				FTT.tabLinks[0].focus();
			} else {
				FTT.tabLinks[FTT.tabLinks.length -1].focus();
			}
		}
	}
};
if ( FTT.settings.shortcuts ) {
	$('body').on('keydown',function(event){
		if ( event.ctrlKey && event.altKey && [38,40,78,80,37,39,74,76].includes(event.which) && ! ['TEXTAREA','INPUT'].includes(document.activeElement.nodeName) ) {
			if ( [40,78].includes(event.which) ) {
//				FTT.debug('pressed ctrl+alt+n to scroll to next headline');
				FTT.scrollNextHeadline(undefined,FTT.holdCtrlAlt,event.shiftKey);
			} else if ( [38,80].includes(event.which) ) {
//				FTT.debug('pressed ctrl+alt+p to scroll to previous headline');
				FTT.scrollNextHeadline('prev',FTT.holdCtrlAlt,event.shiftKey);
			} else if ( [37,74].includes(event.which) ) { //pressed j or pressed left
//				FTT.debug('pressed j/left, tab to previous');
				FTT.tabHeaderHotKey(event.shiftKey);
			} else if ( [39,76].includes(event.which) ) { //pressed l or pressed right
//				FTT.debug('pressed l/right, tab to next');
				FTT.tabHeaderHotKey(event.shiftKey,'next');
			}
			FTT.holdCtrlAlt = true;
		} else if ( FTT.holdCtrlAlt && event.ctrlKey && event.altKey && event.which == 13 && FTT.settings.collapsible && document.activeElement.parentElement.classList.contains('mw-editsection') ) { //pressed enter
			event.preventDefault();event.stopPropagation();			
//			FTT.debug('pressed ctrl+alt+enter while scrolling between sections, open/close last highlighted section');
			FTT.hotKeyCollap = 1;
			try{$('#mw-content-text .mw-headline')[FTT.SNHLast].parentElement.querySelectorAll('.FTTSVGChevronIcon')[0].click();} catch (e) {}
			FTT.hotKeyCollap = 0;
		} else if ( event.ctrlKey && event.shiftKey && event.which == 191 && ! $('html.ve-activated')[0] ) {//pressed /
			if ( ! $('#FTTReplyForm')[0] ) {
				FTT.PRMSettings = $.extend( true, {justSettings:1}, FTT.PRMnSec ); // copy as = only creates a shortcut
				FTT.openReplyForm(FTT.PRMSettings,'slash');
			} else {
				FTT.openSettings();
			}
		} else if ( event.ctrlKey && event.which == 191 && ! $('html.ve-activated')[0] ) {//pressed /
			FTT.popup(FTT.msgs.shortcutsMap);
		} else if ( event.which == 13 && ( document.activeElement.classList.contains('FTTGenLink') || document.activeElement.classList.contains('FTTThankLink') || document.activeElement.classList.contains('FTTCmtA') || document.activeElement.classList.contains('FTTCollapMini') || document.activeElement.classList.contains('FTTSecEdit') || document.activeElement.classList.contains('FTTSubscribe') ) ) {
			event.preventDefault();event.stopPropagation();
			document.activeElement.click();
		} else if ( event.altKey && event.key == 'i' ) {//pressed i (73)
			event.preventDefault();
			FTT.focusInput();
		}
	});
	$('body').on('keyup',function(event){
		if ( ! event.ctrlKey || ! event.altKey ) {
			FTT.holdCtrlAlt = false;
			delete FTT.SNHLast;
			delete FTT.prevSNHType;
		}
	});
}
FTT.addRefListMarkers = function() {
	if ( FTT.settings.editRefs && ! $('a.FTTRef')[0] ) {
		FTT.editRefMarker = document.createElement('a');
		FTT.editRefMarker.className = 'FTTReplyLink FTTSVGEditIcon FTTSVG FTTRef';
		FTT.editRefMarker.title = FTT.msgs.editRef;
		FTT.editRefMarker.innerHTML = '<span class="FTTScreenReaderLabel">'+FTT.msgs.editRef+'</span>';
		$('ol.references li').append(FTT.editRefMarker);
		$('ol.references li .FTTSVGEditIcon').on('click',FTT.highlightRef);
	}
};
FTT.addRefListMarkers();
FTT.postReply1 = function(PRM,postReplyTrigger) {
try{
//	FTT.debug('postReply1, trigger: ' + postReplyTrigger);
	delete FTT.escPageXOffset; //if form was brought into focus by pressing escape, don't scroll back to where you were (that only happens if you cancel the form)
	delete FTT.escPageYOffset;
	if ( PRM.type != 'newheading' && FTT.UITextInputTitle.getValue() == '' && FTT.UITextInputTitle.isVisible() && ! FTT.ignoreEmptyTitle ) {
		FTT.UITextInputTitle.focus();
		$(FTT.BTitleEl).removeClass('FTTShakeIt');
		$(FTT.BTitleEl)[0].style = 'transition:all 0.2s ease-in !important';
		$(FTT.BTitleEl).addClass('FTTEaseIn FTTRedBG');
		var FTTShakeTitle = setInterval(function () {
				clearInterval(FTTShakeTitle);
				$(FTT.BTitleEl).addClass('FTTShakeIt');
		}, 20);
		FTT.UITextInputTitle.on('change',function(){if(FTT.UITextInputTitle.getValue() != ''){$(FTT.BTitleEl).removeClass('FTTRedBG');}});
//		FTT.debug('title is empty');
		FTT.postReplyInProgress[PRM.id] = false;
		FTT.ignoreEmptyTitle = true;
		return;
	}
	FTT.postReplyInProgress[PRM.id] = true;
//	if ( ! FTT.dryRunOnce ) { //FTT.debug
//		FTT.timeOutWait = 10000; //FTT.debug
//		if ( FTT.settings.doubleTimeout ) { //while on long pages 11-15 seconds does happen IRL, 20s would be VERY rare FTT.debug
//			FTT.timeOutWait = 20000;//FTT.debug
//		}//FTT.debug
//		var DelayedSuccessCheck = setInterval(function () { //FTT.debug
//			clearInterval(DelayedSuccessCheck); //FTT.debug
//			if ( FTT.postReplyInProgress[PRM.id] == true ) { //FTT.debug
//				FTT.debug('no successful exit was detected from postReply1. Trigger: ' + postReplyTrigger);
//				if ( FTT.settings.debug ) { FTT.addScrewedLink('submit timeout ' + postReplyTrigger,'After attempting to submit, success was not detected after 10 seconds. Sometimes an edit takes longer, but this may indicate something went wrong.','FTTSubmitTimeout'); } //caused more problems than it solved, restricted to debug mode now. FTT.debug
//			} //FTT.debug
//		}, FTT.timeOutWait); //FTT.debug
//	} //FTT.debug
	FTT.disableForm(true); //disable form so you can't double click
	$(document.getElementById('FTTUIReplyButton')).addClass('FTTPendingBlink');
	if ( M1('wgArticleId') == 0 ) { //special pages or otherwise no valid article
		FTT.postReply2(PRM, '', 0);
		return;
	}
//	FTT.debug('get the wikitext of the page we want to add the comment to');
	FTT.currentPageTextParams = {'action':'query','prop':'revisions|info','format': 'json','export':'true','titles': PRM.pageTitle,'inprop':'watched', 'intestactions':'edit'};
	api.get(FTT.currentPageTextParams).then(function(data) {
		try{
//		FTT.debug(data);
		if ( typeof data.query.pages[ Object.keys(data.query.pages)[0] ].actions.edit != 'string' ) {
//			FTT.debug('you can\'t edit this page');
			FTT.showProtectedWarning(PRM.pageTitle,1);
			FTT.disableForm(false);
			FTT.postReplyInProgress[PRM.id] = false;
			return;
		}
		if ( typeof data.query.pages[ Object.keys(data.query.pages)[0] ].watched == 'string' ) {
			FTT.watched = true;
			if ( typeof data.query.pages[ Object.keys(data.query.pages)[0] ].watchlistexpiry == 'string' ) {
				FTT.watchlistexpiry = new Date(data.query.pages[ Object.keys(data.query.pages)[0] ].watchlistexpiry).getTime();
			}
		} else {
			FTT.watched = false;
		}
		if ( ! data.query.pages[-1] ) {
//			FTT.debug('got wikitext');
			FTT.justCurrentPageText = FTT.repairWikiText(FTT.getWikitextFromExport(data.query.export["*"]));
			if ( PRM.type == 'editFullPage' || PRM.type == 'heading' ) { //while comments can be inserted into changed wikitext, full section/page wikitext is always replaced so we must report the baserevid of the source text we preloaded
//				FTT.debug('postReply1: use source text preload revision ID as baserevid');
				FTT.currentRevID = FTT.sourceRev;
			} else {
//				FTT.debug('postReply1: use current revision ID as baserevid');
				FTT.currentRevID = data.query.pages[ Object.keys(data.query.pages)[0] ].revisions[0].revid;
			}
		} else {
//			FTT.debug('response indicates this page doesn\'t exist');
			FTT.justCurrentPageText = '';
			FTT.currentRevID = 0;
		}
//		FTT.debug('got wikitext');
		if ( FTT.settings.checkNewComments && M1('wgArticleId') != 0 && ! data.query.pages[-1] && FTT.pageRevisionIDSinceLastCheck && FTT.pageRevisionIDSinceLastCheck[PRM.pageTitle] == FTT.currentRevID ) {
//			FTT.debug('no new revisions since the last time we checked, moving on towards postReply2');
		} else if ( FTT.settings.checkNewComments && M1('wgArticleId') != 0 && FTT.PRMOpened.type == 'comment' ) {
//			FTT.debug('check for new comments');
			FTT.postReplyInProgress[PRM.id] = false;
			FTT.checkForNewComments(PRM, 'postreply', FTT.justCurrentPageText, FTT.currentRevID);
			return; //kill it for now, FTT.checkForNewComments() will call postReply2 if there are no new comments
		}
		FTT.postReply2(PRM, FTT.justCurrentPageText, FTT.currentRevID);
		} catch (e) {
			FTT.JSError(e);
		}
	}, function ( code, data ) { FTT.APIError(code, data);
	});
} catch (e) {
	FTT.JSError(e);
}
};
FTT.postReply2 = function(PRM, justCurrentPageText, currentRevID, skipWarn) {
try{
//	FTT.debug('postReply2, process comment and actually post it');
	if ( PRM.type != 'editFullPage' ) {
		if ( PRM.type == 'edit' ) {
			FTT.commentMilliseconds = PRM.origTimestamp.toString().slice(-3);
		} else {
			FTT.commentMilliseconds = new Date().getTime().toFixed().slice(-3); //indeed, the local clock may differ from server time which we primarily rely on through {{subst:#time:xNU}}. And it's irrelevant as the milliseconds are only used to make the identifier more unique. Making two edits within the same second is already implausible but within the same millisecond? This doesn't solve the issue of non-FTT users adding multiple comments in a single edit, but solving that would require MediaWiki to support "random" as a magic word that can produce different numbers when called multiple times within the same edit. Or have a bot that slightly adjusts timestamps to ensure they're unique.
		}
		if ( PRM.sectionTitle && PRM.sectionTitle != "" ) {
			FTT.postCommentSummarySectionLink = '/* ' + PRM.sectionTitle + ' */ ';
		} else {
			FTT.postCommentSummarySectionLink = ''; //no section? no link
		}
	}
	if ( FTT.activeEditor == 'visualLight' ) {
		FTT.syncToSource();
	}
	if ( FTT.getValue() == '' && ! skipWarn ) {
//		FTT.debug('no text entered');
		mw.loader.using(['oojs-ui-core','oojs-ui-windows']).then(function(){
			FTT.FFDarkMode();
			$('#FTTOverlay').addClass('FTTNoDisplay');
			OO.ui.confirm(FTT.B1['confirmable-confirm']).done( function(confirmd) {
				FTT.FFDarkMode(1);
				$('#FTTOverlay').removeClass('FTTNoDisplay');
				if (confirmd) {
					FTT.postReply2(PRM,justCurrentPageText,currentRevID,true);
				} else {
					FTT.disableForm(false);
					FTT.postReplyInProgress[PRM.id] = false;
				}
			});
		});
		return;
	}
	$('#mw-content-text .FTTFirstReply').removeClass('FTTNoDisplay');
	FTT.processedComment = FTT.processComment(FTT.getValue(),'edit');
//	FTT.debug('comment processed');
//	FTT.debug('PRM:');
//	FTT.debug(PRM);
	if ( currentRevID != 0 || FTT.settings.watchlistexpirynew == 'same' ) {
		FTT.watchSetting = FTT.settings.watchlistexpiry;
	}	else {
		FTT.watchSetting = FTT.settings.watchlistexpirynew;
	}
	if ( FTT.watched && typeof FTT.watchlistexpiry != 'number' && FTT.settings.watchlist == 'watch' ) {
//		FTT.debug('you\'re already watching this page indefinitely');
		FTT.newWatchSetting = 'nochange';
		FTT.newExpiry = undefined;
	} else if ( FTT.watched && typeof FTT.watchlistexpiry == 'number' && FTT.settings.watchlist == 'watch' && FTT.watchSetting != 'indefinite' ) {
//		FTT.debug('you are watching this page with an expiration date');
		if ( FTT.watchSetting.slice(0,1) == '+' ) {
			FTT.watchUntil = FTT.watchlistexpiry + (FTT.watchSetting.match(/([0-9]+)/)[0] * 86400000);
		} else {
			FTT.watchUntil = new Date().getTime() + (FTT.watchSetting.match(/([0-9]+)/)[0] * 86400000);
		}
		if ( FTT.watchUntil < FTT.watchlistexpiry ) {
//			FTT.debug('you\'re already watching this page for a longer period than your watchlist setting, your watchlist expiry for this page will remain unchanged');
			FTT.newWatchSetting = 'nochange';
			FTT.newExpiry = undefined;
		} else {
			FTT.newWatchSetting = 'watch';
			FTT.newExpiry = new Date(FTT.watchUntil).toISOString();
		}
	} else {
		FTT.newWatchSetting = FTT.settings.watchlist;
		if ( FTT.watchSetting.slice(0,1) == '+' ) {
			FTT.newExpiry = FTT.watchSetting.slice(1);
		} else {
			FTT.newExpiry = FTT.watchSetting;
		}
	}
	FTT.postCommentParams = {
		format: 'json',
		assert:FTT.assert,
		action: 'edit',
		title: PRM.pageTitle,
		baserevid: currentRevID,
		nocreate: true,
		watchlist: FTT.newWatchSetting
	};
	if ( FTT.newWatchSetting != 'nochange' && FTT.newWatchSetting != 'unwatch' ) {
		FTT.postCommentParams.watchlistexpiry = FTT.newExpiry;
	}
//	FTT.debug('posting type and subtype: ' + PRM.type + '/' + PRM.subtype);
	FTT.summarySnippetText = '';
	FTT.unQuote = new RegExp('["„“«»”「\'][ \']?\\$1[ \']?["“«»”」\']');
	if ( FTT.settings.sumSnippet && ['comment','newheading'].includes(PRM.type) && FTT.processedComment ) {
		FTT.snippetRemoveRecipient = new RegExp('^' + E1(FTT.PRMOpened.origReplyTo) + '[ ,:;]*'); //having "replying to USER: USER, foo bar.." in the summary is redundant
		FTT.snippetForSummary = FTT.flattenWikiText(FTT.processedComment).replace(/^[\\:\\*\\#]*/, '').replace(FTT.snippetRemoveRecipient, '').replace(/[ ]?—&nbsp;$/,'');
		if ( FTT.snippetForSummary.length > 0 ) {
			FTT.snippetForSummaryShort = FTT.snippetForSummary;
			if ( FTT.snippetForSummary.length > 50 ) {
				FTT.snippetForSummaryShort = FTT.snippetForSummary.slice(0,40) + '...';
			}
			FTT.summarySnippetText = ' "' + FTT.snippetForSummaryShort + '"';
		}
	}
	if ( PRM.type == 'comment' ) {
		FTT.postCommentParams.summary = FTT.postCommentSummarySectionLink + FTT.wikiMsgs.postCmtSummaryPost.replace(/USER/g, FTT.escapeReplacement(PRM.origReplyTo.replace(/_/g,' ')+FTT.summarySnippetText));
		FTT.origCmtData = FTT.getInsertionPointComment(PRM, justCurrentPageText);
		if ( FTT.origCmtData == 'retry' ) {
//			FTT.debug('no insertion point, possible moved locator, step back to postReply1');
			FTT.postReply1(FTT.PRMOpened,'getInsertionPointComment');
			return;
		} else if ( FTT.origCmtData == 'ERRORBAD' ) {
//			FTT.debug('no coming back from this..');
			return;
//		} else { FTT.debug('got insertion point');
		}
		if ( FTT.origCmtData.sectiontext != 'SECTIONLESS' ) {
			FTT.postCommentParams.section = FTT.origCmtData.sectionnum;
			FTT.originalWikiText = FTT.origCmtData.sectiontext;
		} else {
			FTT.originalWikiText = justCurrentPageText;
		}
		FTT.checkHigherLevelIndentRegExp = new RegExp('^([\:\#\*]{' + (FTT.origCmtData.relevantIndentation.length + 1) + '}).*' + FTT.signDateRegExpLocalMonths.source,'m'); //look for comments with higher indentation levels than what we're replying to. if found, no outdent
		FTT.origCmtLevelPlusOne = FTT.origCmtData.relevantIndentation.length + 1;
		FTT.replyBelowThisRegExp = new RegExp('(' + E1(FTT.origCmtData.relevantPrecedingLine + FTT.origCmtData.relevantComment) + ')((\n[\\:\\*\\#]{' + FTT.origCmtLevelPlusOne + ',}([^\\:\\*\\#\\=]|[$]).*)*)', 'm');
		FTT.replyBelowThisMatch = FTT.originalWikiText.match(FTT.replyBelowThisRegExp);
		if ( ! FTT.replyBelowThisMatch ) {
			FTT.replyBelowThisRegExp = new RegExp('(' + E1(FTT.origCmtData.relevantComment) + ')((\n[\\:\\*\\#]{' + FTT.origCmtLevelPlusOne + ',}([^\\:\\*\\#\\=]|[$]).*)*)', 'm');
			FTT.replyBelowThisMatch = FTT.originalWikiText.match(FTT.replyBelowThisRegExp);
		}
		if ( ! FTT.replyBelowThisMatch ) {
			FTT.addScrewedLink('insertion point RegExp no match','Insertion point not found.');
			throw 'FTT: insertion point RegExp no match (FTT.replyBelowThisMatch is null)';
		}
		FTT.replyBelowThis = FTT.replyBelowThisMatch[0];
		FTT.checkAlreadyOutdentedRegExp = new RegExp(E1(FTT.origCmtData.relevantComment) + '\n[:\\*].*┌[─]*┘');
		if (FTT.origCmtData.relevantIndentation.length >= FTT.settings.outdent && ! FTT.cleanTimestamp(FTT.origCmtData.sectiontext).match(FTT.checkHigherLevelIndentRegExp) && ( FTT.origCmtData.sectiontext && ! FTT.origCmtData.sectiontext.match(FTT.checkAlreadyOutdentedRegExp) ) ) {
//			FTT.debug('maximum indentation exceeded, outdenting');
			FTT.outdentLineLength = ( FTT.origCmtData.relevantIndentation.length - 1) * 3;
			FTT.commentTextIndent = FTT.origCmtData.relevantIndentation.slice(FTT.origCmtData.relevantIndentation.length - 1) + '┌';
			for (FTT.indentint = 0; FTT.indentint < FTT.outdentLineLength; FTT.indentint++) {
				FTT.commentTextIndent = FTT.commentTextIndent + '─';
			}
			FTT.commentTextIndent = FTT.commentTextIndent + '┘<br/>';
		} else if (FTT.origCmtData.relevantIndentation.length != 0) {
//			if ( FTT.origCmtData.sectiontext && FTT.origCmtData.sectiontext.match(FTT.checkAlreadyOutdentedRegExp) ) { FTT.debug('You\'re replying to a comment that is followed by an outdent. You do know that\'s bad form, right? Err, good for you? Fight the power I guess? No outdent for you'); }
//			FTT.debug('get indendation from the line we will be posting under');
			FTT.commentTextIndent = FTT.replyBelowThis.split(/\n/)[FTT.replyBelowThis.split(/\n/).length -1].match(new RegExp('[\\:\\#\\*]{' + FTT.origCmtData.relevantIndentation.length + ',' + (FTT.origCmtData.relevantIndentation.length +1) + '}'))[0]; //we copy the indentation of the comment right above us instead of what we're replying to, which may or may not be the same. This prevents screwups in mixed indentation discussions
			if ( FTT.origCmtData.relevantIndentation == FTT.commentTextIndent ) {
//				FTT.debug('we\'re replying to the most indented comment in this tree, repeating the last character of the indentation of the previous message so e.g. ::* becomes ::**');
				FTT.commentTextIndent = FTT.commentTextIndent + FTT.commentTextIndent.slice(FTT.origCmtData.relevantIndentation.length - 1);
			}
			if ( FTT.settings.preventDoubleHashtag && FTT.commentTextIndent.match(/##$/) ) {
				FTT.commentTextIndent = FTT.commentTextIndent.replace(/##$/,'#:');
			}
//			FTT.debug('new indendation: ' + FTT.commentTextIndent);
		} else {
			FTT.commentTextIndent = FTT.getMostPopularIndent(justCurrentPageText, FTT.origCmtData.sectiontext);
		}
		if ( FTT.oTT && FTT.oTT.archiveSection.isSelected() && typeof FTT.postCommentParams.section == 'number' ) {
			FTT.archiveResult = '';
			if ( FTT.UITextInputSummary.getValue() != '' ) {
				FTT.archiveResult = FTT.UITextInputSummary.getValue();
			}
			if ( FTT.B1.atops != '' && FTT.B1.abots != '' ) {
				FTT.atopTemplate = '\n{{subst:' + FTT.B1.atops + '|1=COMMENT|2=EDITSUMMARY}}';
				FTT.abotTemplate = '\n{{subst:' + FTT.B1.abots + '|1=COMMENT|2=EDITSUMMARY}}';
			} else {
				if ( FTT.B1.lockicon != '' ) {
					FTT.atopTemplateLockIcon = '[[File:' + FTT.B1.lockicon + '|60px|right]]';
				} else {
					FTT.atopTemplateLockIcon = '<div style="float:right;font-size:xx-large">&#x1F512;</div>';
				}
				FTT.atopTemplate = '\n<div class="archived" style="background:#EDEAFF;padding:0.5em;border:0.1em solid grey">' + FTT.atopTemplateLockIcon;
				FTT.abotTemplate = '\n----\nCOMMENT\n</div>';
			}
			FTT.postCommentParams.text = FTT.originalWikiText
				.replace(/^(=.*=)/,'$1\n' + FTT.atopTemplate
					.replace(/EDITSUMMARY/g,FTT.escapeReplacement(FTT.archiveResult))
						.replace(/COMMENT/g,FTT.escapeReplacement(FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment)))
				).replace(/$/,FTT.abotTemplate
					.replace(/EDITSUMMARY/g,FTT.archiveResult)
					.replace(/COMMENT/g,FTT.escapeReplacement(FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment)))
				);
		} else {
			FTT.postCommentParams.text = FTT.originalWikiText.replace(FTT.replyBelowThis, FTT.replyBelowThis + '\n' + FTT.commentTextIndent + FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment));
		}
		FTT.insertAnchor = function() {
			FTT.addAnchorsRegExp = new RegExp('\\[\\[\#([0-9]{12})[_ ]([^\\|\\]]+)(\\||\\]\\])','g');
			FTT.addAnchorsRegExpGroups = new RegExp(FTT.addAnchorsRegExp.source);
			FTT.addAnchorsIntMatches = FTT.processedComment.match(FTT.addAnchorsRegExp);
			if ( FTT.addAnchorsIntMatches ) {
//				FTT.debug('insertAnchor: post contains links for which anchors may need to be added');
				for (FTT.addAnchorsInt=0;FTT.addAnchorsInt<FTT.addAnchorsIntMatches.length;FTT.addAnchorsInt++) {
//					FTT.debug('insertAnchor: check' + FTT.addAnchorsIntMatches[FTT.addAnchorsInt]);
					FTT.addAnchorTarget = FTT.addAnchorsIntMatches[FTT.addAnchorsInt].match(FTT.addAnchorsRegExpGroups);
					for (FTT.addAnchorLoopRLPInt=0;FTT.addAnchorLoopRLPInt<Object.keys(FTT.PRM).length;FTT.addAnchorLoopRLPInt++) {
						FTT.checkParams = FTT.PRM[Object.keys(FTT.PRM)[FTT.addAnchorLoopRLPInt]];
						FTT.checkParams = FTT.addPageAndSectionTitleToRPL(FTT.checkParams);
						if ( FTT.checkParams.origReplyTo == FTT.addAnchorTarget[2] && FTT.checkParams.pageTitle.replace(/ /g,'_') == PRM.pageTitle.replace(/ /g,'_') && FTT.checkParams.sectionTitle.replace(/ /g,'_') == PRM.sectionTitle.replace(/ /g,'_') && FTT.checkParams.sectionseq == PRM.sectionseq ) {
							if ( FTT.unixTimeToFlatDate(FTT.sigDateToMachineReadable(FTT.checkParams.origTimestamp,true)) == FTT.addAnchorTarget[1] ) {
//								FTT.debug('insertAnchor: PRM #' + FTT.checkParams.int + ' seems to match user/section/timestamp of link');
								FTT.addAnchorReplaceRegExp = new RegExp('([\#\:\*]*)(.*)([' + FTT.addAnchorTarget[2].slice(0,1).toUpperCase() + FTT.addAnchorTarget[2].slice(0,1).toLowerCase() + ']' + E1(FTT.addAnchorTarget[2].slice(1)).replace(/[ _]/g,'[ _]') + '(.){0,255}' + E1(FTT.checkParams.origTimestamp).replace(/[ _]/g,'[ _]') + ')');
								FTT.matchHeader = new RegExp('^=.+=[ ]*(FTTSAFED'+FTT.semiRandom+'HTMLCOMMENT)?[ ]*$','gm');
//								FTT.debug('insertAnchor: add anchor for:');
//								FTT.debug(FTT.addAnchorReplaceRegExp);
								FTT.addAnchorCheckForExistingAnchor = new RegExp('id="' + E1(FTT.addAnchorTarget[1]) + '[_ ]' + E1(FTT.addAnchorTarget[2]));
								if ( FTT.postCommentParams.text.match(FTT.addAnchorCheckForExistingAnchor) == null ) {
//									FTT.debug('insertAnchor: no existing anchor found for '+FTT.addAnchorTarget[1]+'_'+FTT.addAnchorTarget[2]);
									FTT.textWithAnchor = '';
									FTT.textNoAnchorSplit = FTT.postCommentParams.text.split(/\n/);
									FTT.ancCmtLineFound = 0;
									FTT.ancCmtLine = 0;
									for(FTT.ancInt=0;FTT.ancInt<FTT.textNoAnchorSplit.length;FTT.ancInt++) {
										if ( ! FTT.ancCmtLineFound && FTT.cleanTimestamp(FTT.textNoAnchorSplit[FTT.ancInt]).match(FTT.addAnchorReplaceRegExp) ) {
											FTT.ancCmtLineFound = 1;
//											FTT.debug('insertAnchor: line with matching signature: '+FTT.ancInt);
//											FTT.debug(FTT.textNoAnchorSplit[FTT.ancInt]);
											break;
										} else if ( FTT.cleanTimestamp(FTT.textNoAnchorSplit[FTT.ancInt]).match(FTT.signDateRegExpLocalMonths) || FTT.textNoAnchorSplit[FTT.ancInt].match(FTT.wikiTextUpToSigUnsafedRegExp) ) { //match a signature or section header, indicating a new comment may start on the next line
//											FTT.debug('insertAnchor: found a timestamp or section header on line '+FTT.ancInt);
											FTT.ancCmtLine = FTT.ancInt+1; 
										}
									}
									if ( FTT.ancCmtLineFound ) {
//										FTT.debug('insertAnchor: found a line with a signature to add an anchor to: '+FTT.ancCmtLine);
										for(FTT.ancInt2=0;FTT.ancInt2<FTT.textNoAnchorSplit.length;FTT.ancInt2++) {
											if ( FTT.ancInt2 == FTT.ancCmtLine ) {
//												FTT.debug('insertAnchor: adding anchor to line '+FTT.ancInt2);
												FTT.textWithAnchor = FTT.textWithAnchor + FTT.textNoAnchorSplit[FTT.ancInt2].replace(/^([\:\*\#]*)([^\:\*\#])/,'$1<span id="' + FTT.escapeReplacement(FTT.addAnchorTarget[1]) + '_' + FTT.escapeReplacement(FTT.addAnchorTarget[2]) + '"></span>$2')+'\n';
											} else {
//												FTT.debug('insertAnchor: duplicate line #'+FTT.ancInt2);
												FTT.textWithAnchor = FTT.textWithAnchor + FTT.textNoAnchorSplit[FTT.ancInt2]+'\n';
											}
										}
									}
									FTT.postCommentParams.text = FTT.textWithAnchor;
									FTT.addAnchorOrigSpeechBalloon = document.getElementById('FTTLink-' + FTT.checkParams.id);
									if ( typeof FTT.addAnchorOrigSpeechBalloon == 'object' ) {
//										FTT.debug('insertAnchor: add anchor to original speech balloon so it sort of works while viewing your freshly posted comment with parse-in-place');
										FTT.addAnchorForParseInPlace = document.createElement('span');
										FTT.addAnchorForParseInPlace.id = FTT.escapeReplacement(FTT.addAnchorTarget[1]) + '_' + FTT.escapeReplacement(FTT.addAnchorTarget[2]);
										FTT.addAnchorOrigSpeechBalloon.prepend(FTT.addAnchorForParseInPlace);
									}
//								} else { FTT.debug('insertAnchor: there already is an anchor for '+FTT.addAnchorTarget[1]+'_'+FTT.addAnchorTarget[2]);
								}
							}
						}
					}
				}
			}
		};
		FTT.insertAnchor();
	} else if ( PRM.type == 'FCL' ) {
//		FTT.debug('posting message through FCL (FTT Comment Link)');
		if ( typeof PRM.summary != 'undefined' ) {
			FTT.FCLExtraSummary = PRM.summary;
		} else {
			FTT.FCLExtraSummary = '';
		}
		if ( PRM.allowcreate ) {
			delete FTT.postCommentParams.nocreate;
			delete FTT.postCommentParams.baserevid;
		}
		if ( typeof PRM.sectionTitle != 'undefined' ) {
			FTT.postCommentParams.summary = FTT.postCommentSummarySectionLink + FTT.FCLExtraSummary + FTT.wikiMsgs.summaryCredit;
		} else {
			FTT.postCommentParams.summary = FTT.FCLExtraSummary + FTT.wikiMsgs.summaryCredit;
		}
		FTT.origCmtData = FTT.getInsertionPointSection(PRM, justCurrentPageText);
		if ( PRM.indent ) {
			FTT.commentTextIndent = PRM.indent;
		} else {
			FTT.commentTextIndent = FTT.getMostPopularIndent(justCurrentPageText, FTT.origCmtData.sectiontext);
		}
		FTT.postCommentParams.section = FTT.origCmtData.sectionnum;
		if ( PRM.subtype == 'page' ) {
			delete FTT.postCommentParams.section;
		}
		if ( PRM.indentation && PRM.indentation.match(/^\#\:\*$/) ) {
			FTT.commentTextIndent = PRM.indentation;
		}
		FTT.postCommentParams.appendtext = '\n' + FTT.commentTextIndent + FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment);
		if ( PRM.createonly ) {
			delete FTT.postCommentParams.nocreate;
			FTT.postCommentParams.createonly = true;
			FTT.postCommentParams.appendtext = FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment);
		}
	} else if ( PRM.type == 'newheading' ) {
		if ( FTT.UITextInputTitle.getValue() == '' ) { //appending comment
			if ( FTT.UITextInputSummary.getValue() == '' ) {
				FTT.postCommentParams.summary = FTT.postCommentSummarySectionLink + FTT.wikiMsgs.newSectionCmt.replace(/CREATE/,FTT.B1.creating.replace(FTT.unQuote,'\$1')).replace(/PLUS/,FTT.wikiMsgs.plus+FTT.wikiMsgs.cmt).replace(/\$1/,FTT.wikiMsgs.cmt).replace(/SNIPPET/,FTT.escapeReplacement(FTT.summarySnippetText));
			} else {
				FTT.postCommentParams.summary = FTT.postCommentSummarySectionLink + FTT.summarySnippetText + FTT.wikiMsgs.summaryCredit;
			}
		} else {
			FTT.postCommentParams.summary = FTT.postCommentSummarySectionLink + FTT.B1.newsectionsummary.replace(/\$1/g, FTT.escapeReplacement(FTT.UITextInputTitle.getValue())) + FTT.wikiMsgs.summaryCredit;
		}
		FTT.origSectData = FTT.getInsertionPointSection(PRM, justCurrentPageText);
		FTT.postCommentParams.section = FTT.origSectData.sectionnum;
		FTT.newSectionLevel = '===';
		FTT.headerCode = {'h1':'==','h2':'===','h3':'====','h4':'=====','h5':'======','h6':'======'};
		FTT.tryHeadingEl = FTT.processElementArray[FTT.PRMOpened.int].parentElement;
		while ( FTT.tryHeadingEl ) {
			if ( FTT.tryHeadingEl.nodeName.match(/^H[1-6]$/) ) {
				FTT.newSectionLevel = FTT.headerCode[FTT.tryHeadingEl.nodeName.toLocaleLowerCase()];
				break;
			}
			FTT.tryHeadingEl = FTT.tryHeadingEl.parentElement;
		}
		if ( FTT.UITextInputTitle.getValue() != '' ) {
			FTT.postCommentParams.appendtext = '\n' + FTT.newSectionLevel + FTT.UITextInputTitle.getValue() + FTT.newSectionLevel + '\n' + FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment);
		} else {
			FTT.newTitlelessSubSecIndent = FTT.getMostPopularIndent(justCurrentPageText,FTT.origSectData.sectiontext);
			FTT.postCommentParams.appendtext = '\n' + FTT.newTitlelessSubSecIndent + FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment);
		}
	} else if ( PRM.type == 'newsection' ) {
		FTT.postCommentParams.summary = FTT.B1.newsectionsummary.replace(/\$1/g,FTT.escapeReplacement(FTT.UITextInputTitle.getValue())) + FTT.wikiMsgs.summaryCredit;
		if ( PRM.summary ) {
//			FTT.debug('insert InputBox provided summary');
			FTT.postCommentParams.summary = FTT.B1.newsectionsummary.replace(/\$1/g,FTT.escapeReplacement(FTT.UITextInputTitle.getValue())) + ' / ' + PRM.summary;
		}
		if ( PRM.minor == true ) {
//			FTT.debug('adding InputBox specified minor flag');
			FTT.postCommentParams.minor = true;
		}
		FTT.postCommentParams.section = 'new';
		FTT.postCommentParams.sectiontitle = FTT.UITextInputTitle.getValue();
		FTT.postCommentParams.text = FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment);
		if ( FTT.postCommentParams.sectiontitle == '' ) {
//			FTT.debug('no section title? better hope there\'s one included in the main text, but hey, YOLO right?');
			delete FTT.postCommentParams.section;
			delete FTT.postCommentParams.sectiontitle;
			FTT.postCommentParams.appendtext = '\n'+FTT.postCommentParams.text;
			FTT.postCommentParams.summary = FTT.B1.newsectionsummary.replace(/\/\*[^\*]*\*\/[ ]*/g,'');
			delete FTT.postCommentParams.text; //gets overruled by appendtext, no need to upload more text
		}
		delete FTT.postCommentParams.nocreate;
	} else if ( PRM.type == 'edit' ) {
		FTT.editCmtSumma = FTT.wikiMsgs.editCmtSummary.replace(/NAME/,FTT.B1.editing.replace(FTT.unQuote,'\$1').replace('$1',FTT.wikiMsgs.cmt));
		FTT.editCmtSummaryHere = FTT.editCmtSumma.replace(/USER/,'');
		FTT.delCmt = FTT.B1['delete-confirm'].replace(FTT.unQuote,'\$1').replace('$1',FTT.wikiMsgs.cmt);
		if ( FTT.settings.theStranger && PRM.origReplyTo != FTT.userNameUnderscore) {
			FTT.pingVictimSumma = '([[User:' + PRM.origReplyTo.replace(/_/g,' ') + '|' + PRM.origReplyTo.replace(/_/g,' ') + ']])';
			if ( FTT.processedComment != '' ) {
				FTT.editCmtSummaryHere = FTT.editCmtSumma.replace(/USER/,FTT.escapeReplacement(FTT.pingVictimSumma));
			} else {
				FTT.editCmtSummaryHere = FTT.wikiMsgs.rmCmtSummary.replace(/USER/g,FTT.escapeReplacement(FTT.pingVictimSumma)).replace(/DELETE/,FTT.delCmt);
			}
		} else if ( FTT.processedComment == '' ) {
			FTT.editCmtSummaryHere = FTT.wikiMsgs.rmCmtSummary.replace(/USER/g,'').replace(/DELETE/,FTT.delCmt);
		}
		if ( PRM.subtype == 'locator' ) {
			FTT.editCmtSummaryHere = FTT.editCmtSummaryHere.replace(/LINK/,'#'+FTT.escapeReplacement(PRM.id));
		} else { //no anchor for legacy comments
			FTT.editCmtSummaryHere = FTT.editCmtSummaryHere.replace(/\[\[LINK\|([^\]]*)\]\]/,'$1');
		}
		FTT.postCommentParams.summary = FTT.postCommentSummarySectionLink + FTT.editCmtSummaryHere;
		FTT.origCmtData = FTT.getInsertionPointComment(PRM, justCurrentPageText);
		if ( FTT.origCmtData == 'retry' ) {
//			FTT.debug('no insertion point, possible moved locator, step back to postReply1');
			FTT.postReply1(FTT.PRMOpened,'getInsertionPointComment');
			return;
//		} else { FTT.debug('got insertion point');
		}
		if ( FTT.origCmtData.sectiontext != 'SECTIONLESS' ) {
			FTT.postCommentParams.section = FTT.origCmtData.sectionnum;
			FTT.originalWikiText = FTT.origCmtData.sectiontext;
		} else {
			FTT.originalWikiText = justCurrentPageText;
		}
		FTT.userMentionInSummary = FTT.pingFix(FTT.getValue(),FTT.wikiTextForEditComment);
		if ( FTT.userMentionInSummary != '' ) {
			FTT.postCommentParams.summary = FTT.postCommentParams.summary.replace(FTT.wikiMsgs.summaryCredit, ' [' + FTT.userMentionInSummary.replace(/^, /, '') + ']' + FTT.wikiMsgs.summaryCredit);
		}
		FTT.commentToReplaceRegExp = new RegExp(E1(FTT.wikiTextForEditComment));
		FTT.unalteredCommentNoSig = FTT.originalWikiText.match(FTT.commentToReplaceRegExp)[0].replace(FTT.wikiTextForEditSigForPreview,'');
		if ( FTT.processedComment == '' ) {
//			FTT.debug('blanked input field, erase line with comment entirely');
			FTT.postCommentParams.text = FTT.originalWikiText.replace(new RegExp('[\n]?.*'+E1(FTT.wikiTextForEditComment)+'.*'), '');
		} else {
			FTT.alteredComment = FTT.originalWikiText.match(FTT.commentToReplaceRegExp)[0].replace(FTT.unalteredCommentNoSig,FTT.processedComment);
			if ( FTT.reinsertNL ) { //outdented comment, reinsert newline that got trimmed
				FTT.alteredComment = '<br/>'+FTT.alteredComment;
			}
			FTT.postCommentParams.text = FTT.originalWikiText.replace(FTT.commentToReplaceRegExp, FTT.alteredComment);
		}
	} else if ( PRM.type == 'editFullPage' ) {
		FTT.postCommentParams.summary = FTT.wikiMsgs.summaryCredit;
		if ( M1('wgCurRevisionId') == 0 && FTT.processedComment.length > 0 ) {
			FTT.FPSumma = FTT.processedComment.split(/\n/)[0];
			if ( FTT.FPSumma.length > 85 ) {
				FTT.FPSumma = FTT.FPSumma.slice(0,80) + '…';
			}
			FTT.postCommentParams.summary = FTT.B1['autosumm-new'].replace(/\$1/,FTT.escapeReplacement(FTT.FPSumma)) + FTT.wikiMsgs.summaryCredit;
		} else if ( M1('wgCurRevisionId') == 0 ) {
			FTT.postCommentParams.summary = FTT.B1['autosumm-newblank'] + FTT.wikiMsgs.summaryCredit;
		} else if ( M1('wgCurRevisionId') > 0 && FTT.processedComment.trim().length == 0 ) {
			FTT.postCommentParams.summary = FTT.B1['autosumm-blank'] + FTT.wikiMsgs.summaryCredit;
		} else if ( M1('wgCurRevisionId') > 0 && FTT.processedComment.length < 80 ) {
			FTT.postCommentParams.summary = FTT.B1['autosumm-replace'].replace('$1',FTT.processedComment) + FTT.wikiMsgs.summaryCredit;
		}
		FTT.postCommentParams.text = FTT.processedComment;
		delete FTT.postCommentParams.nocreate;
		if ( FTT.oldRevLoaded ) {
			FTT.postCommentParams.baserevid = M1('wgCurRevisionId'); //edit fails otherwise when restoring an old revision
		}
	} else if ( PRM.type == 'heading' ) {
		FTT.summReplaced = '';
		if ( FTT.processedComment.length < 80 ) {
			FTT.summReplaced = ' '+FTT.B1['autosumm-replace'].replace('$1',FTT.processedComment);
		}
		FTT.postCommentParams.summary = FTT.postCommentSummarySectionLink + FTT.summReplaced + FTT.wikiMsgs.summaryCredit;
		FTT.postCommentParams.text = FTT.processedComment;
		FTT.postCommentParams.section = FTT.getInsertionPointSection(PRM, justCurrentPageText).sectionnum;
	}
	if ( FTT.replyToWikiLove && FTT.postCommentParams.section ) { //if there is no section number or it's zero follow the usual strategy
		FTT.postCommentParams.appendtext = '\n' + FTT.commentTextIndent + FTT.processedComment + FTT.repairTagImbalance(FTT.processedComment);
	}
	if ( FTT.UITextInputSummary.isElementAttached() && FTT.UITextInputSummary.getValue() != '' ) {
		FTT.postCommentParams.summary = FTT.postCommentParams.summary.replace(FTT.wikiMsgs.summaryCredit, ' ' + FTT.UITextInputSummary.getValue() + FTT.wikiMsgs.summaryCredit).trim();
	}
	if ( FTT.UIMinorCheck && FTT.UIMinorCheck.isSelected() ) {
		FTT.postCommentParams.minor = true;
	}
	if ( typeof FTT.sectionNumFromLink == 'number' ) { //detection of section number from wikitext failed so we're using the fallback of the section number from the edit section link
		FTT.postCommentParams.section = FTT.sectionNumFromLink;
	}
	if ( FTT.RETFsummary != '' ) {
		FTT.postCommentParams.summary = FTT.postCommentParams.summary.replace(FTT.wikiMsgs.summaryCredit, ' [' + FTT.RETFsummary + ']' + FTT.wikiMsgs.summaryCredit);
	}
	delete FTT.wipeSectionParams;
	delete FTT.wipeSectionAnchor;
	if ( ( ['comment','editFullPage'].indexOf(PRM.type) != -1 || PRM.type == 'heading' ) && FTT.oTT && FTT.oTT.moveContentToPage.getValue() != '' ) {
//		FTT.debug('a pagename was entered to post or move the content to');
		delete FTT.postCommentParams.baserevid; //will appendtext the section or content. no point in checking for edit conflicts
		if ( typeof FTT.postCommentParams.section == 'number' && ! FTT.dryRunOnce ) {
//			FTT.debug('store params to wipe existing section from ' + PRM.pageTitle);
			FTT.wipeSectionParams = $.extend( true, {}, FTT.postCommentParams );
			FTT.wipeSectionParams.text = '';
			delete FTT.wipeSectionParams.appendtext; //wouldn't typically be set (never?), but we don't want appendtext to override our empty text in this case
			FTT.wipeSectionAnchor = '';
			if ( PRM.sectionTitle != '' ) {
				FTT.wipeSectionAnchor = '#' + PRM.sectionTitle;
			}
			FTT.wipeSectionCustomSummary = '';
			if ( FTT.UITextInputSummary.isElementAttached() && FTT.UITextInputSummary.getValue() != '' ) {
				FTT.wipeSectionCustomSummary = ' ('+FTT.UITextInputSummary.getValue()+')';
			}
			FTT.wipeSectionParams.summary = FTT.B1.prot_1movedto2.replace('$1',PRM.pageTitle.replace(/_/g,' ')+FTT.wipeSectionAnchor+'|'+FTT.wipeSectionAnchor).replace('$2',FTT.oTT.moveContentToPage.getValue() + FTT.wipeSectionAnchor) + FTT.wipeSectionCustomSummary + FTT.wikiMsgs.summaryCredit;
		}
		if ( ! FTT.postCommentParams.appendtext ) {
			FTT.postCommentParams.appendtext = '\n\n' + FTT.postCommentParams.text;
			delete FTT.postCommentParams.text;
		}
		delete FTT.postCommentParams.section;
//		FTT.debug('rewrite pagename in locators');
		FTT.postCommentParams.appendtext = FTT.postCommentParams.appendtext.replace(/(<span id="[^:]*:([0-9]{10,11}|\{\{subst:#time:xNU\}\})[0-9]{3}:)([^"\n]*)(" class="FTTCmt")/g,'$1' + FTT.escapeHTML(FTT.oTT.moveContentToPage.getValue().replace(/ /g,'_').replace(/:/g,'FTTCLN')) + '$4');
		FTT.postCommentParams.title = FTT.oTT.moveContentToPage.getValue();
		if ( FTT.wipeSectionParams ) {
			FTT.postCommentParams.summary = FTT.wipeSectionParams.summary;
		}
		if ( FTT.oTT.allowPageCreation.isSelected() ) {
			delete FTT.postCommentParams.nocreate;
		} else {
			FTT.postCommentParams.nocreate = true;
		}
	}
	FTT.applyModules('beforeEdit');
//	FTT.debug('ready to post');
	FTT.mustReload = false;
	if ( justCurrentPageText != FTT.wikiTextWithComment || FTT.postCommentParams.appendtext ) {
		if ( ! FTT.postCommentParams.appendtext ) {
//			FTT.debug('generated wikitext is different from the current wikitext, so it can be posted (without being a null edit). wikitext:\n' + FTT.postCommentParams.text);
		}
//		FTT.debug(FTT.postCommentParams);
		if ( FTT.dryRunOnce == true ) {
			FTT.dryRunOnce = false;
//			FTT.debug('dry run enabled, skipped API call');
			FTT.dryRunInfoDiv = document.createElement('div');
			FTT.dryRunInfoDiv.style = 'padding:1em;background:#efe;text-align:initial;font-style:normal;font-size:initial;font-family:initial';
			FTT.dryRunInfoDiv.id = 'DryRunInfo' + FTT.escapeHTML(FTT.PRMOpened.id);
			FTT.dryRunInfoDiv.classList.add('FTTDryRun');
			FTT.openDryRun = 1;
			FTT.dryRunInfo = '';
			for(FTT.paramsInPreviewInt=0;FTT.paramsInPreviewInt<Object.keys(FTT.postCommentParams).length;FTT.paramsInPreviewInt++) {
				FTT.dryRunInfo = FTT.dryRunInfo + '<b>' + FTT.escapeHTML(Object.keys(FTT.postCommentParams)[FTT.paramsInPreviewInt]) + ': </b>' + FTT.escapeHTML(Object.values(FTT.postCommentParams)[FTT.paramsInPreviewInt]).replace(/\n/g,'<br/>') + '<br/>';
			}
			FTT.dryRunInfoDiv.innerHTML = FTT.dryRunInfo + '<br/><a onclick="FTT.closeDryRun()">whatev</a>';
			if ( FTT.PRMOpened.type == 'heading' && FTT.PRMOpened.subtype == 'edit' ) {
				document.getElementById('FTTForm-' + FTT.PRMOpened.id).append(FTT.dryRunInfoDiv);
			} else {
				document.getElementById('FTTForm-' + FTT.PRMOpened.id).parentElement.append(FTT.dryRunInfoDiv);
			}
			document.getElementById(FTT.dryRunInfoDiv.id).scrollIntoView(FTT.smoothScroll);
			FTT.disableForm(false); //re-enable form
			FTT.removeOverlay();
		} else {
			FTT.doAPICall(FTT.postCommentParams);
		}
		if ( ( !FTT.isMobile || PRM.type == 'comment' ) && (PRM.subtype != 'InputBox' || PRM.pageTitle.replace(/_/g,' ') == FTT.PN) && M1('wgCurRevisionId') && ( ( FTT.settings.afterPost == 'parse' && ['comment','newsection','newheading'].indexOf(PRM.type) != -1 && ! FTT.PRMOpened.redirect ) || ( FTT.settings.afterPost == 'parsecmtonly' && ['comment'].indexOf(PRM.type) != -1 ) ) ) { //.redirect would indicate a new section posted from section=new which requires a redirect
			delete FTT.postedCommentParsed;
			FTT.doPreview('previewposted', PRM); //afterPost = 'parse' does support edits to comments that were posted without reloading/leaving the page. It worked well like 80-90% of the time but it's disabled as the indentation kept getting screwed up occasionally. Should be fixable but I'm done with this
		} else {
			FTT.mustReload = true;
		}
	} else {
//		FTT.debug('couldn\'t insert comment, wikitext same as current text, possibly insertion point failure');
		FTT.disableForm(false); //re-enable form
	}
} catch (e) {
	FTT.JSError(e);
}
}; //end postReply2
FTT.closeDryRun = function() {
	FTT.openDryRun = 0;
	$('.FTTPreviewAfterPost.FTTPurpleBG').removeClass('FTTPurpleBG');
	$('.FTTDryRun').remove();
	$('#FTTTextAndPreview')[0].scrollIntoView(FTT.smoothScroll);
};
if ( FTT.settings.enableOnDiffOldId ) {
	FTT.viewingPage = (M1('wgAction') == 'view' && window.location.href.match('veaction=') == null);
} else {
	FTT.viewingPage = (M1('wgAction') == 'view' && M1('wgDiffNewId') == null && window.location.href.match('veaction=') == null && window.location.href.match('oldid=') == null);
}
//todo: is .minerva-talk-add-button deprecated?
FTT.isDiscussionPage = ( (mw.config.get('wgCanonicalNamespace').match(/_talk$/) && ! mw.config.get('wgPageName').match(/\//) ) || $('#ca-addsection,.minerva-talk-add-button,span[data-event-name="talkpage.add-topic"]')[0] || M1('wgExtraSignatureNamespaces').includes(M1('wgNamespaceNumber')) ); //this doesn't include .FTTCmt or .LegacyCmt because comments from a talk page could be transcluded on a non-talk page, like in the Signpost, also .LegacyCmt doesn't exist before searchNodeContentsLoop has completed. See FTT.isDiscussionPageWithCmts
FTT.isOtherPage = ( ! FTT.isDiscussionPage && FTT.viewingPage );
FTT.viewingArticle = ( M1('wgNamespaceNumber') == 0 && ! FTT.isDiscussionPage && FTT.viewingPage );
if ( ( M2('FTTScrToTime') || FTT.settings.HLCmtClick ) && FTT.isDiscussionPage && FTT.settings.HLCmtClick ) {
	$('#mw-content-text').on('click',function(e){FTT.HLCmt(e.target);});
}
if ( ( FTT.settings.editCmtDblClick || FTT.settings.replyDblClick ) && FTT.isDiscussionPage ) {
	$('#mw-content-text').on('dblclick',function(e){FTT.HLCmt(e.target,undefined,'edit');});
}
if ( FTT.settings.RLmasq ) {
	FTT.replyLinkInnerHTML = '<span class="FTTBracket">&nbsp;(</span><span class="FTTReplyLink FTTRLMasq" style="color: #0645ad">'+FTT.msgs.reply+'</span><span class="FTTBracket">)</span>';
	FTT.svgFTTIconClass = 'FTTCmtLink';
	FTT.editLinkInnerHTML = '<span class="FTTBracket">&nbsp;(</span><span class="FTTReplyLink FTTRLMasq" style="color: #0645ad">'+FTT.B1.editlink+'</span><span class="FTTBracket">)</span>';
	FTT.svgFTTEditIconClass = 'FTTEditLink';
	FTT.svgFTTEditIconRedClass = 'FTTEditLink FTTtheStrangerRLMasq';
	FTT.thankLinkInnerHTML = '<span class="FTTBracket">&nbsp;(</span><span class="FTTReplyLink FTTRLMasq" style="color: #0645ad">'+FTT.B1['thanks-thank']+'</span><span class="FTTBracket">)</span>';
	FTT.svgFTTThankIconClass = '';
	FTT.thankedLinkInnerHTML = '<span class="FTTBracket">&nbsp;(</span><a class="mw-selflink" style="cursor:initial;">'+FTT.B1['thanks-thanked']+'</a><span class="FTTBracket">)</span>';
	FTT.svgFTTThankedIconClass = 'FTTThanked';
	FTT.permaCmtLinkInnerHTML = '<span class="FTTBracket">&nbsp;(</span><span class="FTTReplyLink FTTRLMasq" style="color: #0645ad">'+FTT.B1.permalink+'</span><span class="FTTBracket">)</span>';
	FTT.svgFTTPermaCmtIconClass = '';
} else {
	FTT.replyLinkInnerHTML = '<span class="FTTScreenReaderLabel" data-content="'+FTT.msgs.reply+'"></span>';
	FTT.svgFTTIconClass = 'FTTCmtLink FTTReplyLink FTTSVGIcon FTTSVG';
	FTT.editLinkInnerHTML = '<span class="FTTScreenReaderLabel" data-content="'+FTT.B1.editlink+'"></span>';
	FTT.svgFTTEditIconClass = 'FTTEditLink FTTReplyLink FTTSVGEditIcon FTTSVG';
	FTT.svgFTTEditIconRedClass = 'FTTEditLink FTTReplyLink FTTSVGEditRedIcon FTTSVG';
	FTT.svgFTTEditIconRedGrayClass = 'FTTEditLink FTTReplyLink FTTSVGEditIcon FTTtheStranger FTTSVG';
	FTT.thankLinkInnerHTML = '<span class="FTTScreenReaderLabel" data-content="'+FTT.B1.thanks+'"></span>';
	FTT.svgFTTThankIconClass = 'FTTReplyLink FTTSVG FTTSVGHeartIcon';
	FTT.thankedLinkInnerHTML = '<span class="FTTScreenReaderLabel" data-content="'+FTT.B1['thanks-thanked']+'"></span>';
	FTT.svgFTTThankedIconClass = 'FTTSVG FTTSVGHeartRedIcon FTTThanked';
	FTT.permaCmtLinkInnerHTML = '<span class="FTTScreenReaderLabel" data-content="'+FTT.B1.permalink+'"></span>';
	FTT.svgFTTPermaCmtIconClass = 'FTTReplyLink FTTSVG FTTSVGLinkIcon';
}
if ( FTT.settings.RLmasqSect ) {
	FTT.permaLinkInnerHTML = '<span class="FTTBracket">&nbsp;(</span><span class="FTTReplyLink FTTRLMasq" style="color: #0645ad">'+FTT.B1.permalink+'</span><span class="FTTBracket">)</span>';
	FTT.svgFTTPermaIconClass = '';
	FTT.newSubSectionInnerHTML = '<span class="FTTBracket">&nbsp;(</span><span class="FTTReplyLink FTTRLMasq" style="color: #0645ad">'+FTT.msgs.newSubSectionTitle+'</span><span class="FTTBracket">)</span>';
	FTT.svgFTTnewSubSectionIconClass = '';
	FTT.editFullSectionInnerHTML = '<span class="FTTBracket">&nbsp;(</span><span class="FTTReplyLink FTTRLMasq" style="color: #0645ad">'+FTT.msgs.editFullSectionTitle+'</span><span class="FTTBracket">)</span>';
	FTT.svgFTTeditFullSectionIconClass = '';
} else {
	FTT.permaLinkInnerHTML = '<span class="FTTScreenReaderLabel" data-content="'+FTT.B1.permalink+'"></span>';
	FTT.svgFTTPermaIconClass = 'FTTReplyLink FTTSVG FTTSVGLinkIcon';
	FTT.newSubSectionInnerHTML = '<span class="FTTScreenReaderLabel" data-content="'+FTT.msgs.newSubSectionTitle+'"></span>';
	FTT.svgFTTnewSubSectionIconClass = 'FTTReplyLink FTTSVGNewSectionIcon FTTSVG';
	FTT.editFullSectionInnerHTML = '<span class="FTTScreenReaderLabel" data-content="'+FTT.msgs.editFullSectionTitle+'"></span>';
	FTT.svgFTTeditFullSectionIconClass = 'FTTReplyLink FTTSVGEditIcon FTTSVG';
}
FTT.FTTThanksObj = FTT.testValidJSON(FTT.getItemLS('FTTThanks'));
if ( FTT.FTTThanksObj && FTT.settings.thankLink ) {
	FTT.thankedCommentsArray = Object.values(FTT.FTTThanksObj);
} else if ( FTT.settings.thankLink ) {
	FTT.thankedCommentsArray = [];
}
FTT.testExistingSub = function(PRM) {
	try {
		FTT.testSubExist = FTT.checkSubs[PRM.pageTitle.replace(/_/g,' ')].subs[PRM.sectionTitle].foo;
	} catch (e) {return false;}
	return true;
};
FTT.replyLinkDebugInfo = '';
FTT.checkSubs = FTT.stalkGetSubs();
FTT.insertAfter = function(element,newElement) {
	if ( element.nextElementSibling ) {
//		FTT.debug('found nextElementSibling, insert before that');
		element.parentElement.insertBefore(newElement,element.nextElementSibling);
	} else {
//		FTT.debug('no nextElementSibling, append to parentElement');
		element.parentElement.append(newElement);
	}
};
FTT.addReplyLinkTo = function(PRM,textNodeSig,legacySigMatch) {
	if ( ! PRM.origReplyTo ) {
		FTT.PRM[PRM.int].origReplyTo = '';
	}
	if ( ! FTT.viewingPage ) {
//		FTT.debug('don\'t add links to diff pages etc (you\'d still have new section/edit full page)');
		return;
	}
	if ( ! FTT.finishedAddingLinks && typeof PRM.isFirst == 'undefined' && ( PRM.type == 'comment' || ( PRM.type == 'edit' && PRM.type == 'legacy' ) ) ) {
		FTT.replyUserDate = PRM.origReplyTo + PRM.origTimestamp;
		for (FTT.seqint = 0; FTT.seqint < 100; FTT.seqint++) {
			if ( FTT.addedReplyLinks.indexOf(FTT.replyUserDate + '-' + FTT.seqint) == -1 ) {
				FTT.addedReplyLinks.push(FTT.replyUserDate + '-' + FTT.seqint);
				FTT.PRM[PRM.int].seq = FTT.seqint;
				if ( FTT.seqint > 0 ) {
					FTT.PRM[PRM.int].id = PRM.id + '_' + FTT.seqint;
				}
				break;
			}
		}
	}
//	if ( FTT.settings.debug && PRM.int ) { FTT.replyLinkDebugInfo = ' (int #' + PRM.int + ', PRM.int #' + PRM.int + ', subtype: ' + PRM.subtype + ')';}//FTT.debug
	if ( PRM.isFirst != true ) {
		FTT.replyLinkHTML = document.createElement('span');
	} else {
		FTT.replyLinkHTML = document.createElement('div');
	}
	FTT.replyLinkHTML.id = 'FTTLink-' + FTT.escapeHTML(PRM.id);
	if ( PRM.type == 'comment' ) {
		FTT.replyLinkHTML.className = 'FTTLinks FTTCommentLinks';
		FTT.replyToTitle = FTT.msgs.replyToTitle.replace(/USER/g, FTT.escapeHTML(PRM.origReplyTo).replace(/_/g,' ')) + FTT.replyLinkDebugInfo;
		if ( ! FTT.settings.RLmasq ) {
			FTT.replyLinkInnerHTML = '<span class="FTTScreenReaderLabel" data-content="'+FTT.replyToTitle+'"></span>';
		}
		FTT.replyLinkText = '<span title="' + FTT.replyToTitle + '" class="' + FTT.svgFTTIconClass + '">' + FTT.replyLinkInnerHTML + '</span>';
	} else if ( PRM.type == 'newheading' ) {
		FTT.replyLinkHTML.className = 'FTTLinks FTTHeadingLinks';
		FTT.replyLinkText = '';
		if ( FTT.settings.secLinks ) {
			FTT.replyLinkText = '<span title="' + FTT.msgs.newSubSectionTitle + FTT.replyLinkDebugInfo + '" class="'+FTT.svgFTTnewSubSectionIconClass+'">'+FTT.newSubSectionInnerHTML+'</span>';
		}
	} else if ( PRM.type == 'edit' ) {
		FTT.replyLinkHTML.className = 'FTTLinks FTTEditLink';
		if ( PRM.origReplyToUnderscore != FTT.userNameUnderscore ) {
			if ( ! FTT.settings.grayscale || FTT.settings.RLmasq ) {
				FTT.editIconClass = FTT.svgFTTEditIconRedClass;
			} else {
				FTT.editIconClass = FTT.svgFTTEditIconRedGrayClass;
			}
		} else {
			FTT.editIconClass = FTT.svgFTTEditIconClass;
		}
		FTT.replyLinkText = '<span title="' + FTT.msgs.editTitle + FTT.replyLinkDebugInfo + '" class="'+FTT.editIconClass+'">'+FTT.editLinkInnerHTML+'</span>';
		FTT.replyLinkHTML.id = 'FTTEditLink-' + FTT.escapeHTML(PRM.id);
	}
	if ( FTT.isDiscussionPage || PRM.type != 'newheading' ) {
		if ( FTT.finishedAddingLinks ) {
			FTT.onClickParams = '\'' + D1(JSON.stringify(PRM)).replace(/\'/g, '%27') + '\'';
		} else if ( PRM.type == 'edit' ) {
			FTT.onClickParams = 'FTT.' + FTT.keyPRMEdit + '[' + JSON.stringify(PRM.int) + ']';
		} else {
			FTT.onClickParams = 'FTT.' + FTT.keyPRM + '[' + JSON.stringify(PRM.int) + ']';
		}
		FTT.replyLinkHTML.innerHTML = '<a tabindex="0" class="FTTCmtA" onclick="event.preventDefault();event.stopPropagation();FTT.' + FTT.keyOpenReplyForm + '(' + FTT.onClickParams + ')">' + FTT.replyLinkText + '</a>';
	}
	if ( ! PRM.isFirst && FTT.settings.thankLink && PRM.type == 'comment' && PRM.origReplyTo != FTT.userNameUnderscore ) {
//		FTT.debug('add thank link');
		if ( FTT.thankedCommentsArray.indexOf(PRM.id) == - 1 ) {
			FTT.thankLinkElement = document.createElement('a');
			FTT.thankLinkElement.tabIndex = '0';
			FTT.thankLinkElement.classList = 'FTTThankLink';
			FTT.thankLinkElement.id = 'thankLink-' + PRM.int;
			FTT.thankLinkElement.title = FTT.B1.thanks;
			FTT.thankLinkElement.innerHTML = '<span class="'+FTT.svgFTTThankIconClass+'">'+FTT.thankLinkInnerHTML+'</span>';
			FTT.thankLinkElement.onclick = function(){FTT.giveThanks(PRM);};
		} else {
			FTT.thankLinkElement = document.createElement('span');
			FTT.thankLinkElement.innerHTML = '<span class="'+FTT.svgFTTThankedIconClass+'" title="'+FTT.B1['thanks-button-action-completed'].replace('$1',PRM.origReplyTo)+'">'+FTT.thankedLinkInnerHTML+'</span>';
		}
		FTT.replyLinkHTML.prepend(FTT.thankLinkElement);
	}
	if ( ! PRM.isFirst && ( ( FTT.settings.dateLinksIcon && PRM.type == 'comment' ) || ( FTT.settings.dateLinksIconSection && PRM.type == 'newheading' ) ) ) {
//		FTT.debug('add permalink generator link');
		FTT.permaLinkGenLink = document.createElement('a');
		FTT.permaLinkGenLink.title = FTT.B1.permalink;
		if ( PRM.type == 'newheading' ) {
			FTT.permaLinkGenLink.href = '#' + D1(FTT.getPageTitleAndSectionName(PRM.int).sectionTitle.replace(/ /g,'_'));
		} else if ( PRM.type == 'comment' && PRM.subtype == 'locator' ) {
			FTT.permaLinkGenLink.href = '#' + D1(PRM.id);
		}
		FTT.permaLinkGenLink.onclick = function(e){e.preventDefault();FTT.genPermaLink(PRM,'link');};
		FTT.permaLinkGenLink.id = 'genLink-' + PRM.int;
		FTT.permaLinkGenLink.className = 'FTTGenLink';
		FTT.permaLinkGenLink.tabIndex = '0';
		if ( PRM.type == 'comment' ) {
			FTT.permaLinkGenLink.innerHTML = '<span class="'+FTT.svgFTTPermaCmtIconClass+'">'+FTT.permaCmtLinkInnerHTML+'</span>';
		} else {
			FTT.permaLinkGenLink.innerHTML = '<span class="'+FTT.svgFTTPermaIconClass+'">'+FTT.permaLinkInnerHTML+'</span>';
		}
		FTT.replyLinkHTML.prepend(FTT.permaLinkGenLink);
	}
	if ( PRM.type == 'newheading' && FTT.settings.editFullSection ) {
		FTT.editFullSectionLinkText = '<span title="' + FTT.msgs.editFullSectionTitle + FTT.replyLinkDebugInfo + '" class="'+FTT.svgFTTeditFullSectionIconClass+'">'+FTT.editFullSectionInnerHTML+'</span>';
		FTT.PRMHeadingEdit[PRM.int] = $.extend( true, {}, PRM );
		FTT.PRMHeadingEdit[PRM.int].type = 'heading';
		FTT.PRMHeadingEdit[PRM.int].subtype = 'edit';
		FTT.headerEditLinks[PRM.int] = document.createElement('a');
		if ( FTT.settings.editFullSHref ) {
			for ( FTT.eFSHInt=0;FTT.eFSHInt<10;FTT.eFSHInt++) {
				FTT.eFSHEl = FTT.processElementArray[PRM.pageTitleInt].querySelectorAll('a')[FTT.eFSHInt].href;
				if ( FTT.eFSHEl.match(/action=edit/) ) {
					FTT.headerEditLinks[PRM.int].href = FTT.eFSHEl;
					break;
				}
			}
		}
		FTT.headerEditLinks[PRM.int].tabIndex = '0';
		FTT.headerEditLinks[PRM.int].classList.add('FTTSecEdit');
		FTT.headerEditLinks[PRM.int].onclick = function(){FTT.openReplyForm(FTT.PRMHeadingEdit[PRM.int]);};
		FTT.headerEditLinks[PRM.int].innerHTML = FTT.editFullSectionLinkText;
		FTT.replyLinkHTML.append(FTT.headerEditLinks[PRM.int]);
	}
	if ( FTT.settings.stalkAddSubLinks && PRM.type == 'newheading' && FTT.viewingPage && FTT.isDiscussionPage && !FTT.ownTalk ) {
		PRM = FTT.addPageAndSectionTitleToRPL(PRM);
		FTT.stalkerSubscribeLink = document.createElement('span');
		FTT.stalkerSubscribeLink.innerHTML = '<a tabindex="0" title="'+FTT.msgs.subscribe+'" class="FTTSubscribe FTTSVG FTTReplyLink FTTSVGBellIcon" onclick="FTT.' + FTT.keyStalkSubscribe + '(FTT.' + FTT.keyPRM + '[' + JSON.stringify(PRM.int) + '],\'bellicon\',null,event);"><span class="FTTScreenReaderLabel" data-content="'+FTT.msgs.subscribe+'"></span></a>';
		if ( FTT.checkSubs[PRM.pageTitle.replace(/_/g,' ')] && FTT.checkSubs[PRM.pageTitle.replace(/_/g,' ')].subs && FTT.checkSubs[PRM.pageTitle.replace(/_/g,' ')].subs[PRM.sectionTitle.replace(/_/g,' ')] ) {
			FTT.stalkerSubscribeLink.innerHTML = FTT.stalkerSubscribeLink.innerHTML.replace('FTTSVGBellIcon','FTTSVGBellStruckIcon').replace('FTT.' + FTT.keyStalkSubscribe,'FTT.' + FTT.keyStalkUnsubscribe).replace(new RegExp('([>"])'+E1(FTT.msgs.subscribe)+'([<"])','g'),'$1'+FTT.msgs.unsubscribe+'$2');
		}
		FTT.replyLinkHTML.append(FTT.stalkerSubscribeLink);
	}
	if ( PRM.isFirst == true ) {
//		FTT.debug('addReplyLinkTo: add extra speech bubble for section starter, '+PRM.int+' triggerInt:'+PRM.triggerInt);
		delete FTT.replyLinkHTML.id;
		FTT.replyLinkHTML.classList.add('FTTFirstReply');
		FTT.replyLinkHTML.innerHTML = FTT.replyLinkHTML.innerHTML.replace('FTTSVGIcon','FTTSVGFirstReplyIcon FTTFirstReply');
		if ( FTT.firstReplyInSectionLastElement && ! FTT.settings.reverseSectionOrder ) { // in case of reverse section order, fuck this
			FTT.replyLinkHTML.classList.add('FTFirstReplyLast');
			if ( $('#FTTnSecBottom')[0] ) {
//				FTT.debug('addReplyLinkTo: last element, insert extra speech bubble for section starter before new section link at the bottom');
				$('#FTTnSecBottom')[0].parentElement.parentElement.insertBefore(FTT.replyLinkHTML,$('#FTTnSecBottom')[0].parentElement);
			} else {
//				FTT.debug('addReplyLinkTo: last element, append extra speech bubble for section starter to mw-content-text');
				$('#mw-content-text').append(FTT.replyLinkHTML);
			}
		} else {
//			FTT.debug('addReplyLinkTo: add extra speech bubble for section starter to section');
//			FTT.debug(FTT.processElementArray[PRM.triggerInt].parentElement.parentElement);
			FTT.processElementArray[PRM.triggerInt].parentElement.parentElement.insertBefore(FTT.replyLinkHTML,FTT.processElementArray[PRM.triggerInt].parentElement);
		}
	} else if ( PRM.type == 'edit' ) {
//		FTT.debug('insert link to edit comment (' + PRM.int + ')' + ' FTTLink-' + FTT.escapeHTML(PRM.id));
		document.getElementById('FTTLink-' + FTT.escapeHTML(PRM.id)).append(FTT.replyLinkHTML);
	} else if ( PRM.freshcomment == true ) {
//		FTT.debug('insert links after posting a new comment using parse-in-place, appending to #' + PRM.id);
		document.getElementById(PRM.id).append(FTT.replyLinkHTML);
	} else if ( PRM.subtype == 'locator' ) {
//			FTT.debug('insert link to add comment (using locator), appending to processElementArray #' + PRM.int);
			FTT.processElementArray[PRM.int].append(FTT.replyLinkHTML);
			if ( FTT.settings.autoCollapse || FTT.settings.markNewCmts ) {
				if ( PRM.origReplyTo != FTT.userNameUnderscore && ( FTT.settings.autoCollapse || FTT.settings.markNewCmts || FTT.settings.markNewCmtsSubbed ) && FTT.locatorTimestamp > FTT.LVTimestamp ) {
					PRM = FTT.addPageAndSectionTitleToRPL(PRM);//testExistingSub needs a section title
					if ( FTT.settings.markNewCmtsSubbed && FTT.testExistingSub(PRM) ) {
						FTT.replyLinkHTML.parentElement.classList.add('FTTNewCmt','FTTNewCmtText','FTTNewCmtSubscribed','FTTEaseIn');
					} else if ( FTT.settings.markNewCmts || FTT.settings.autoCollapse ) {
						FTT.replyLinkHTML.parentElement.classList.add('FTTNewCmt','FTTNewCmtText','FTTEaseIn');
					}
				}
			}
	} else if ( PRM.subtype == 'legacy' ) {
//		FTT.debug('insert link to add comment (legacy), appending to processElementArray #' + PRM.int);
		FTT.replyLinkHTMLButtons = FTT.replyLinkHTML;
		FTT.replyLinkHTML = document.createElement('span');
		FTT.replyLinkHTML.append(FTT.replyLinkHTMLButtons);
		delete FTT.newSigTime;
		FTT.legacyNodeDate = PRM.origTimestamp;
		if ( FTT.settings.dateLinksLocalTime || FTT.settings.autoCollapse || FTT.settings.markNewCmts || FTT.settings.discussionActivity ) {
			FTT.machineReadableDate = FTT.sigDateToMachineReadable(FTT.legacyNodeDate);
			if ( FTT.machineReadableDate ) {
				if ( FTT.settings.discussionActivity && FTT.machineReadableDate > FTT.sectionLatestCommentDate ) {
					FTT.sectionLatestCommentDate = FTT.machineReadableDate;
					FTT.sectionLatestAnchor = 'FTTLink-'+FTT.PRM[PRM.int].id;
					FTT.sectionLatestUser = PRM.origReplyTo;
				}
				if ( PRM.origReplyTo != FTT.userNameUnderscore && ( FTT.settings.autoCollapse || FTT.settings.markNewCmts || FTT.settings.markNewCmtsSubbed ) && FTT.machineReadableDate > FTT.LVTimestamp ) {
					PRM = FTT.addPageAndSectionTitleToRPL(PRM);
					if ( FTT.settings.markNewCmtsSubbed && FTT.testExistingSub(PRM) ) {
						FTT.replyLinkHTML.classList.add('FTTNewCmt','FTTNewCmtText','FTTNewCmtSubscribed','FTTEaseIn');
					} else if ( FTT.settings.markNewCmts || FTT.settings.autoCollapse ) {
						FTT.replyLinkHTML.classList.add('FTTNewCmt','FTTNewCmtText','FTTEaseIn');
					}
				}
				if ( FTT.settings.dateLinksLocalTime ) {
//					FTT.debug('replace sigdate with localized date');
					FTT.newSigTime = FTT.renderLocalDate(FTT.machineReadableDate);
					FTT.replyLinkHTML.prepend(FTT.cleanTimestamp(textNodeSig.data).replace(PRM.origTimestamp, FTT.newSigTime));
					FTT.replyLinkHTML.title = PRM.origTimestamp;
				}
			}
		}
		if ( typeof FTT.newSigTime == 'undefined' ) {
			FTT.replyLinkHTML.prepend(textNodeSig.data);
		}
		FTT.replyLinkHTML.classList.add('LegacyCmt');
		FTT.textNodeSigData = textNodeSig.data;
		FTT.processElementArray[PRM.int].replaceChild(FTT.replyLinkHTML,textNodeSig);
	} else if ( PRM.type == 'newheading' || PRM.type == 'heading' ) {
//		FTT.debug('insert link to add subsection (' + PRM.int + ')');
		if ( M1('skin') == 'minerva' ) {
			FTT.processElementArray[PRM.int].prepend(FTT.replyLinkHTML);
		} else {
			FTT.processElementArray[PRM.int].append(FTT.replyLinkHTML);
		}
	}
	PRM.origReplyToUnderscore = PRM.origReplyTo.replace(/ /g,'_');
	if ( ! PRM.isFirst && PRM.type == 'comment' && FTT.userNameUnderscore && ( PRM.origReplyToUnderscore == FTT.userNameUnderscore || FTT.settings.theStranger ) && FTT.settings.editLinks ) { //wait, I know this fucker, or I want to mess with other people's stuff. FTT.userNameUnderscore is required because anons can't ping their victim
//		FTT.debug('add edit link');
		FTT.PRMEdit[PRM.int] = $.extend( true, {}, PRM );
		FTT.PRMEdit[PRM.int].type = 'edit';
		FTT.addReplyLinkTo(FTT.PRMEdit[PRM.int]);
	}
};
FTT.VEMurderSpree = function(int) {
	if ( $('html.ve-deactivating,html.ve-active')[0] ) {
//		FTT.debug('VE went on a murder spree again? phab:T318772');
		int=0;
		var VERestoreFH = setInterval(function () { //no there's no event triggered for this. I checked
			int++;
			if ( int > 20 ) {
				clearInterval(VERestoreFH);
			}
			if ( ! $('html.ve-deactivating')[0] ) {
				clearInterval(VERestoreFH);
				if ( FTT.firstHeadingLinks && ! $('#FTTFirstHeadingLinks')[0] ) {
				$('#firstHeading').append(FTT.firstHeadingLinks);
				}
				if ( FTT.ledeEditLink && ! $('#FTTEditLede')[0] ) {
					$('#firstHeading').append(FTT.ledeEditLink);
				}
				if ( FTT.collapseAllChevron && ! $('#FTTCollapseAllSections')[0] ) {
					$('#firstHeading').append(FTT.collapseAllChevron);
				}
			}
		},100);
	}
};
mw.hook('postEdit').add(function(){FTT.VEMurderSpree();}); //when an edit is submitted with VE it overwrites the header to account for DISPLAYTITLE changes (T318772)
FTT.addToolBarLink = function(pos,newLinkSpanClass,textName,onClickAction,hrefContent) {
	FTT.addLinkToToolbar = ( ( newLinkSpanClass == 'FTTSVGNewSectionIcon FTTSVG' && FTT.settings.firstHeadingAdd ) || ( newLinkSpanClass == 'FTTSVGEditIcon FTTSVG' && FTT.settings.firstHeadingFull ) );
	if ( M1('skin') == 'minerva' ) {
		FTT.skinLinkPadding = 'padding:0.75em 1em 0.75em 1em';
	} else if ( M1('skin') == 'monobook' && FTT.addLinkToToolbar ) {
		FTT.skinLinkPadding = 'padding:0';
	} else {
		FTT.skinLinkPadding = '';
	}
	if ( ! FTT.addLinkToToolbar || textName == FTT.B1['action-edit'] ) { // adding icon to #firstHeading or inserting full page edit icon into toolbar
		FTT.newLinkSpanInnerText = '<span class="FTTScreenReaderLabel" data-content="'+textName+' ('+FTT.SN+')"></span>';
	} else {
		newLinkSpanClass = '';
		if ( FTT.isMobile ) {
			FTT.newLinkSpanInnerText = '+';
		} else if ( $('#ca-addsection')[0] ) {
			FTT.newLinkSpanInnerText = $('#ca-addsection')[0].innerText;
		} else {
			FTT.newLinkSpanInnerText = FTT.B1.addsection;
		}
	}
	FTT.newLinkSpanClass = newLinkSpanClass;
	if ( ['modern','monobook'].includes(M1('skin')) && FTT.addLinkToToolbar ) {
		FTT.newLinkSpanInnerText = textName;
		FTT.newLinkSpanClass = '';
	}
	FTT.newLinkHref = '';
	if ( typeof hrefContent != 'undefined' ) {
		FTT.newLinkHref = ' href="' + hrefContent + '"';
	}
	FTT.newLinkOnclick = '';
	if ( typeof onClickAction != 'undefined' ) {
		FTT.newLinkOnclick = ' onclick="' + onClickAction + '"';
	}
	FTT.newLinkLi = document.createElement('li');
	FTT.newLinkLi.innerHTML = '<a title="'+textName+' ('+FTT.SN+')" '+FTT.newLinkHref + FTT.newLinkOnclick + '><span style="' + FTT.skinLinkPadding + '" class="FTTReplyLink ' + FTT.newLinkSpanClass + '">' + FTT.newLinkSpanInnerText + '</span></a>';
	FTT.newLinkLi.title = textName + ' ('+FTT.SN+')';
	if ( ! FTT.addLinkToToolbar ) {
		if ( ! $('#FTTFirstHeadingLinks')[0] ) {
			FTT.firstHeadingLinks = document.createElement('span');
			FTT.firstHeadingLinks.id = 'FTTFirstHeadingLinks';
			try {FTT.firstHeadingDir = $('#firstHeading')[0].attributes.dir.value;} catch (e) {}
			if ( ( FTT.firstHeadingDir || FTT.CSSContentDir ) == 'rtl' ) {
				FTT.firstHeadingLinks.style.float = 'left';
			} else {
				FTT.firstHeadingLinks.style.float = 'right';
			}
			$('#firstHeading').append(FTT.firstHeadingLinks);
			$('#ca-view').on('click',function(int){
				FTT.VEMurderSpree();
			});
			$('body').keydown(function(event){
				if ( event.which == 27 ) {//pressed escape. also a way to exit VE
					FTT.VEMurderSpree();
				}
			});
		}
		$('#FTTFirstHeadingLinks').append(FTT.newLinkLi.innerHTML);
		return;
	}
	if ( textName == FTT.B1.newsection ) {
		FTT.newLinkLi.id = 'FTTNewSectionTop';
	}
	if ( $('#ca-edit')[0] && ['vector','vector-2022','timeless','modern'].includes(M1('skin')) ) {
		if ( M1('skin') == 'vector-2022' ) {
			FTT.newLinkLi.classList.add('mw-list-item');
			FTT.newLinkLi.classList.add('vector-tab-noicon');
		} else if ( M1('skin') == 'timeless' ) {
			FTT.timelessCopyStyles = ['background-image','background-color','background-repeat','background-position','display','margin-bottom'];
			FTT.timelessStyleStr = '';
			for(FTT.timelessCopyStylesInt=0;FTT.timelessCopyStylesInt<FTT.timelessCopyStyles.length;FTT.timelessCopyStylesInt++){
				FTT.timelessStyleStr = FTT.timelessStyleStr + FTT.timelessCopyStyles[FTT.timelessCopyStylesInt]+':'+getComputedStyle($('#ca-addsection a,a#ca-addsection')[0])[FTT.timelessCopyStyles[FTT.timelessCopyStylesInt]]+';';
			}
			FTT.newLinkLi.style = FTT.timelessStyleStr;
		}
		if ( $('#ca-edit')[0].nextElementSibling ) {
			$('#ca-edit')[0].parentElement.insertBefore(FTT.newLinkLi,$('#ca-edit')[0].nextElementSibling);
		} else {
			$('#ca-edit')[0].parentElement.append(FTT.newLinkLi);
		}
		if ( FTT.settings.hideDT && FTT.settings.nSecLink && mw.user.options.get('discussiontools-newtopictool') == 1 && $('#ca-addsection')[0] ) {
			$('#ca-addsection')[0].childNodes[0].classList.add('FTTNoDisplay');
			$('#ca-addsection-sticky-header').on('click',function(event){ //Vector 2022
				event.preventDefault();event.stopPropagation();
				FTT.openReplyForm(FTT.PRMnSec);
			});
		}
	} else if ( M1('skin') == 'monobook' && typeof $('#ca-edit')[0] != "undefined" ) {
		if ( textName == FTT.B1.newsection ) {
			FTT.newLinkLi.childNodes[0].childNodes[0].innerHTML = '+(B)';
		}
		if ( FTT.settings.hideDT && FTT.settings.nSecLink && mw.user.options.get('discussiontools-newtopictool') == 1 && $('#ca-addsection')[0] ) {
			$('#ca-addsection')[0].classList.add('FTTNoDisplay');
		}
		if ( pos == -1 ) {
			$('#p-cactions .pBody ul').append(FTT.newLinkLi);
		} else {
			$('#p-cactions .pBody ul')[0].insertBefore(FTT.newLinkLi, $('#p-cactions .pBody ul')[0].children[pos]);
		}
	} else if ( M1('skin') == 'minerva' && typeof $('#ca-edit')[0] != "undefined" ) {
		FTT.minervaLabelSpan = document.createElement('span');
		FTT.minervaLabelSpan.classList.add('toggle-list-item__label');
		FTT.minervaLabelSpan.innerText = textName.slice(0,1).toUpperCase()+textName.slice(1);
		FTT.newLinkLi.childNodes[0].append(FTT.minervaLabelSpan);
		FTT.newLinkLi.childNodes[0].classList.add('toggle-list-item__anchor');
		FTT.newLinkLi.childNodes[0].style = 'padding-left:0;padding-right:0;';
		FTT.newLinkLi.classList.add('toggle-list-item');
		if ( $('#p-tb')[0] ) {
			$('#p-tb')[0].append(FTT.newLinkLi);
		}
		if ( FTT.settings.hideDT ) {
			mw.util.addCSS('.ext-discussiontools-init-new-topic{display:none !important;}');
		}
	}
};
if ( FTT.settings.hideAdvFE && M1('skin') == 'minerva' && FTT.isDiscussionPage ) {
	mw.util.addCSS('.skin-minerva--talk-simplified .comment .list-header,.comment .comment-content{display:none !important;}');
}
FTT.PRMnSec = {
	'id':'newSectionForm-' + D1(M1('wgPageName')),
	'type':'newsection',
	'pageTitle': M1('wgPageName'),
};
FTT.PRMFP = {'id':'editFullPage-' + M1('wgPageName'),'type':'editFullPage','pageTitle': M1('wgPageName')};
FTT.loadPreloadParams = function() {
	FTT.preloadParamsURL = window.location.href.match(/preloadparams%5[Bb]%5[Dd]=[^&]*/g);
	if ( FTT.preloadParamsURL ) {
		FTT.PRMFP.preloadparams = [];
		FTT.PRMnSec.preloadparams = [];
		FTT.preloadParam = [];
		for(FTT.preloadParamsURLInt=0;FTT.preloadParamsURLInt<FTT.preloadParamsURL.length;FTT.preloadParamsURLInt++){
			FTT.preloadParam[FTT.preloadParamsURLInt] = D2(FTT.preloadParamsURL[FTT.preloadParamsURLInt].replace(/preloadparams%5[Bb]%5[Dd]=/,'')).replace(/\+/g,' ');
			FTT.PRMFP.preloadparams.push(FTT.preloadParam[FTT.preloadParamsURLInt]);
			FTT.PRMnSec.preloadparams.push(FTT.preloadParam[FTT.preloadParamsURLInt]);
		}
	}
};
if ( ( FTT.settings.nSecLink || FTT.settings.nSecBottomLink ) && M1('wgPageContentModel') == 'wikitext' && M1('wgIsProbablyEditable') ) {
	if ( M2('action') == 'edit' && M2('section') == 'new' ) {
		if ( M2('preloadtitle') || M2('preload') ) {
			FTT.PRMnSec.subtype = 'InputBox'; //we'll just pretend this is an inputbox so preload parameters will be processed
			if ( M2('preloadtitle') ) {
				FTT.PRMnSec.preloadtitle = M2('preloadtitle');
			}
			if ( M2('preload') ) {
				FTT.PRMnSec.preload = M2('preload');
			}
			if ( M2('editintro') ) {
				FTT.PRMnSec.editIntro = M2('editintro');
			}
			FTT.loadPreloadParams();
		}
	}
	if ( FTT.settings.nSecLink ) {
		if ( $('#ca-addsection')[0] ) {
			FTT.newSectionOrigLink = $('#ca-addsection a,a#ca-addsection')[0].href;
			if ( FTT.settings.hideDT && FTT.newSectionOrigLink.match(/&action=edit&section=new$/) && mw.user.options.get('discussiontools-newtopictool') == 1 ) {
				FTT.newSectionOrigLink = FTT.newSectionOrigLink + '&preloadparams=Factotum_nixed_DT_with_phony_preloadparams_you_are_welcome&loadFTT=0';
			} else {
				FTT.newSectionOrigLink = FTT.newSectionOrigLink + '&loadFTT=0';
			}
		}
		FTT.addToolBarLink(5,'FTTSVGNewSectionIcon FTTSVG',FTT.B1.newsection,'event.preventDefault();event.stopPropagation();$(\'#FTTnSecBottom\').addClass(\'FTTNoDisplay\');FTT.' + FTT.keyOpenReplyForm + '(FTT.' + Object.keys(FTT)[Object.values(FTT).indexOf(FTT.PRMnSec)] + ')',FTT.newSectionOrigLink);
	}
	if ( FTT.settings.hideAdvFE && $('.minerva-talk-add-button')[0] ) {
		$('.minerva-talk-add-button')[0].outerHTML = $('.minerva-talk-add-button')[0].outerHTML.replace(/href="[^"]*"/,'');
		$('.minerva-talk-add-button').on('click',function(){FTT.openReplyForm(FTT.PRMnSec);});
	}
}
if ( FTT.isMobile && M2('redlink') == 1 && (FTT.settings.nSecLink || FTT.settings.nSecBottomLink || FTT.settings.editFullPage) ) { //when the native editor opens, all you can do is create the page or exit, but exit NAVIGATES AWAY, which it DEFINITELY shouldn't do.
	var MFnewpagebugger = setInterval(function (int) {
		if ( $('.mw-overlays-container ul.header-cancel button.cancel')[0] ) {
			$('.mw-overlays-container ul.header-cancel button.cancel').on('click',function(event){event.stopPropagation();window.location = M1('wgArticlePath').replace('$1',M1('wgPageName'));});
			clearInterval(MFnewpagebugger);
		}
		if ( int>50 ) {
			clearInterval(MFnewpagebugger);
		}
		int++;
	},200);
}
if ( FTT.settings.hideNewSec && FTT.settings.nSecLink && $('#ca-addsection')[0] ) {
	$('#ca-addsection').addClass('FTTNoDisplay');
}
if ( M2('preload') && M1('wgPageContentModel') == 'wikitext' && M1('wgIsProbablyEditable') && M2('preload') ) {
//	FTT.debug('set preload settings for full page editing from URL parameters');
	FTT.PRMFP.preload = M2('preload');
	FTT.loadPreloadParams();
}
if ( FTT.settings.editFullPage && ( (M1('wgIsProbablyEditable')&&M1('wgPageContentModel')!='flow-board') || FTT.settings.editTheUneditable ) ) {
		FTT.addToolBarLink(5,'FTTSVGEditIcon FTTSVG',FTT.B1['action-edit'],'FTT.' + FTT.keyOpenReplyForm + '(FTT.' + Object.keys(FTT)[Object.values(FTT).indexOf(FTT.PRMFP)] + ')');
}
FTT.collapseSection = function(sectionID,level,linkElement,int) {
//	FTT.debug('activeElement:');
//	FTT.debug(document.activeElement);
//	FTT.debug('linkElement:');
//	FTT.debug(linkElement);
	if ( typeof sectionID == 'undefined' ) {
		int = 0;
		FTT.findCollapsHeaderElement = linkElement.target;
		while ( int < 10 && FTT.findCollapsHeaderElement && ! FTT.findCollapsHeaderElement.nodeName.match(/^H[12]$/) ) {
			int++;
			FTT.findCollapsHeaderElement = FTT.findCollapsHeaderElement.parentElement;
		}
	}
	if ( ! FTT.hotKeyCollap && ! $('.FTTLastCmtLink:hover,#toc a:hover,.FTTCollapMini:hover')[0] && ! document.activeElement.classList.contains('FTTCollapMini') && ( ( document.activeElement.nodeName == 'A' && ! document.activeElement.classList.contains('sectioncollapse') ) || ( document.activeElement.nodeName != 'A' && $('a:hover')[0] && ! $('#FTTCollapseAllSections:hover')[0] ) ) ) { //second condition can happen with href-less JS links
//		FTT.debug('you clicked a link within a header. not (un)collapsing section');
		return;
	} else if ( typeof sectionID == 'undefined' && FTT.findCollapsHeaderElement.nodeName.match(/^H[12]$/) ) {
//		FTT.debug('you clicked a header in a non-link area to collapse/uncollapse the section');
		sectionID = FTT.findCollapsHeaderElement.parentElement.id;
		level = FTT.findCollapsHeaderElement.nodeName.slice(1);
	}
	if ( typeof sectionID == 'undefined' ) {
//		FTT.debug('collapseSection: no sectionID, quitting');
		return;
	}
//	FTT.debug('(un)collapse section ' + sectionID);
	if ( sectionID != '' && $('#'+sectionID + ' .FTTForm, #'+sectionID + ' .FTTHighlightCmt')[0] ) {
//		FTT.debug('won\'t collapse a section with an open reply form or highlighted comment in it');
		$('#'+sectionID + ' .FTTForm, #'+sectionID + ' .FTTHighlightCmt')[0].scrollIntoView(FTT.smoothScroll); //todo: this doesn't seem to work properly, section can get collapsed anyway despite this triggering
		return;
	}
	if ( $('#'+sectionID)[0] && $('#'+sectionID).hasClass('collapsedSection') ) {
//		FTT.debug('unhiding section ' + sectionID);
		$('#'+sectionID+' *').removeClass('FTTNoDisplay').removeClass('FTTSVGChevronIconRot').removeClass('collapsedSection');
		$('#'+sectionID).removeClass('collapsedSection');
		if ( $('#'+sectionID + ' .sectioncollapse')[0] ) { // does not exist on w:en:Main_Page
			$('#'+sectionID + ' .sectioncollapse')[0].classList.remove('FTTSVGChevronIconRot');
		}
		if ( FTT.settings.hideArchived ) {
			$('.archived .FTTCmtA,.boilerplate .FTTCmtA').addClass('FTTNoDisplay');
		}
		if ( FTT.settings.hideArchivedAll ) {
			$('.archived .FTTReplyLink,.boilerplate .FTTReplyLink,.archived .FTTBracket,.boilerplate .FTTBracket').addClass('FTTNoDisplay');
		}
		return;
	}
//	FTT.debug('hiding section ' + sectionID);
	$('#'+sectionID+' *:not(H' + level + ',H' + level + ' *)').addClass('FTTNoDisplay');
	$('#'+sectionID).addClass('collapsedSection');
	if ( $('#'+sectionID + ' .sectioncollapse')[0] ) {
		$('#'+sectionID + ' .sectioncollapse')[0].classList.add('FTTSVGChevronIconRot');
	}
	if ( $('.collapsedSection')[0] && $('#firstHeading .sectioncollapse span')[0] ) {
		$('#firstHeading .sectioncollapse span')[0].classList.add('FTTSVGChevronIconRot');
		$('#firstHeading .sectioncollapse span')[0].title = FTT.B1['collapsible-expand'];
		$('#firstHeading .sectioncollapse span .FTTScreenReaderLabel')[0].innerText = FTT.B1['collapsible-expand'];
	}
	if ( FTT.scrHeader ) {
		delete FTT.scrHeader;
		FTT.HLscroll($('#'+sectionID)[0]);
	}
};
FTT.HLscroll = function(el,a) {
	el.style.transition = 'background ease-in 0.4s';
	el.style.background = 'rgba(0,100,256,0.3)';
	a = true;
	if ( getComputedStyle(el).display == 'none' ) { //collapsed section
		FTT.HLUncollapse(el);
	}
	FTT.scrollAndCall(el,function(){
		var HLscroll = setInterval(function () {
			if ( a ) { //after 0.4s
				el.style.background = '';
				a = false;
			} else { //after 0.8s
				el.style.transition = '';
				clearInterval(HLscroll);
			}
		},400);
	},'magic');
};
FTT.toggleAllSections = function(sInt) {
	if ( $('#mw-content-text .FTTSVGChevronIconRot').length ) {
//		FTT.debug('toggleAllSections: something was collapsed, uncollapsing');
		FTT.collapsedSections = Array.from($('.sectioncollapse.FTTSVGChevronIconRot'));
		for (sInt=0;sInt<FTT.collapsedSections.length;sInt++) {
			FTT.collapsedSections[sInt].click();
		}
		$('#firstHeading .sectioncollapse span')[0].classList.remove('FTTSVGChevronIconRot');
		$('#firstHeading .sectioncollapse span')[0].title = FTT.B1['collapsible-collapse'];
		$('#firstHeading .sectioncollapse span .FTTScreenReaderLabel')[0].innerText = FTT.B1['collapsible-collapse'];
	} else if ( ! $('#mw-content-text .FTTSVGChevronIconRot').length ) {
//		FTT.debug('toggleAllSections: nothing is collapsed, collapsing all');
		FTT.uncollapsedSectionCount = $('.sectioncollapse.FTTSVGChevronIcon:not(.FTTSVGChevronIconRot)').length;
		for (sInt=0;sInt<FTT.uncollapsedSectionCount;sInt++) {
			$('.sectioncollapse.FTTSVGChevronIcon:not(.FTTSVGChevronIconRot)')[0].click();
		}
		if ( $('#firstHeading .sectioncollapse span')[0] ) {
			$('#firstHeading .sectioncollapse span')[0].classList.add('FTTSVGChevronIconRot');
			$('#firstHeading .sectioncollapse span')[0].title = FTT.B1['collapsible-expand'];
			$('#firstHeading .sectioncollapse span .FTTScreenReaderLabel')[0].innerText = FTT.B1['collapsible-expand'];
		}
	}
};
FTT.allSectionsHTMLContainer = {};
FTT.sectionHTMLContainer = {};
FTT.splitHTMLSections = function(level,elementToSort,splitH1,intContEl) {
	if ( $(elementToSort + ' H' + level + ':not(#mw-toc-heading)')[1] ) { //don't bother if there are less than 2 sections of this type
		if ( ! FTT.collapsingArticle && $('.toctogglecheckbox')[0] && FTT.settings.reverseSectionOrder && FTT.settings.reverseCollapToC ) {
//			FTT.debug('splitHTMLSections: hiding the TOC. kinda confusing when we\'re going to Australia, and if you are reversing sections you probably want to start reading anyway.');
			$('.toctogglecheckbox')[0].checked = true;
		}
		FTT.sectionHTMLContainer[level] = {};
		FTT.allSectionsHTMLContainer[level] = document.createElement('div');
		FTT.allSectionsHTMLContainer[level].classList.add('FTTH' + level + 'SectionsContainer');
		FTT.allSectionsInsertionElement = $(elementToSort + ' H' + level + ':not(#mw-toc-heading)')[0];
		FTT.firstSectionIsEncapsulated = false;
		for (FTT.allSectionsHTMLContainerInt=0;FTT.allSectionsHTMLContainerInt<100;FTT.allSectionsHTMLContainerInt++){
			FTT.allSectionsClassList = FTT.allSectionsInsertionElement.parentElement.classList;
			if ( FTT.allSectionsClassList.contains('mw-parser-output') || FTT.allSectionsClassList.contains('FTTH1SectContainer') ) {
				break;
			} else {
//				FTT.debug('splitHTMLSections: first section is encapsulated in whatever, try parentElement');
				FTT.firstSectionIsEncapsulated = true;
				FTT.allSectionsInsertionElement = FTT.allSectionsInsertionElement.parentElement;
			}
		}
		if ( FTT.firstSectionIsEncapsulated && FTT.allSectionsInsertionElement.nextElementSibling ) {
			FTT.allSectionsInsertionElement.parentElement.insertBefore(FTT.allSectionsHTMLContainer[level],FTT.allSectionsInsertionElement.nextElementSibling);
		} else {
			FTT.allSectionsInsertionElement.parentElement.insertBefore(FTT.allSectionsHTMLContainer[level],FTT.allSectionsInsertionElement);
		}
		FTT.contentSectionChildren = Array.from($(elementToSort).children());
		FTT.contentLength = FTT.contentSectionChildren.length;
		for (intContEl = 0; intContEl < FTT.contentLength; intContEl++) {
			if ( FTT.contentSectionChildren[intContEl].nodeName == 'H' + level ) {
//				FTT.debug('splitHTMLSections: another section header found');
				if ( ! FTT.collapsingArticle && FTT.settings.autoCollapse && intContEl == FTT.contentLength -1 && typeof FTT.sectionHTMLID != 'undefined' && ! $('#' + FTT.sectionHTMLID + ' .FTTNewCmt')[0] ) {
//					FTT.debug('splitHTMLSections: collapse previous section as it contains nothing new since your last visit');
					FTT.collapseSection(FTT.sectionHTMLID,level);
				}
				FTT.parentSectionHTMLID = '';
				if ( splitH1 ) {
					FTT.parentSectionHTMLID = '_' + splitH1;
				}
				FTT.sectionHTMLID = 'FTTL' + level + 'sectionContainer' + intContEl + FTT.parentSectionHTMLID;
				if ( ( FTT.collapsingArticle && FTT.settings.collapArticle ) || FTT.settings.collapsible ) { //T306660 is pretty cool. FTT had a head start to implement it quickly.
					FTT.collapseChevron = document.createElement('span');
					FTT.collapseChevron.style = 'float:' + FTT.CSSDirectionR;
					FTT.collapseChevron.className = 'sectioncollapse FTTSVG FTTSVGChevronIcon';
					FTT.collapseChevron.title = FTT.B1['collapsible-expand'] + ' / ' + FTT.B1['collapsible-collapse'];
					FTT.collapseChevron.innerHTML = '<span class="FTTScreenReaderLabel"></span>'; //populated by CSS :before
//					FTT.debug('splitHTMLSections: appending chevron to #'+intContEl);
					if ( ! FTT.settings.hideDT && mw.user.options.get('discussiontools-visualenhancements') && FTT.isDiscussionPage ) {
						//insert after .mw-editsection when user has DT's discussion activity enabled and visible. Probably more expensive than just appending to the header (more DOM poking) but otherwise .ext-discussiontools-init-section-bar makes everything look like shit. Putting discussion activity inside the header, yet displaying it below it. Great job. Thanks DT.
						FTT.insertAfter(FTT.contentSectionChildren[intContEl].querySelectorAll('.mw-editsection,.mw-headline')[0],FTT.collapseChevron);
					} else {
						FTT.contentSectionChildren[intContEl].append(FTT.collapseChevron);
					}
				}
//				FTT.debug('splitHTMLSections: append or prepend (for section reversal) new section div to allSectionsHTMLContainer #'+intContEl+', level'+level);
				FTT.sectionHTMLContainer[level][intContEl] = document.createElement('div');
				FTT.sectionHTMLContainer[level][intContEl].id = FTT.sectionHTMLID;
				FTT.sectionHTMLContainer[level][intContEl].classList.add('FTTH' + level + 'SectContainer');
				FTT.sectionHTMLContainer[level][intContEl].prepend(FTT.contentSectionChildren[intContEl]); //add H1/H2/etc element to new div
				if ( ! FTT.collapsingArticle && FTT.settings.reverseSectionOrder ) {
//					FTT.debug('splitHTMLSections: prepending');
					FTT.allSectionsHTMLContainer[level].prepend(FTT.sectionHTMLContainer[level][intContEl]);
				} else {
//					FTT.debug('splitHTMLSections: appending');
//					FTT.debug(FTT.sectionHTMLContainer[level][intContEl]);
//					FTT.debug('splitHTMLSections: to');
//					FTT.debug(FTT.allSectionsHTMLContainer[level]);
					FTT.allSectionsHTMLContainer[level].append(FTT.sectionHTMLContainer[level][intContEl]);
				}
//				FTT.debug('splitHTMLSections: appending section child');
//				FTT.debug(FTT.contentSectionChildren[intContEl]);
//				FTT.debug('splitHTMLSections: to');
//				FTT.debug(FTT.sectionHTMLContainer[level][intContEl]);
				FTT.sectionHTMLContainer[level][intContEl].append(FTT.contentSectionChildren[intContEl]);
				FTT.latestHTMLSectionInt = intContEl;
//				FTT.debug('splitHTMLSections: latestHTMLSectionInt is '+FTT.latestHTMLSectionInt);
			} else if ( typeof FTT.latestHTMLSectionInt != 'undefined' ) { //! FTT.contentSectionChildren[intContEl].classList.contains('FTTH1SectionsContainer') && ! FTT.contentSectionChildren[intContEl].classList.contains('FTTH2SectionsContainer')
//				FTT.debug('splitHTMLSections: some element of a section, reconstruct section within new div, appending');
//				FTT.debug(FTT.contentSectionChildren[intContEl]);
//				FTT.debug('splitHTMLSections: to');
//				FTT.debug(FTT.sectionHTMLContainer[level][FTT.latestHTMLSectionInt]);
				FTT.sectionHTMLContainer[level][FTT.latestHTMLSectionInt].append(FTT.contentSectionChildren[intContEl]);
			}
		}
		$(elementToSort + ' H' + level).on('click',function() { FTT.collapseSection(undefined,undefined,event); });
		delete FTT.latestHTMLSectionInt;
		if ( FTT.settings.collapsible && ! $('#FTTCollapseAllSections')[0] && $('.FTTSVGChevronIcon')[0] ) {
			FTT.collapseAllChevron = document.createElement('span');
			FTT.collapseAllChevron.style = 'float:' + FTT.CSSDirectionR;
			FTT.collapseAllChevron.title = FTT.B1['collapsible-collapse'];
			FTT.collapseAllChevron.innerHTML = '<a id="FTTCollapseAllSections" class="sectioncollapse FTTReplyLink" style="color:#888"><span class="FTTSVG FTTSVGChevronIcon"><span class="FTTScreenReaderLabel" data-content="'+FTT.B1['collapsible-collapse']+'"></span></span></a>';
			$('#firstHeading').append(FTT.collapseAllChevron);
			$('#FTTCollapseAllSections').on('click',function(){FTT.toggleAllSections();});
		}
	} else {
//		FTT.debug('splitHTMLSections: there\'s no or only one level ' + level + ' header in ' + elementToSort);
	}
};
FTT.floatTheTOC = function(){
	if ( M1('skin') == 'vector-2022' ) {
		$('body').append($('.mw-table-of-contents-container.mw-sticky-header-element')[0]);
		$('.mw-table-of-contents-container.mw-sticky-header-element').addClass('FTTFloatingToC');
		$('.FTTFloatingToC').removeClass(['mw-sticky-header-element','mw-table-of-contents-container']);
		$('.FTTFloatingToC ul').addClass('FTTNoDisplay');
		$('.FTTFloatingToC').on('click',function(){
			if ( $('.FTTFloatingToC ul')[0] && $('.FTTFloatingToC ul')[0].classList.contains('FTTNoDisplay') ) {
				$('.FTTFloatingToC ul').removeClass('FTTNoDisplay');
			} else {
				$('.FTTFloatingToC ul').addClass('FTTNoDisplay');
			}
		});
	} else if ( $('#toc')[0] ) {
		if ( M1('skin') == 'minerva' && $('#toctogglecheckbox')[0] ) {
			$('#toctogglecheckbox')[0].checked = false;
		} else if ( $('#toctogglecheckbox')[0] ) {
			$('#toctogglecheckbox')[0].checked = true;
		} else {
			$('#toc .toctitle:eq(0)').addClass('FTTNoDisplay');
		}
		$('body').append($('#toc')[0]);
		$('#toc').addClass('FTTFloatingToC');
		$('#toc').on('click',function(ev){ev.stopPropagation();});
		if ( $('#mw-toc-heading')[0] ) {
			$('#mw-toc-heading')[0].outerHTML = $('#mw-toc-heading')[0].outerHTML;
		}
		$('#mw-toc-heading').on('click',function(){$('#toctogglecheckbox').click();});
	}
	$('#toc.FTTFloatingToC #mw-toc-heading').addClass('FTTTocTitle');
	if ( ['vector','monobook','modern','timeless','minerva'].includes(M1('skin')) ) {
		FTT.toctogglespan = function() {
			if ( $('#toctogglecheckbox')[0] && $('#toctogglecheckbox')[0].checked ) {
				$('#toc.FTTFloatingToC .toctogglespan').addClass('FTTNoDisplay');
				$('.skin-minerva #toc.FTTFloatingToC').removeClass('FTTFloatToCMinerva');				
			} else {
				$('#toc.FTTFloatingToC .toctogglespan').removeClass('FTTNoDisplay');
				$('.skin-minerva #toc.FTTFloatingToC').addClass('FTTFloatToCMinerva');
			}
		};
		$('#toctogglecheckbox').on('change',FTT.toctogglespan);
		FTT.toctogglespan();
	}
};
if ( FTT.settings.replySecLink && ( FTT.isDiscussionPage || $('.FTTCmt')[0] ) ) {
//	FTT.debug('links will be added to the bottom of sections to reply to them');
	FTT.replyToSectionLinkNeeded = true;
}
FTT.commentersInSection = [];
FTT.commentersInSectionBySection = {};
FTT.getSillyNumerals = function() {
	if ( ! FTT.sillyNumerals ) {
		FTT.sillyNumeralsArr = {
			'aeb-arab':'ar',
			'anp':'hi',
			'ar':'٠١٢٣٤٥٦٧٨٩',
			'as':'০১২৩৪৫৬৭৮৯',
			'awa':'hi',
			'azb':'fa',
			'bcc':'fa',
			'bgn':'fa',
			'bho':'hi',
			'blk':'my',
			'bn':'as',
			'bo':'༠༡༢༣༤༥༦༧༨༩',
			'bpy':'as',
			'bqi':'fa',
			'ckb':'ar',
			'dty':'hi',
			'dz':'bo',
			'fa':'۰۱۲۳۴۵۶۷۸۹',
			'glk':'fa',
			'gom-deva':'hi',
			'gu':'૦૧૨૩૪૫૬૭૮૯',
			'hi':'०१२३४५६७८९',
			'kjp':'my',
			'kk':'fa',
			'km':'០១២៣៤៥៦៧៨៩',
			'kn':'೦೧೨೩೪೫೬೭೮೯',
			'ks-arab':'ar',
			'ks-deva':'hi',
			'ks':'ar',
			'ku-arab':'ar',
			'lki':'fa',
			'lo':'໐໑໒໓໔໕໖໗໘໙',
			'lrc':'fa',
			'luz':'fa',
			'lzh':'〇一二三四五六七八九',
			'mai':'hi',
			'mni':'꯰꯱꯲꯳꯴꯵꯶꯷꯸꯹',
			'mnw':'my',
			'mr':'hi',
			'my':'၀၁၂၃၄၅၆၇၈၉',
			'mzn':'fa',
			'ne':'hi',
			'new':'hi',
			'nqo':'߀߁߂߃߄߅߆߇߈߉',
			'or':'୦୧୨୩୪୫୬୭୮୯',
			'pi':'hi',
			'pnb':'fa',
			'ps':'fa',
			'sa':'hi',
			'sat':'᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙',
			'sdh':'fa',
			'skr':'ar',
			'tcy':'kn'
		};
		FTT.sillyNumerals = FTT.sillyNumeralsArr[M1('wgContentLanguage')];
		if ( FTT.sillyNumerals && FTT.sillyNumerals.match(/^[a-z\-]*$/) ) { //to avoid duplicating series of numbers, entries like 'mai':'mr' are valid
			FTT.sillyNumerals = FTT.sillyNumeralsArr[FTT.sillyNumerals].split('');
		}
	}
};
FTT.getSillyNumerals();
FTT.convSillyNumerals = function(str,i) {
	if ( ! FTT.sillyNumerals ) { //this function shouldn't even be running with input that doesn't require conversion, but just in case
		return str;
	}
	for(i=0;i<10;i++){
		str = str.replace(new RegExp(FTT.sillyNumerals[i],'g'),i);
	}
	return str;
};
FTT.sigTWZJ = function(str3){
	if ( M1('wgContentLanguage') == 'hak' ) {
		str3 = str3.replace(/\((Ngit|Yit|Ngi|Sâm|Si|Ńg|Liuk)\) /g,'');
	}
	return str3.replace(/年/g,' ').replace(/日( \([^\)]+\))?/g,'').replace(/月/g,'月 '); // 年 = year, 月 = month, 日 = day
};
FTT.sigMY = function(str3){
	return str3.replace(/([0-2]?[0-9]:[0-5][0-9])၊/g,'$1');
};
FTT.sigFA = function(str3){
	return str3.replace('ساعت ','').replace(/،/g,'').replace(/[ ][ ]/,' ');
};
FTT.sigPT = function(str3){
	return str3.replace(/([0-9])h([0-5][0-9])min/g,'$1:$2');
};
FTT.signDateCleanerFunc = { //language-specific adjustments. Replacements should be global as this will process both timestamp textnodes (where global isn't needed) and whole comment lines (where it could be needed) when scanning subscribed sections
	'ami':FTT.sigTWZJ,
	'ar':function(str2){return str2.replace(/ت&#160;ع&#160;م/g,'UTC');}, //UTC written in hieroglyphs
	'arz':function(str2){return str2.replace(/يو تى سى/g,'UTC');}, //UTC written in hieroglyphs
	'as':function(str2){return str2.replace(/\(ইউ.টি.চি.\)/g,'(UTC)');},
	'azb':FTT.sigFA,
	'be':function(str2){return str2.replace(/\(\+[0-9]*\)/g,'(MINSK)');},
	'bn':function(str2){return str2.replace(/\(ইউটিসি\)/g,'(UTC)');},
	'bpy':function(str2){return str2.replace(/\(ইউটিসি\)/g,'(UTC)');},
	'blk':FTT.sigMY,
	'cdo':function(str2){return str2.replace(/ hô̤/g,'').replace(/\(.*\) ([0-2]?[0-9]:[0-5][0-9])/g,'$1');},
	'ckb':function(str2){return str2.replace(/ی/g,'');},
	'crh':function(str2){return str2.replace(/ s\. /g,' ');},
	'cs':function(str2){return str2.replace(/ 1. 2([0-1])/g,' Jan 2$1').replace(/ 2. 2([0-1])/g,' Feb 2$1').replace(/ 3. 2([0-1])/g,' Mar 2$1').replace(/ 4. 2([0-1])/g,' Apr 2$1').replace(/ 5. 2([0-1])/g,' May 2$1').replace(/ 6. 2([0-1])/g,' Jun 2$1').replace(/ 7. 2([0-1])/g,' Jul 2$1').replace(/ 8. 2([0-1])/g,' Aug 2$1').replace(/ 9. 2([0-1])/g,' Sep 2$1').replace(/ 10. 2([0-1])/g,' Oct 2$1').replace(/ 11. 2([0-1])/g,' Nov 2$1').replace(/ 12. 2([0-1])/g,' Dec 2$1');}, //timestamps like "7. 8. 2022, 17:32 (CEST)", ambiguous. Months can appear identical to days. In the year 2200 this will break. Place your bets, by 2200: a) we are all using Flow b) the Czech language has gone extinct c) wikis and their contributors have been replaced by an Elon Musk hive mind, whatever that is d) zombie apocalypse e) AJ upped the ones to twos in 2100 so why did we ever worry
	'diq':function(str2){return str2.replace(/(2[0-9]{3} \()($|\[\[)/g,'$1UTC)');},
	'fa':FTT.sigFA,
	'fur':function(str2){return str2.replace(/ di /g,'');},
	'glk':FTT.sigFA,
	'hak':FTT.sigTWZJ,
	'gan':FTT.sigTWZJ,
	'gn':function(str2){return str2.replace(/jasyteĩ/g,'Jan').replace(/jasykõi/g,'Feb').replace(/jasyapy/g,'Mar').replace(/jasyrundy/g,'Apr').replace(/jasypoteĩ/g,'Jun').replace(/jasypokõi/g,'Jul').replace(/jasypoapy/g,'Aug').replace(/jasyporundy/g,'Sep').replace(/jasypo/g,'May').replace(/jasypateĩ/g,'Nov').replace(/jasypakõi/g,'Dec').replace(/jasypa/g,'Oct');}, //overlapping months. fun
	'ii':FTT.sigTWZJ,
	'ja':FTT.sigTWZJ,
	'jbo':function(str2){return str2.replace(/ly\. pa no/g,'Oct').replace(/ly\. pa pa/g,'Nov').replace(/ly\. pa re/g,'Dec').replace(/ly\. pa/g,'Jan').replace(/ly\. re/g,'Feb').replace(/ly\. ci/g,'Mar').replace(/ly\. vo/g,'Apr').replace(/ly\. mu/g,'May').replace(/ly\. xa/g,'Jun').replace(/ly\. ze/g,'Jul').replace(/ly\. bi/g,'Aug').replace(/ly\. so/g,'Sep');}, //having 3 months (oct/nov/dec) that 100% overlap with another (jan) is a FUCKING BAD idea for SO many reasons. Fix your language! And as this is Lojban, a constructed language, that isn't even offensive!
	'kk':function(str2){return str2.replace(/\(\+[0-9]*\)/g,'(ALMATY)');},
	'km':function(str2){return str2.replace(/(ម៉ោង|ឆ្នាំ|ទី|ថ្ងៃអាទិត្យ|ថ្ងៃច័ន្ទ|ថ្ងៃអង្គារ|ថ្ងៃពុធ|ថ្ងៃព្រហស្បតិ៍|ថ្ងៃសុក្រ|ថ្ងៃសៅរ៍)/g,'').replace(/[ ][ ]/g,' ');}, //hour, year, garbage, weekdays
	'ko':function(str2){return str2.replace(/[년일]/g,'').replace(/\([^A-Za-z]\) /g,'');}, //년 = year, 일 = day. there's also (월) for.. something, don't need it
	'lrc':FTT.sigFA,
	'mnw':FTT.sigMY,
	'mwl':FTT.sigPT,
	'my':FTT.sigMY,
	'mzn':FTT.sigFA,
	'ne':function(str2){return str2.replace(/\(नेपाली समय\)/g,'(UTC)');}, //UTC written in hieroglyphs
	'ps':function(str2){return str2.replace(/(2[0-9]{3} \()($|\[\[)/g,'$1UTC)');},
	'pt':FTT.sigPT,
	'pwn':FTT.sigTWZJ,
	'sd':function(str2){return str2.replace(/\( يو.ٽي.سي\)/g,'(UTC)');}, //UTC written in hieroglyphs
	'si':function(str2){return str2.replace(/\(යූටීසී\)/g,'(UTC)');}, //UTC written in hieroglyphs
	'szy':FTT.sigTWZJ,
	'tay':FTT.sigTWZJ,
	'tet':FTT.sigPT,
	'th':function(str2){FTT.solarThaiYear = str2.match(/ (2[0-9]{3}) \(/);if(FTT.solarThaiYear){str2=str2.replace(FTT.solarThaiYear[1],FTT.solarThaiYear[1]-543);}return str2;},
	'trv':FTT.sigTWZJ,
	'ur':function(str2){return str2.replace(/([0-9])[\ء] \(/g,'$1 (').replace(/\(($|\[\[)/g,'(UTC)');},
	'uz':function(str2){return str2.replace(/([0-2]?[0-9]:[0-5][0-9], [0-9]+)\-/g,'$1 ');},
	'wuu':function(str2){return FTT.sigTWZJ(str2).replace(/号/g,'');},
	'zh':FTT.sigTWZJ,
	'lzh':function(str2){return FTT.sigTWZJ(str2).replace(/([0-2][0-9])時([0-5][0-9])分/g,'$1:$2').replace(/ ([0-9])/g,'');},
	'nan':function(str2){return str2.replace(/(\-nî|\-ji̍t)/g,'').replace(/\((?!UTC)[^\)]*\) /g,'');}, //zh-min-nan
	'vo':function(str2){return str2.replace(/id \(UTC\)/g,' (UTC)');},
	'yue':function(str2){return str2.replace(/([0-9])年/g,'$1 ').replace(/([0-9])號/g,'$1').replace(/([0-9]月)/g,'$1 ');},
	'za':FTT.sigTWZJ,
	'qqq':function(str2){return str2;},
};
if ( typeof FTT.signDateCleanerFunc[M1('wgContentLanguage')] == 'function' ) {
	FTT.applyCleanerFunc = true;
}
FTT.cleanTimestamp = function(str) { //apply signDateCleanerFunc and some more generic adjustments
//	FTT.debug('cleanTimestamp: '+str);
	if ( FTT.cleanTimestampReplaceChar ) {
		str = str.replace(new RegExp(E1(FTT.cleanTimestampReplaceChar[0]),'g'),' ');
//		FTT.debug('cleanTimestamp: removed some unneeded stuff: '+FTT.cleanTimestampReplaceChar[0]);
		if ( FTT.cleanTimestampReplaceChar[1] ) {
			str = str.replace(new RegExp(E1(FTT.cleanTimestampReplaceChar[1]),'g'),' ');
//			FTT.debug('cleanTimestamp: removed some more unneeded stuff: '+FTT.cleanTimestampReplaceChar[1]);
		}
		if ( FTT.cleanTimestampReplaceChar[2] ) {
			str = str.replace(new RegExp(E1(FTT.cleanTimestampReplaceChar[2]),'g'),' ');
//			FTT.debug('cleanTimestamp: again removed some more unneeded stuff: '+FTT.cleanTimestampReplaceChar[1]);
		}
		str = str.replace(/[ ][ ]/g,' ');
	}
	if ( FTT.sillyNumerals ) {
		str = FTT.convSillyNumerals(str);
	}
	if ( FTT.applyCleanerFunc ) { 
		str = FTT.signDateCleanerFunc[M1('wgContentLanguage')](str);
	}
	return str.replace(/[\.,]([^0-9])/g, '$1');
};
FTT.getTZOffset = function() {
	FTT.TZOffsetDate = new Date();
	FTT.TZOffsetDateWiki = FTT.TZOffsetDate.toLocaleString("en", {timeZone:FTT.wikiTimezone});
	FTT.TZOffsetDateUTC = FTT.TZOffsetDate.toLocaleString("en", {timeZone:'UTC'});
	FTT.TZOffsetDateWikiMs = new Date(FTT.TZOffsetDateWiki).getTime();
	FTT.TZOffsetDateUTCMs = new Date(FTT.TZOffsetDateUTC).getTime();
	FTT.TZOffsetSeconds = ( FTT.TZOffsetDateWikiMs - FTT.TZOffsetDateUTCMs);
	if ( FTT.TZOffsetSeconds <= 0 ) {
		FTT.TZOffsetSecondsFlip = Math.abs(FTT.TZOffsetSeconds);
	} else {
		FTT.TZOffsetSecondsFlip = FTT.TZOffsetSeconds * -1;
	}
};
FTT.getTZOffset();
FTT.localDateFormat = {year:'numeric',month:'short',weekday:undefined,day:'numeric',hour:'2-digit',minute:'2-digit',hour12:false};
FTT.localDateFormatTimeOnly = {year:undefined,month:undefined,weekday:undefined,day:undefined,hour:'2-digit',minute:'2-digit',hour12:false};
if ( FTT.settings.dateLinksLocalTime12H ) { FTT.localDateFormat.hour12 = true;FTT.localDateFormatTimeOnly.hour12 = true;}
if ( FTT.settings.dateLinksLocalTimeNumMonth ) { FTT.localDateFormat.month = 'numeric';}
else if ( FTT.settings.dateLinksLocalTimeLongMonth ) { FTT.localDateFormat.month = 'long';}
if ( FTT.settings.dateLinksLocalTimeWeekdayFull ) { FTT.localDateFormat.weekday = 'long';}
else if ( FTT.settings.dateLinksLocalTimeWeekday ) { FTT.localDateFormat.weekday = 'short';}
FTT.userOptTZ = mw.user.options.get('timecorrection').match(/ZoneInfo\|[0-9]+\|(.+)/);
if ( FTT.settings.dateLinksLocalTimeUserOptTZ && FTT.userOptTZ ) { FTT.localDateFormat.timeZone = FTT.userOptTZ[1];FTT.localDateFormatTimeOnly.timeZone = FTT.userOptTZ[1];}
FTT.dayOfWeek = new Date(FTT.timestampInit).getDay();
FTT.renderLocalDate = function(localTimestamp,relativeOnly) {
	FTT.nAgo = (FTT.timestampInit - localTimestamp);
	if ( FTT.settings.dateLinksLocalTimeRelative || relativeOnly) {
		if ( FTT.nAgo < 120000 ) { //less than 2 minutes ago, say "n seconds ago"
			FTT.localDateRelative = FTT.B1.ago.replace('$1',FTT.B1.seconds.replace('$1',Math.round(FTT.nAgo/1000)));
		} else if ( FTT.nAgo < 5400000 ) { //less than 90 minutes ago, say "n minutes ago"
			FTT.localDateRelative = FTT.B1.ago.replace('$1',FTT.B1.minutes.replace('$1',Math.round(FTT.nAgo/60000)));
		} else if ( FTT.nAgo < 86400000 ) { //less than 24 hours ago, say "n hours ago"
			FTT.localDateRelative = FTT.B1.ago.replace('$1',FTT.B1.hours.replace('$1',Math.round(FTT.nAgo/3600000)));
		} else if ( FTT.nAgo < 604800000 ) { //less than 7 days ago, say "n days ago" based on the weekday
			FTT.weekDayOfCmt = new Date(localTimestamp).getDay();
			if ( FTT.dayOfWeek <= FTT.weekDayOfCmt ) {
				FTT.commentDaysAgo = (FTT.dayOfWeek +7) - FTT.weekDayOfCmt;
			} else {
				FTT.commentDaysAgo = FTT.dayOfWeek - FTT.weekDayOfCmt;
			}
			FTT.localDateRelative = FTT.B1.ago.replace('$1',FTT.B1.days.replace('$1',FTT.commentDaysAgo));
		} else if ( FTT.nAgo < 5356800000 ) { //less than 62 days ago, say "n days ago"
			FTT.localDateRelative = FTT.B1.ago.replace('$1',FTT.B1.days.replace('$1',Math.round(FTT.nAgo/86400000)));
		} else if ( FTT.nAgo < 63244800000 ) { //less than 732 days ago (one non-leap year plus one leap year plus one day), say "n months ago"
			FTT.localDateRelative = FTT.B1.ago.replace('$1',FTT.B1.months.replace('$1',Math.round(FTT.nAgo/86400000/30.44))); //30.44 is like an average month.. I'd say close enough for this
		} else { //years
			FTT.localDateRelative = FTT.B1.ago.replace('$1',FTT.B1.years.replace('$1',Math.floor(FTT.nAgo/86400000/365)));
		}
		if ( relativeOnly ) {
			return FTT.localDateRelative;
		}
	}
	if ( FTT.settings.dateLinksLocalTimeAbsolute) {
		FTT.localDateAbsolute = new Date(localTimestamp).toLocaleString(undefined,FTT.localDateFormat);
	}
	if ( FTT.settings.dateLinksLocalTimeRelative && FTT.settings.dateLinksLocalTimeAbsolute) {
		return FTT.localDateRelative + ' (' + FTT.localDateAbsolute + ')';
	} else if ( FTT.settings.dateLinksLocalTimeRelative ) {
		return FTT.localDateRelative;
	} else if ( FTT.settings.dateLinksLocalTimeAbsolute ) {
		return FTT.localDateAbsolute;
	} else {
		return ''; //no relative time, no absolute date. who cares when someone said something
	}
};
FTT.getTitleFromToc = function(el) {
	return $('#toc a[href="#'+CSS.escape(el)+'"] .toctext')[0].innerText;
};
FTT.getPageTitleAndSectionName = function(mwHeadLineInt,foundPageTitle) { //this is called right after opening the reply form, so the user can start typing while this runs. Could also do it upon hitting the reply button, but that would make replying slightly less responsive
	if ( FTT.UIReplyButton ) {
		FTT.UIReplyButton.setDisabled(true); //just so you can't reply before this stuff is known
	}
	if ( typeof FTT.mwHeadLineArray == 'undefined' ) {//going over .mw-headline/.mw-editsection elements (typically <50ms) only has to be done once per page load, after that the values can be retrieved from the array (typically <1ms)
		FTT.mwHeadLineArray = Array.from($('#mw-content-text .mw-heading H1,#mw-content-text .mw-heading H2,#mw-content-text .mw-heading H3,#mw-content-text .mw-heading H4,#mw-content-text .mw-heading H5,#mw-content-text .mw-heading H6'));
		FTT.mwEditSectionArray = Array.from($(FTT.mwEditSec));
		FTT.mwHeadLineSectionTitle = [];
		FTT.mwHeadLineCorrupted = 0; // w:en:MediaWiki:Gadget-autonum.js workaround. (plus potentially other gadgets that mess with .mw-headline text content) Sadly not easily detectable
		if ( $('#toc .toctext')[0] ) {
			FTT.TocEntry = $('#toc .toctext');
			FTT.TocEntryTitle = FTT.TocEntry[0].innerText;
			FTT.headLineTitleAnchor = new mw.Uri((FTT.TocEntry[0].parentElement.href||''));
			FTT.headLineTitle = document.getElementById(FTT.headLineTitleAnchor.fragment.replace(/ /g,'+'));
			if ( FTT.headLineTitle && FTT.headLineTitle[0] && FTT.headLineTitle[0].innerText.length != FTT.TocEntryTitle.length ) {
				FTT.mwHeadLineCorrupted = 1;
			}
		}
		if ( FTT.mwHeadLineArray.length != FTT.mwEditSectionArray.length ) {
//			FTT.debug('mismatched amount of .mw-headlines and .mw-editsections, get section titles using alternative slower method');
			FTT.gotSectionTitlesAlt = true;
			FTT.mwEditSectionArrayLength = FTT.mwEditSectionArray.length;
			FTT.mwEditSectionBadBadBad = false;
			for (FTT.mwEditSectionInt=0;FTT.mwEditSectionInt<FTT.mwEditSectionArray.length;FTT.mwEditSectionInt++) {
				FTT.mwEditSectionAltCheckLink = FTT.mwEditSectionArray[FTT.mwEditSectionInt].querySelectorAll('A')[0];
				if ( FTT.mwEditSectionAltCheckLink && FTT.mwEditSectionAltCheckLink.href.match(/action=edit/) ) {
					if ( FTT.mwHeadLineCorrupted ) {
						FTT.mwHeadLineSectionTitle.push(FTT.getTitleFromToc(FTT.mwEditSectionArray[FTT.mwEditSectionInt].parentElement.querySelectorAll('.mw-headline')[0].id));
					} else {
						FTT.mwHeadLineSectionTitle.push(FTT.mwEditSectionArray[FTT.mwEditSectionInt].parentElement.querySelectorAll('.mw-headline')[0].innerText);
					}
				} else {
//					FTT.debug('mwEditSectionArray #' + FTT.mwEditSectionInt + ' is a FAKE .mw-editsection! Bad bad bad! Changing it\'s class to mw-editsection-like.');
					FTT.mwEditSectionArray[FTT.mwEditSectionInt].classList.add('mw-editsection-like');
					FTT.mwEditSectionArray[FTT.mwEditSectionInt].classList.remove('mw-editsection');
					FTT.mwEditSectionBadBadBad = true;
				}
			}
			if ( FTT.mwEditSectionBadBadBad ) {
//				FTT.debug('You had fake mw-editsections. Let\'s try this again.');
				delete FTT.mwHeadLineArray;
				delete FTT.gotSectionTitlesAlt;
				return FTT.getPageTitleAndSectionName(mwHeadLineInt);
			}
		}
		if ( FTT.mwHeadLineArray.length != FTT.mwEditSectionArray.length && FTT.mwHeadLineSectionTitle.length != FTT.mwEditSectionArrayLength ) {
			FTT.addScrewedLink('mismatched amount of headlines (' + FTT.mwHeadLineArray.length + ') / section titles related to .mw-editsection (' + FTT.mwHeadLineSectionTitle.length + ') and section edit links (' + FTT.mwEditSectionArray.length + ')','Unexpected difference between the number of section titles and section edit links.');
			return false;
		}
		FTT.mwHeadLinePageTitle = [];
		FTT.mwHeadLineSectionSeqID = [];
		FTT.mwHeadLineSectionSeq = [];
		for(FTT.mwEditSectionArrayInt=0;FTT.mwEditSectionArrayInt<FTT.mwEditSectionArray.length;FTT.mwEditSectionArrayInt++) {
			if ( typeof FTT.gotSectionTitlesAlt == 'undefined' ) {
				if ( FTT.mwHeadLineCorrupted ) {
					FTT.mwHeadLineSectionTitle.push(FTT.getTitleFromToc(FTT.mwHeadLineArray[FTT.mwEditSectionArrayInt].id));
				} else {
					FTT.mwHeadLineSectionTitle.push(FTT.mwHeadLineArray[FTT.mwEditSectionArrayInt].innerText);
				}
			}
			if ( M1('skin') == 'minerva' ) {
				FTT.getPageTitleFromElement = 'A.edit-page';
			} else {
				FTT.getPageTitleFromElement = 'A';
			}
			foundPageTitle = 0;
			for (FTT.mwEditSectionElementArrayInt=0;FTT.mwEditSectionElementArrayInt<FTT.mwEditSectionArray[FTT.mwEditSectionArrayInt].querySelectorAll(FTT.getPageTitleFromElement).length;FTT.mwEditSectionElementArrayInt++){
				FTT.mwEditSectionArrayTestElement = FTT.mwEditSectionArray[FTT.mwEditSectionArrayInt].querySelectorAll(FTT.getPageTitleFromElement)[FTT.mwEditSectionElementArrayInt];
				FTT.pageTitleUri = '';		
				try{FTT.pageTitleUri = new mw.Uri(FTT.mwEditSectionArray[FTT.mwEditSectionArrayInt].querySelectorAll(FTT.getPageTitleFromElement)[FTT.mwEditSectionElementArrayInt].attributes.href.nodeValue);} catch (e) {}
				if ( FTT.pageTitleUri.query && FTT.pageTitleUri.query.title ) {
					FTT.mwHeadLinePageTitle.push(FTT.pageTitleUri.query.title.replace(/_/g, ' '));
					foundPageTitle = 1;
					break;
				}
			}
			if ( !foundPageTitle ) {
//				FTT.debug('getPageTitleAndSectionName: failed to get page name from .mw-editsection link, assuming current page (could be wrong, but maybe preferable over just throwing an error)');
				FTT.mwHeadLinePageTitle.push(mw.config.get('wgPageName').replace(/_/g, ' '));
			}
			FTT.sectionPlusPage = FTT.mwHeadLinePageTitle[FTT.mwEditSectionArrayInt] + FTT.mwHeadLineSectionTitle[FTT.mwEditSectionArrayInt];
			for (FTT.secseqint = 0; FTT.secseqint < 100; FTT.secseqint++) {
//				FTT.debug('make record of the section+pagename so others with the same data can be identified - this is #' + FTT.secseqint);
				if ( FTT.mwHeadLineSectionSeqID.indexOf(FTT.sectionPlusPage + '-' + FTT.secseqint) == -1 ) {
					FTT.mwHeadLineSectionSeqID.push(FTT.sectionPlusPage + '-' + FTT.secseqint);
					FTT.mwHeadLineSectionSeq.push(FTT.secseqint);
					break;
				}
			}
		}
	}
	FTT.mwHeadLineIndex = FTT.mwEditSectionArray.indexOf(FTT.processElementArray[mwHeadLineInt]);
	if ( FTT.UIReplyButton ) {
		FTT.UIReplyButton.setDisabled(false);
	}
	return {'pageTitle':FTT.mwHeadLinePageTitle[FTT.mwHeadLineIndex],'sectionTitle':FTT.mwHeadLineSectionTitle[FTT.mwHeadLineIndex].replace(/\xA0/g,' '),'sectionSeq':FTT.mwHeadLineSectionSeq[FTT.mwHeadLineIndex]}; //xA0 thing replaces no-break spaces with regular ones, because MediaWiki seemingly replaces regular spaces with no-break space, like before exclamation marks
};
FTT.addPageAndSectionTitleToRPL = function(PRM) {
	if ( typeof PRM.sectionTitle != 'undefined' || typeof PRM.pageTitleInt != 'number') {
//		FTT.debug('these PRM either already have a sectionTitle or there is no pageTitleInt so we can\'t work with it');
		return PRM;
	}
	if ( PRM.pageTitleInt == -1 ) {
//		FTT.debug('addPageAndSectionTitleToRPL found pageTitleInt -1, assuming sectionless comment');
		PRM.sectionTitle = '';
		PRM.sectionseq = 0;
		if ( typeof PRM.pageTitle == 'undefined' ) { //locator for example already knows the pagetitle but doesn't know the section.
			PRM.pageTitle = M1('wgPageName');
		}
		FTT.PRM[PRM.int] = PRM;
		return PRM;
	}
	FTT.sectionInfo = FTT.getPageTitleAndSectionName(PRM.pageTitleInt);
	if ( FTT.sectionInfo ) {
		if ( typeof PRM.pageTitle == 'undefined' ) { //locator for example already knows the pagetitle but doesn't know the section.
			PRM.pageTitle = FTT.sectionInfo.pageTitle;
		}
		PRM.sectionTitle = FTT.sectionInfo.sectionTitle;
		PRM.sectionseq = FTT.sectionInfo.sectionSeq;
	}
	return PRM;
};
FTT.parseParticipants = function(wikitext,mwEditSecInt) {
	FTT.replaceParticipants = function() {
		var DelayedParticipants = setInterval(function () {
			if ( $('#FTTParticipants')[0] ) {
				clearInterval(DelayedParticipants);
				$('#FTTParticipants')[0].innerHTML = FTT.participantsWikitextParsed[mwEditSecInt];
			}			
		},50);
	};
	if ( FTT.participantsWikitextParsed[mwEditSecInt] ) {
		FTT.replaceParticipants();
		return;
	}
	api.post({action:'parse',text:'<div id="PARTICIPANTS_START"></div>\n'+wikitext+'<div id="PARTICIPANTS_END"></div>',title:FTT.PN,format:'json',disablelimitreport:true}).then(function(data){
		FTT.participantsWikitextParsed[mwEditSecInt] = data.parse.text['*'].replace(/^[^]*<div id="PARTICIPANTS_START">([^]*)<div id="PARTICIPANTS_END"><\/div>[^]*$/,'$1');
		FTT.replaceParticipants();
	});
};
FTT.addDiscussionActivity = function(mwEditSecInt,pInt,uInt) {
	FTT.lastCommentRelative = FTT.renderLocalDate(FTT.sectionLatestCommentDate + FTT.TZOffsetSeconds,true);
	FTT.participantsHTML[mwEditSecInt] = '<div id="FTTParticipants"><ul>';
	FTT.participantsWikitext[mwEditSecInt] = '';
	for (pInt=0;pInt<FTT.commentersInSection.length;pInt++) {
		FTT.participantsHTML[mwEditSecInt] = FTT.participantsHTML[mwEditSecInt]+'<li><a href="'+M1('wgArticlePath').replace('$1',FTT.NS[2]+':'+D1(FTT.commentersInSection[pInt]))+'">'+FTT.commentersInSection[pInt]+'</a>&nbsp;(<a href="'+M1('wgArticlePath').replace('$1',FTT.NS[3])+':'+D1(FTT.commentersInSection[pInt])+'">'+FTT.B1.talkpagelinktext+'</a>)</li>';
		FTT.participantsWikitext[mwEditSecInt] = FTT.participantsWikitext[mwEditSecInt] + '* [['+FTT.NS[2]+':'+FTT.commentersInSection[pInt]+'|'+FTT.commentersInSection[pInt]+']] ([['+FTT.NS[3]+':'+FTT.commentersInSection[pInt]+'|'+FTT.B1.talkpagelinktext+']])\n';
	}
	FTT.participantsHTML[mwEditSecInt] = FTT.participantsHTML[mwEditSecInt]+'</ul></div>';
	FTT.participantsWikitext[mwEditSecInt] = FTT.participantsWikitext[mwEditSecInt]+'';
	FTT.participantStart = '';
	FTT.participantEnd = '';
	if ( FTT.commentersInSection.length > 0 ) {
		FTT.discActUsersTip = '';
		for (uInt=0;uInt<FTT.commentersInSection.length;uInt++) {
			FTT.discActUsersTip = FTT.discActUsersTip + FTT.commentersInSection[uInt]+', ';
		}
		FTT.discActUsersTip = FTT.discActUsersTip.slice(0,-2);
		if ( FTT.discActUsersTip.length > 1000 ) {
			FTT.discActUsersTip = FTT.discActUsersTip.slice(0,1000)+'...';
		}
		FTT.participantStart = '<a title="'+FTT.escapeHTML(FTT.discActUsersTip)+'" onclick="FTT.popup(FTT.participantsHTML[\''+mwEditSecInt+'\'],\'#FTTParticipants\',true);FTT.parseParticipants(FTT.participantsWikitext[\''+mwEditSecInt+'\'],'+mwEditSecInt+');">';
		FTT.participantEnd = '</a>';
	}
	if ( FTT.commentersInSection.length == 1 ) {
		FTT.userSP = FTT.msgs.discActUserS;
	} else {
		FTT.userSP = FTT.msgs.discActUserP;
	}
	if ( FTT.commentsInSection == 1 ) {
		FTT.repliesSP = FTT.msgs.discActS;
	} else {
		FTT.repliesSP = FTT.msgs.discActP;
	}
	FTT.discussionActivityInfo = ' | '+FTT.repliesSP.replace(/COMMENTS/,FTT.commentsInSection)+' | '+FTT.userSP.replace(/USERS/,FTT.participantStart+FTT.commentersInSection.length)+FTT.participantEnd;
	if ( FTT.processElementArray[FTT.pageTitleToEditInt] ) {
		FTT.titleParentEl = FTT.processElementArray[FTT.pageTitleToEditInt].parentElement;
		FTT.titleParentEl.title = FTT.msgs.discActLast.replace(/LAST/,FTT.lastCommentRelative) + FTT.discussionActivityInfo.replace(/<[\/]?a[^>]*>/g,'');
		if ( ! FTT.settings.discussionActivityTitleOnly ) {
			FTT.discussionActivityElement = document.createElement('span');
			FTT.discussionActivityElement.classList = 'FTTDiscussionActivity';
			FTT.discussionActivityElement.innerHTML = FTT.msgs.discActLast.replace(/LAST/,'<a title="'+FTT.msgs.discActLastTip+'" class="FTTLastCmtLink" onclick="FTT.scrollToComment(\''+FTT.sectionLatestUser+'\',\''+FTT.sectionLatestCommentDate+'\');">'+FTT.lastCommentRelative+'</a>') + FTT.discussionActivityInfo+'<br />';
			if ( FTT.isMF && FTT.titleParentEl.nextElementSibling && FTT.titleParentEl.nextElementSibling.nodeName == 'SECTION' ) {
				FTT.titleParentEl.nextElementSibling.prepend(FTT.discussionActivityElement);
			} else {
				FTT.insertAfter(FTT.titleParentEl,FTT.discussionActivityElement);
			}
		}
	}
};
FTT.extInputBox = {};
FTT.extendInputBox = function(int,ibInt,PPInt,PPVal,el) {
	FTT.IBJSON = FTT.testValidJSON(FTT.processElementArray[int].id.match(/^FTT[^\{]*(\{.*\})[^\}]*$/)[1]);
	FTT.extInputBox[int] = {};
	if ( FTT.IBJSON ) {
		PPInt=0;
		mw.loader.using(['oojs-ui-core']).then(function(){
			for(ibInt=0;ibInt<Object.keys(FTT.IBJSON).length;ibInt++) {
				if ( Object.keys(FTT.IBJSON)[ibInt].match(/^PP/) ) {
					PPVal = null;
					if ( FTT.processElementArray[int].querySelectorAll('input[name="preloadparams[]"]')[PPInt] ) {
						PPVal = FTT.processElementArray[int].querySelectorAll('input[name="preloadparams[]"]')[PPInt].value;
					}
					PPInt++;
					FTT.extInputBox[int]['TIW'+ibInt] = new OO.ui.TextInputWidget({value:(PPVal||'')});
					FTT.extInputBox[int]['TIWFL'+ibInt] = new OO.ui.FieldLayout( FTT.extInputBox[int]['TIW'+ibInt], { label: FTT.IBJSON[Object.keys(FTT.IBJSON)[ibInt]].replace(/_/g,' '), align: 'top', classes: [ 'FTTIBExt' ]} );
					el = FTT.processElementArray[int].querySelectorAll('input[type=submit]')[0];
					el.parentElement.insertBefore(FTT.extInputBox[int]['TIWFL'+ibInt].$element[0],el);
				} else if ( Object.keys(FTT.IBJSON)[ibInt] == 'autopost' ) {
					FTT.extInputBox[int].autopost = 1;
				}
			}
			el.parentElement.insertBefore(document.createElement('br'),el);
		});
	}
};
//The following types exist, these describe how to handle the item:
// comment: reply to existing comment (has signature, placed in appropriate location below existing comment)
// edit: alteration of an existing comment (no new signature)
// newheading: new subsection with comment (has signature, uses appendtext in the API)
// heading: only exists with subtype "edit" (full section edit)
// newsection: creates a new section with comment at the bottom of the page (section=new in the API)
// FCL: FTT Comment Link, special link that either opens the reply form (if the user has FTT loaded already) or reloads the page with withJS option to load FTT
// editFullPage: edit the page as a whole. Doesn't squeeze output into a single line, doesn't add signature, etc
//Subtypes, describes more minor adjustments to the general type:
// InputBox: subtype for newsection. See mediawiki.org/wiki/Extension:InputBox
// legacy: subtype for comments, searches HTML for text that looks like a signature date. Searches <p>, <dd> and <li>, end to start. If a date is found it keeps going to try and find a username link to extract the username.
// locator: subtype for comments, added to comments by FTT by default. Contains username and the date as the number of milliseconds since 1970-01-01. You know. The SENSIBLE thing to do.
// heading: subtype for heading
// page: subtype for FCL, add comment to the bottom of the page.
// section: subtype for FCL, add comment to the bottom of the section.
// edit: subtype for "heading"
FTT.searchNodeContents = function(i,tempI) {
	FTT.elementClassList = Array.from(FTT.processElementArray[i].classList);
	if ( typeof FTT.elementClassList == 'undefined' ) {
		FTT.elementClassList = [];
	}
	if ( FTT.elementClassList.includes('FTTCmt') ) {
//		FTT.debug('locator found');
		if ( FTT.processElementArray[i].id.match(/(.*)\:([0-9]{13,14})\:([^\:]*).*/) == null ) {
//			FTT.debug('invalid locator');
			return;
		}
		FTT.locatorUsername = FTT.processElementArray[i].id.replace(/(.*)\:([0-9]{13,14})\:([^\:]*).*/, '$1').replace(/(FTTCOLON|FTTCLN)/g, ':'); //in time "FTTCOLON" can be deprecated
		FTT.locatorTimestamp = Number(FTT.processElementArray[i].id.replace(/(.*)\:([0-9]{13,14})\:([^\:]*).*/, '$2'));
		FTT.locatorPageTitle = FTT.processElementArray[i].id.replace(/(.*)\:([0-9]{13,14})\:([^\:]*).*/, '$3').replace(/(FTTCOLON|FTTCLN)/g, ':');
		FTT.PRM[i] = {
			'int': i,
			'type':'comment',
			'subtype':'locator',
			'id': FTT.processElementArray[i].id,
			'pageTitle': D2(FTT.locatorPageTitle),
			'pageTitleInt':FTT.pageTitleToEditInt,
			'origReplyTo': D2(FTT.locatorUsername),
			'origTimestamp': FTT.locatorTimestamp,
			'origTimestampTextNode':FTT.locatorTimestamp
		};
		if ( FTT.settings.discussionActivity && FTT.locatorTimestamp > FTT.sectionLatestCommentDate ) {
			FTT.sectionLatestCommentDate = FTT.locatorTimestamp;
			FTT.sectionLatestAnchor = FTT.processElementArray[i].id;
			FTT.sectionLatestUser = D2(FTT.locatorUsername);
		}
		if ( FTT.settings.dateLinksLocalTime ) {
			FTT.newSigTime = FTT.renderLocalDate(Number(FTT.locatorTimestamp));
			for(FTT.newSigTimeChildNodesInt=0;FTT.newSigTimeChildNodesInt<FTT.processElementArray[i].childNodes.length;FTT.newSigTimeChildNodesInt++){
				FTT.origSigDateTextNode = FTT.processElementArray[i].childNodes[FTT.newSigTimeChildNodesInt].textContent;
				FTT.origSigDateTextNodeClean = FTT.cleanTimestamp(FTT.origSigDateTextNode);
				FTT.origSigDateMatch = FTT.origSigDateTextNodeClean.match(FTT.signDateRegExpLocalMonths);
				if ( FTT.processElementArray[i].childNodes[FTT.newSigTimeChildNodesInt].nodeName == '#text' && FTT.origSigDateMatch ) {
					FTT.origSigDate = FTT.origSigDateMatch[0];
					FTT.processElementArray[i].childNodes[FTT.newSigTimeChildNodesInt].parentElement.title = FTT.origSigDate; //put original textnode content/timestamp in the title
					FTT.processElementArray[i].childNodes[FTT.newSigTimeChildNodesInt].textContent = FTT.origSigDateTextNodeClean.replace(FTT.origSigDate, FTT.newSigTime);
				}
			}
		}
		if ( ! FTT.firstReplyInSection ) {
			FTT.firstReplyInSection = $.extend( true, {}, FTT.PRM[i] );
			FTT.firstReplyInSection.isFirst = true;
		}
		FTT.addReplyLinkTo(FTT.PRM[i]);
		if ( FTT.commentersInSection.indexOf(D2(FTT.locatorUsername.replace(/_/g,' '))) == -1 ) {
			FTT.commentersInSection.push(D2(FTT.locatorUsername.replace(/_/g,' ')));
		}
		FTT.commentsInSection++;
	} else if ( FTT.elementClassList.includes('mw-editsection') ) { //section edit link
		if ( FTT.replyToSectionLinkNeeded && FTT.firstReplyInSection && FTT.commentersInSection.length > 3 ) {
//			FTT.debug('found a new section. adding "reply to section starter" link for the previous section');
			FTT.firstReplyInSection.triggerInt = i;
			FTT.addReplyLinkTo(FTT.firstReplyInSection);
			delete FTT.firstReplyInSection;
		}
		if ( FTT.settings.discussionActivity && FTT.commentsInSection > 0 ) {
			FTT.addDiscussionActivity(i);
		}
		FTT.commentersInSectionBySection[FTT.pageTitleToEditInt] = FTT.commentersInSection;
		FTT.commentersInSection = [];
		FTT.commentsInSection = 0;
		FTT.sectionLatestCommentDate = 0;
		delete FTT.firstReplyInSection;
//		FTT.debug('mw-editsection link found (' + i + ')');
		FTT.pageTitleToEditInt = i;
		FTT.PRM[i] = {
			'int':i,
			'id':'heading-' + i,
			'type':'newheading',
			'subtype':'heading',
			'pageTitleInt':FTT.pageTitleToEditInt,
		};
		if ( FTT.settings.editFullSection || FTT.settings.secLinks || FTT.settings.stalkAddSubLinks || FTT.settings.dateLinksIconSection ) {
			FTT.addReplyLinkTo(FTT.PRM[i]); //add link to create a new subsection
			FTT.sectionHeaderLinksAdded = true;
		}
	} else if ( FTT.elementClassList.includes('commentbox') || FTT.elementClassList.includes('createbox') ) {
//		FTT.debug('found InputBox');
		if ( FTT.settings.inputBoxTO ) {
//			FTT.debug('inputbox takeover enabled');
			if ( FTT.processElementArray[i].id.match(/^FTT/) ) {
				FTT.extendInputBox(i);
			}
			FTT.PRM[i] = {
				'int': i,
				'id':'newSectionForm-InputBox',
				'type':'newsection',
				'subtype':'InputBox',
				'pageTitle': M1('wgPageName'),
				'preloadparams': []
			};
			for (tempI = 0; tempI < FTT.processElementArray[i].children.length; tempI++) {
//				FTT.debug('process child element #' + tempI + '/' + FTT.processElementArray[i].children.length + ' for element #' + i);
				if ( FTT.processElementArray[i].children[tempI].name == 'preload' ) {
					FTT.PRM[i].preload = FTT.processElementArray[i].children[tempI].value;
				} else if ( FTT.processElementArray[i].children[tempI].name == 'preloadparams[]' ) {
					FTT.PRM[i].preloadparams.push(FTT.processElementArray[i].children[tempI].value);
				} else if ( FTT.processElementArray[i].children[tempI].name == 'editintro' ) {
					FTT.PRM[i].editIntro = FTT.processElementArray[i].children[tempI].value;
				} else if ( FTT.processElementArray[i].children[tempI].name == 'summary' ) {
					FTT.PRM[i].summary = FTT.processElementArray[i].children[tempI].value;
				} else if ( FTT.processElementArray[i].children[tempI].name == 'minor' ) {
					FTT.PRM[i].minor = FTT.processElementArray[i].children[tempI].value;
				} else if ( FTT.processElementArray[i].children[tempI].name == 'title' && FTT.processElementArray[i].children[tempI].value != '' ) {
					FTT.PRM[i].pageTitle = FTT.processElementArray[i].children[tempI].value;
				} else if ( FTT.processElementArray[i].children[tempI].name == 'preloadtitle' ) {
					FTT.PRM[i].preloadtitle = FTT.processElementArray[i].children[tempI].value;
				} else if ( FTT.processElementArray[i].children[tempI].type == 'submit' ) {
//					FTT.debug('sabotage submit, add our own onclick instead');
					FTT.processElementArray[i].children[tempI].outerHTML = FTT.processElementArray[i].children[tempI].outerHTML.replace(/>$/, ' onclick="FTT.' + FTT.keyOpenReplyForm + '(FTT.' + FTT.keyPRM + '[' + JSON.stringify(FTT.PRM[i].int) + '])">');
					FTT.processElementArray[i].querySelectorAll('input[type=submit]')[0].addEventListener('click',function(e){e.preventDefault();}); //don't do whatever you normally do
				}
			}//for
		}//FTT.settings.inputBoxTO
	} else if ( ( FTT.elementClassList.includes('mw-ui-button') || FTT.elementClassList.includes('oo-ui-buttonElement-button')) && FTT.settings.mwuibuttonTO) { //todo: add support for preload etc
//		FTT.debug('I haz a .mw-ui-button, what do I do wif it?');
		FTT.mwuibuttonRegExp = new RegExp('\\?((title=[^=&]*[&]?|action=edit[&]?|section=new[&]?){3})$');
		if ( ( FTT.processElementArray[i].href && FTT.processElementArray[i].href.match(FTT.mwuibuttonRegExp) ) || ( FTT.processElementArray[i].parentElement && FTT.processElementArray[i].parentElement.href && FTT.processElementArray[i].parentElement.href.match(FTT.mwuibuttonRegExp) ) ) {
			FTT.processElementArray[i].onclick = function(ev){
				ev.preventDefault();ev.stopPropagation();
				FTT.PRMmwuibutton = $.extend( true, {}, FTT.PRMnSec );
				if ( this.href && this.href.match(FTT.mwuibuttonRegExp) ) { // "this" is the .mw-ui-button element that was clicked
					FTT.PRMmwuibutton.pageTitle = D2(this.href.match(/title=([^&#]*)/)[1]);
				} else {
					FTT.PRMmwuibutton.pageTitle = D2(this.parentElement.href.match(/title=([^&#]*)/)[1]);
				}
				FTT.openReplyForm(FTT.PRMmwuibutton);
			};
		}
	} else if ( FTT.elementClassList.includes('FTT-comment-link') ) {
//		FTT.debug('FTT-comment-link (FCL) found');
		FTT.PRM[i] = {
			'int': i,
			'id':'FTT-comment-link',
			'type':'FCL',
			'subtype':'page',
			'pageTitleInt':FTT.pageTitleToEditInt,
			'createonly':false,
			'origReplyTo':'NO-VALUE-PLEASE-IGNORE',
			'origTimestamp':'2000-01-01T00:00:00.000Z',
			'preloadparams': [],
			'editintro':undefined
		};
		FTT.commentLinkID = FTT.testValidJSON(D2(FTT.processElementArray[i].id));
		if ( FTT.commentLinkID ) {
			if ( FTT.commentLinkID.type == 'section' ) {
				FTT.PRM[i].subtype = 'section';
			}
			if ( FTT.commentLinkID.indent && D2(FTT.commentLinkID.indent).match(/^[\:\*\#]{1,5}$/) ) {
				FTT.PRM[i].indent = D2(FTT.commentLinkID.indent);
			}
			if ( FTT.commentLinkID.page ) {
				FTT.PRM[i].pageTitle = D2(FTT.commentLinkID.page);
			}
			if ( FTT.commentLinkID.createonly ) {
				FTT.PRM[i].createonly = true;
			}
			if ( FTT.commentLinkID.editintro ) {
				FTT.PRM[i].editintro = D2(FTT.commentLinkID.editintro);
			}
			if ( FTT.commentLinkID.preload ) {
				FTT.PRM[i].preload = D2(FTT.commentLinkID.preload);
			}
			if ( FTT.commentLinkID.preloadparams ) {
				for (FTT.preloadParamsInt=0;FTT.preloadParamsInt<D2(FTT.commentLinkID.preloadparams).split(';').length;FTT.preloadParamsInt++){
					FTT.PRM[i].preloadparams.push(D2(FTT.commentLinkID.preloadparams).split(';')[FTT.preloadParamsInt]);
				}
			}
			if ( FTT.commentLinkID.summary ) {
				FTT.PRM[i].summary = D2(FTT.commentLinkID.summary);
			}
			if ( FTT.commentLinkID.create ) {
				FTT.PRM[i].allowcreate = true;
			}
			if ( FTT.commentLinkID.purge ) {
				FTT.PRM[i].forcepurge = true;
			}
			if ( FTT.commentLinkID.multiline ) {
				FTT.PRM[i].multiline = true;
			}
			FTT.processElementArray[i].style.display = '';
			FTT.processElementArray[i].querySelectorAll('a')[0].dataset.FTTprocessint = i; //when the link is clicked we retrieve the PRM int from "this" (the clicked element)
			FTT.processElementArray[i].querySelectorAll('a')[0].addEventListener('click',function(event){event.preventDefault();event.stopPropagation();FTT[FTT.keyOpenReplyForm](FTT.PRM[this.dataset.FTTprocessint]);});
			if ( M2('withgadget') == 'FTT' && M2('FTTform') == FTT.processElementArray[i].innerText ) {
				FTT.automaticallyOpenFormParams = $.extend( true, {}, FTT.PRM[i] ); // copy as = only creates a shortcut
				FTT.testAutoPreloadparams = FTT.testValidJSON(D2(FTT.automaticallyOpenFormParams.preloadparams).replace(/\&quot\;/g, '"'));
				if ( FTT.testAutoPreloadparams ) {
					FTT.automaticallyOpenFormParams.preloadparams = FTT.testAutoPreloadparams;
				}
			}
		}
	} else if ( FTT.elementClassList.includes('firstHeading') ) {
		if ( FTT.settings.editFullSection && FTT.processElementArray[i].id == 'firstHeading' && ! $('#FTTEditLede')[0] && M1('wgIsProbablyEditable') == true ) {
//			FTT.debug('adding lede edit link');
			FTT.PRMLede = {'int':i,'id':'editLede-' + M1('wgPageName'),'type':'heading','subtype':'edit','section':0,'sectionseq':0,'sectionTitle':'','pageTitle': M1('wgPageName')};
			FTT.ledeEditLinkSpan = document.createElement('span');
			FTT.ledeEditLinkSpan.classList = 'FTTReplyLink FTTSVGEditIcon FTTSVG mw-editsection-like';
			FTT.ledeEditLinkSpan.id = 'FTTEditLede';
			FTT.ledeEditLink = document.createElement('a');
			if ( M1('wgPageContentModel') != 'wikitext' || M1('wgCurRevisionId') == 0 ) {
				FTT.PRMLede = FTT.PRMFP;
			}
			FTT.ledeEditLink.title = FTT.msgs.editLede;
			FTT.ledeEditLink.onclick = function(){FTT.openReplyForm(FTT.PRMLede);};
			FTT.ledeEditLink.append(FTT.ledeEditLinkSpan);
			FTT.ledeEditLinkSpanSR = document.createElement('span');
			FTT.ledeEditLinkSpanSR.classList = 'FTTScreenReaderLabel';
			FTT.ledeEditLinkSpanSR.dataset.content = FTT.msgs.editLede;
			FTT.ledeEditLinkSpan.append(FTT.ledeEditLinkSpanSR);
			FTT.processElementArray[i].append(FTT.ledeEditLink);
		}
	} else {
//		FTT.debug('another element type, checking for legacy signature');
		for (FTT.legacyElementInt=(FTT.processElementArray[i].childNodes.length -1);FTT.legacyElementInt>-1;FTT.legacyElementInt = FTT.legacyElementInt -1){
//			FTT.debug('checking processElementArray #' + i + ' childnode #' + FTT.legacyElementInt);
			if ( FTT.elementClassList.includes('localcomments') ) { //Gadget-CommentsInLocalTime HACK
				if ( FTT.processElementArray[i].parentElement.classList.contains('FTTCmt') ) {//izza locator
					return;
				}
				FTT.legacyLOCOMatch = FTT.processElementArray[i].title;
			} else {
				delete FTT.legacyLOCOMatch;
			}
			FTT.origStampNodeContent = FTT.processElementArray[i].childNodes[FTT.legacyElementInt].data;
			if ( FTT.legacyLOCOMatch || ( FTT.processElementArray[i].childNodes[FTT.legacyElementInt].nodeType == 3 && FTT.origStampNodeContent.length < 50 && FTT.origStampNodeContent.length > 12 ) ) {
				if ( FTT.legacyLOCOMatch ) { //Gadget-CommentsInLocalTime HACK
//					FTT.debug('a legacyLOCOMatch');
					FTT.legacyElementSigMatch = FTT.legacyLOCOMatch.match(FTT.signDateRegExpLocalMonths); //don't need FTT.cleanTimestamp here as LOCO is enwiki-specific
					FTT.origStampNodeContent = FTT.processElementArray[i].title;
				} else {
//					FTT.debug('textnode with the right length to maybe be a timestamp');
					FTT.legacyElementSigMatch = FTT.cleanTimestamp(FTT.origStampNodeContent).match(FTT.signDateRegExpLocalMonths);
				}
				if ( FTT.legacyElementSigMatch ) {
//					FTT.debug('looks like a timestamp to me');
					FTT.legacyTimestampNode = FTT.processElementArray[i].childNodes[FTT.legacyElementInt];
					if ( FTT.legacyLOCOMatch ) { //Gadget-CommentsInLocalTime HACK
						FTT.legacyUsername = D2(FTT.getLegacyUserForTimestamp(FTT.processElementArray[i]));
					} else {
						FTT.legacyUsername = D2(FTT.getLegacyUserForTimestamp(FTT.legacyTimestampNode));
					}
					if ( FTT.legacyUsername != false ) {
						FTT.legacyUsername = D2(FTT.legacyUsername);
						FTT.legacyID = FTT.legacyUsername + ':' + FTT.legacyElementSigMatch[0] + ':' + i;
						FTT.PRM[i] = {
							'int':i,
							'type':'comment',
							'subtype':'legacy',
							'id':FTT.legacyID,
							'pageTitleInt':FTT.pageTitleToEditInt,
							'origReplyTo':FTT.legacyUsername,
							'origTimestamp':FTT.legacyElementSigMatch[0].trim(),
							'origTimestampTextNode':FTT.origStampNodeContent
						};
						if ( ! FTT.firstReplyInSection ) {
							FTT.firstReplyInSection = $.extend( true, {}, FTT.PRM[i] );
							FTT.firstReplyInSection.isFirst = true;
						}
//						FTT.legacyCommentCount++;//FTT.debug
						if ( FTT.commentersInSection.indexOf(FTT.legacyUsername.replace(/_/g,' ')) == -1 ) {
							FTT.commentersInSection.push(FTT.legacyUsername.replace(/_/g,' '));
						}
						FTT.commentsInSection++;
						FTT.addReplyLinkTo(FTT.PRM[i],FTT.legacyTimestampNode,FTT.legacyElementSigMatch);
						break;
					}
				}
			}
		}
	} //end check which type the element is
};
FTT.finishedAddingLinks = false;
FTT.killSwitchTriggered = false;
FTT.userNameFromLinkFRegExp = new RegExp('([\/\=])(' + FTT.userNSLinkRegExpPart + '|' + FTT.escapeRegExp(FTT.B1.Contributions) + '\/|Special:Contributions\/)([^\/"\#\&\?]+)($|[\/"\#\&\?])');
FTT.orphanedTimestamps = [];
FTT.getLegacyUserForTimestamp = function(textNode) {
	FTT.checkPreSigNode = textNode;
	delete FTT.legacyUser;
	for (FTT.preSigInt=0;FTT.preSigInt < 5;FTT.preSigInt++) {
//		FTT.debug('getLegacyUserForTimestamp: checkPreSigNode '+FTT.preSigInt);
		if ( textNode.parentElement.classList.contains('FTTCmt') ) {
//			FTT.debug('getLegacyUserForTimestamp: the parent of "' + textNode.data + '" is a locator, let the locator code handle it');
			return false;
		}
		if ( FTT.checkPreSigNode.previousElementSibling ) {
//			FTT.debug('getLegacyUserForTimestamp: checkPreSigNode, found previousElementSibling');
			FTT.checkPreSigNode = FTT.checkPreSigNode.previousElementSibling;
			if ( FTT.checkPreSigNode.nodeName == 'A' && FTT.checkPreSigNode.attributes.href ) {
				FTT.legacyUser = FTT.checkPreSigNode.attributes.href.nodeValue.match(FTT.userNameFromLinkFRegExp);
			} else if ( M1('wgCanonicalNamespace') == 'User_talk' && FTT.checkPreSigNode.classList && FTT.checkPreSigNode.classList.contains('mw-selflink') ) {
//				FTT.debug('getLegacyUserForTimestamp: comment by a user on their own talk page');
				return D1(M1('wgRelevantUserName').replace(/ /g,'_')); //to be consistent with URLs, replace spaces with underscores
			} else if ( FTT.checkPreSigNode.tagName ) {
				FTT.legacyUser = FTT.checkPreSigNode.innerHTML.match(FTT.userNameFromLinkFRegExp);
			}
			if ( FTT.legacyUser && FTT.legacyUser[3] ) {
				return FTT.legacyUser[3];
			}
		} else {
			break;
		}
	}
	FTT.checkPostSigNode = textNode;
	for (FTT.postSigInt=0;FTT.postSigInt < 3;FTT.postSigInt++) {
//		FTT.debug('getLegacyUserForTimestamp: checkPostSigNode '+FTT.postSigInt);
		if ( FTT.checkPostSigNode.nextElementSibling ) {
			FTT.checkPostSigNode = FTT.checkPostSigNode.nextElementSibling;
			if ( FTT.checkPostSigNode.nodeName == 'A' && FTT.checkPostSigNode.attributes.href ) {
				FTT.legacyUser = FTT.checkPostSigNode.attributes.href.nodeValue.match(FTT.userNameFromLinkFRegExp);
			} else if ( ['DD','DL','P'].indexOf(FTT.checkPostSigNode.nodeName) != -1 ) {
//				FTT.debug('getLegacyUserForTimestamp: next comment or paragraph, guess this timestamp is an orphan');
				break;
			} else if ( FTT.checkPostSigNode.tagName ) {
				FTT.legacyUser = FTT.checkPostSigNode.innerHTML.match(FTT.userNameFromLinkFRegExp);
			}
			if ( FTT.legacyUser && FTT.legacyUser[3] ) {
				return FTT.legacyUser[3];
			}
		} else {
			break;
		}
	}
	FTT.PaineNode = textNode.parentElement.previousElementSibling; //I suspect this workaround/fallback is now used for everything as DT links timestamps now.
	if ( FTT.PaineNode && ['SUB','SUP','SMALL'].indexOf(FTT.PaineNode.nodeName) != -1 ) { //signature wrapped in some tag
		FTT.PaineNode = FTT.PaineNode.lastElementChild;
	}
	if ( FTT.PaineNode ) {
		for (FTT.PaineWorkaround=0;FTT.PaineWorkaround<4;FTT.PaineWorkaround++) {
			if ( FTT.PaineNode && FTT.PaineNode.nodeName == 'A' && FTT.PaineNode.href ) { //Paine Ellsworth on enwiki has "[[User talk:Paine Ellsworth|<sup>put'r&nbsp;there</sup>]]&nbsp;<small>06:27, 17 April 2022 (UTC)</small>" for a signature, encapsulating ONLY the timestamp in a small tag. Doesn't seem like something you can set in your prefs?
				FTT.legacyUser = FTT.PaineNode.attributes.href.nodeValue.match(FTT.userNameFromLinkFRegExp);
				if ( FTT.legacyUser && FTT.legacyUser[3] ) {
					return FTT.legacyUser[3];
				}
			}
			FTT.PaineNode = FTT.PaineNode.previousElementSibling;
			if ( ! FTT.PaineNode ) {
				return '';
			}
		}
	}
//	FTT.debug('getLegacyUserForTimestamp: orphaned timestamp found:');
//	FTT.debug(textNode);
//	FTT.orphanedTimestamps.push(textNode);//FTT.debug
	return '';
};
FTT.scrollToComment = function(FTTScrToUsr,FTTScrToTime) {
	if ( ! FTT.PRM ) {
		FTT.addScrewedLink('cant scroll','No PRM, unable to scroll to any comment.');
		return;
	}
	FTTScrToUsr = FTTScrToUsr.replace(/_/g,' ');
	for(FTT.scrollToInt=0;FTT.scrollToInt<Object.keys(FTT.PRM).length;FTT.scrollToInt++){
		FTT.scrollTestElement = FTT.PRM[Object.keys(FTT.PRM)[FTT.scrollToInt]];
		if ( FTT.scrollTestElement.origReplyTo && FTT.scrollTestElement.origReplyTo.replace(/_/g,' ') == FTTScrToUsr ) {
//			FTT.debug('user matches GET param');
			if ( typeof FTT.scrollTestElement.origTimestamp == 'number' || FTT.scrollTestElement.origTimestamp.match(/[0-9]{13,14}/) ) {
				FTT.scrollTestDate = FTT.scrollTestElement.origTimestamp;
			} else {
				FTT.getMonthNames(); //if the very first page you load (before having basicmsgs in your localStorage) or your localStorage is broken AND you are on a wiki that uses genitive month names (like uk/el/ru) AND the url links to a comment this way, it'll fail. I think
				FTT.scrollTestDate = FTT.sigDateToMachineReadable(FTT.scrollTestElement.origTimestamp);
			}
//			FTT.debug('test scrollTestDate '+FTT.scrollTestDate);
			if ( FTT.scrollTestDate == Number(FTTScrToTime.replace(/_.*$/,'')) ) { //timestamp has _n appened for uniqueness, remove that for comparison
//				FTT.debug('found comment to scroll to');
				FTT.scrollToFound = true;
				FTT.testFTTScrToUsr = FTT.testValidJSON(mw.user.options.get('userjs-FTTTackOnEchoGlobal'));
				if ( FTT.testFTTScrToUsr && FTT.testFTTScrToUsr[FTTScrToTime] ) {
//					FTT.debug('remove entry with key '+FTTScrToTime+' from userjs-FTTTackOnEchoGlobal');
					FTT.tackOnEchoClear(FTTScrToTime);
				}
				break;
			}
		}
	}
	if ( FTT.scrollToFound ) {
		FTT.scrollCmtInt = Object.keys(FTT.PRM)[FTT.scrollToInt];
		try {FTT.processElementArray[FTT.PRM[FTT.scrollCmtInt].pageTitleInt].parentElement.querySelectorAll('.FTTSVGChevronIconRot')[0].click();} catch (e) {} //if this section is collapsed, uncollapse it
		FTT.cmtScrollEl = FTT.processElementArray[Object.keys(FTT.PRM)[FTT.scrollToInt]];
		FTT.cmtScrollEl.scrollIntoView(FTT.smoothScroll);
		FTT.HLCmt(FTT.cmtScrollEl,undefined,undefined,4000,1);
	}
};
if ( FTT.settings.hideAdvFE && $('.minerva-talk-add-button')[0] && ( ! $('.skin-minerva--talk-simplified').length || FTT.settings.collapsible ) ) { //stop advanced mobile mode topicview mode which interferes with FTT, also breaks adv mobile collapsible sections which might not interfere but what choice do I have, I can't remove only the event listener that goes "window.location.hash = '#' + encodedHeadlineId;", can I? FTT can collapse the sections if you want..
	$('H1,H2,H3,H4,H5,H6').off('click');
} else if ( FTT.isOtherPage && FTT.settings.collapArticle && FTT.isMobile ) {
	$('H1,H2,H3,H4,H5,H6').off('click');
}
FTT.unhideMFCollapsible = function(int) {
	for (int=0;int<$('#mw-content-text section').length;int++) {
		$('#mw-content-text section')[int].hidden = false;
	}
};
if ( FTT.isMF && FTT.settings.collapsible ) {
	FTT.unhideMFCollapsible();
}
FTT.addnSecBottomLink = function() {
	if ( FTT.isDiscussionPageWithCmts && FTT.settings.nSecBottomLink && FTT.viewingPage ) {
		FTT.newSectionBottomLinkDiv = document.createElement('div');
		FTT.newSectionBottomLinkDiv.id = 'FTTnSecBottomDiv';
		FTT.newSectionBottomLinkDiv.style = 'text-align:center';
		if ( FTT.settings.replySecLink ) {
			FTT.newSectionBottomLinkDiv.style = 'text-align:center;margin-top:1em'; //the extra reply-to-section-starter link (which uses a negative margin to avoid page jumping) can overlap with the bottom new section link
		}
		FTT.newSectionBottomLink = document.createElement('a');
		FTT.newSectionBottomLink.id = 'FTTnSecBottom';
		FTT.newSectionBottomLink.style = 'text-align:center';
		FTT.newSectionBottomLink.innerText = FTT.B1['tooltip-ca-addsection'];
		FTT.newSectionBottomLink.onclick = function() {$('#FTTnSecBottom').addClass('FTTNoDisplay');FTT.openReplyForm(FTT.PRMnSec);};
		FTT.newSectionBottomLinkDiv.append(FTT.newSectionBottomLink);
		$('#FTTnSecBottomDiv').remove(); //so reparsing etc. never results in more than one link
		$('#mw-content-text').append(FTT.newSectionBottomLinkDiv);
	}
};
FTT.autonum = function(int,hash) {
	if ( $('#toc')[0] ) {
		FTT.autonumArr = Array.from($('#toc a'));
		for (int=0;int<FTT.autonumArr.length;int++) {
			if ( FTT.autonumArr[int].attributes.href.nodeValue ) {
				try{
					hash = FTT.autonumArr[int].attributes.href.nodeValue;
					FTT.autonumEl = document.getElementById(hash.slice(1));
					FTT.autonumNewEl = document.createElement('a');
					FTT.autonumNewEl.href = hash;
					FTT.autonumNewEl.classList.add('FTTAutoNum');
					if ( FTT.settings.autonumPlain ) {
						FTT.autonumNewEl.classList.add('FTTPlain');
					}
					FTT.autonumNewEl.innerText = FTT.autonumArr[int].querySelectorAll('.tocnumber')[0].innerText;
					FTT.autonumEl.prepend(FTT.autonumNewEl);
				} catch (e) {}
			}
		}
		if ( FTT.settings.autonumCopy ) {
			$('.FTTAutoNum').on('click',function(event){
				event.preventDefault();event.stopPropagation();
				FTT.sectionLink = new mw.Uri(mw.config.get('wgArticlePath').replace('$1',mw.config.get('wgPageName')));
				FTT.sectionLink.fragment = event.delegateTarget.attributes.href.value.slice(1).toString();
				FTT.copyToClipBoard(0,'text',FTT.sectionLink.toString());
			});
		}
		if ( FTT.settings.autonumScroll ) {
			$('.FTTAutoNum').on('click',function(event){
				event.preventDefault();event.stopPropagation();
				if ( $('#toctogglecheckbox')[0] && $('#toctogglecheckbox')[0].checked ) {
					$('#toctogglecheckbox').click(); //uncollapse ToC
				}
				FTT.magicScroll($('#toc a[href="'+CSS.escape(event.delegateTarget.attributes.href.value)+'"]')[0]);
			});
		}
	}
};
FTT.loadLinksEnd = function() {
	if ( FTT.killSwitchTriggeredThisTime == false ) {
		FTT.finishedAddingLinks = true;
	}
	FTT.loadTimeAddLinksEnd = new Date().getTime();
	FTT.loadTimeAddLinks = FTT.loadTimeAddLinksEnd - FTT.loadTimeAddLinksStart;
};
FTT.searchNodeContentsLoop = function(mode,nodeInt,splitH1Div) {
	if ( M1('wgPageContentModel')=='flow-board' ) {
//		FTT.debug('searchNodeContentsLoop: flow-board PageContentModel, not adding links');
		return;
	}
	if ( ( FTT.settings.stalkAddSubLinks || FTT.settings.autoCollapse || FTT.settings.markNewCmts || FTT.settings.markNewCmtsSubbed ) && ( FTT.isDiscussionPage || $('.FTTCmt')[0] ) && M1('wgArticleId') != 0 ) { //if maxLVCached is a number notificationtimestamp data from the watchlist was already obtained and written which includes the lastviewed update for the current page
		if ( mode == 'reparse' ) {
//			FTT.debug('searchNodeContentsLoop: use timestamp from previous time searchNodeContentsLoop ran');
			FTT.LVTimestamp = FTT.LVLastParse;
		} else {
//			FTT.debug('searchNodeContentsLoop: get+update lastviewed for this page for autocollapse and subscription purposes');
			FTT.LVTimestamp = FTT.queryLV(true);
		}
		FTT.LVLastParse = new Date().getTime();
	}
	delete FTT.maxLVCached; //so the timestamp gets updated with parsepageinplace
	FTT.setDSTSetting();
	FTT.getMonthNames(); //get month names for legacy timestamp detection
//	FTT.debug('searchNodeContentsLoop: FTT.monthNames');
//	FTT.debug(FTT.monthNames);
	if ( ! FTT.foundLOCO && ( mw.user.options.get('gadget-CommentsInLocalTime') || M2('withJS') == 'MediaWiki:Gadget-CommentsInLocalTime.js' ) ) {//Gadget-CommentsInLocalTime HACK
		$(document).ready(function() {
			window.commentsInLocalTimeWasRun = true; //If you're already done this changes nothing. If you're still processing, I count on you being done one second from now. If you hadn't started yet, I hereby declare you're done.
			var LOCODELAY = setInterval(function () {
				clearInterval(LOCODELAY);
				FTT.foundLOCO = true;
//				FTT.debug('searchNodeContentsLoop: run searchNodeContentsLoop (LOCO hack)');
				FTT.searchNodeContentsLoop();
			},1000); //commentsInLocalTimeWasRun doesn't seem to indicate LOCO is actually DONE, just that it started, so we'll just pray it can do its thing in a second
			return;
		});
	}
	FTT.applyModules('beforeLinkLoad');
	if ( typeof FTT.legacyCommentCount != 'undefined' ) {
//		FTT.debug('searchNodeContentsLoop: already added all links');
		return;
	}
	if ( M1('skin') == 'minerva' && ( ! FTT.settings.loadMinervaD && ! document.location.href.match(/(\.|\/\/)m\./) ) || ( ! FTT.settings.loadMinervaM && document.location.href.match(/(\.|\/\/)m\./) ) ) {
//		FTT.debug('searchNodeContentsLoop: you disabled link adding on Minerva so I won\'t do it');
		return;
	}
	FTT.ninjaLoaded = true; //so ninjaloader won't try to run searchNodeContentsLoop again
	FTT.loadTimeAddLinksStart = new Date().getTime();
	FTT.PRM = {};
	FTT.keyPRM = Object.keys(FTT)[Object.values(FTT).indexOf(FTT.PRM)];
	delete FTT.automaticallyOpenFormParams;
//	FTT.debug('searchNodeContentsLoop: add reply/permalink/subsection links to comments and sections and replace dates');
	if ( FTT.isDiscussionPage || $('.commentbox')[0] || $('.FTT-comment-link')[0] || ( ( FTT.settings.editFullSection || FTT.settings.dateLinksIconSection ) && $('.mw-editsection')[0] ) || ( FTT.settings.enableOnDiffOldId && M1('wgAction') == 'view' && M1('wgCurRevisionId') > 0 && M1('wgRevisionId') > 0 && M1('wgCurRevisionId') != M1('wgRevisionId') ) ) {
		FTT.processElementArrayElements = '#mw-content-text .commentbox,#mw-content-text .FTT-comment-link,#firstHeading,'+FTT.mwEditSec+',#mw-content-text .mw-ui-button,#mw-content-text .oo-ui-buttonElement-button'; //.mw-ui-button catches some custom new section links like on https://de.wikipedia.org/wiki/Wikipedia:Caf%C3%A9 and .oo-ui-buttonElement-button catches default "Start a discussion" buttons on nonexistent talk pages
		if ( FTT.settings.methodLocator ) {
			FTT.processElementArrayElements = FTT.processElementArrayElements + ',#mw-content-text .FTTCmt'; // FTT locators
		}
		if ( FTT.settings.methodLegacy && ( FTT.isDiscussionPage || $('.FTTCmt')[0] ) ) {
			FTT.processElementArrayElements = FTT.processElementArrayElements + ',#mw-content-text dd,#mw-content-text p,#mw-content-text li,#mw-content-text dd small,#mw-content-text p small,#mw-content-text li small,#mw-content-text dd i,#mw-content-text p i,#mw-content-text li i,#mw-content-text dd s,#mw-content-text p s,#mw-content-text li s,#mw-content-text dd del,#mw-content-text p del,#mw-content-text li del,#mw-content-text .autosigned';
		}
		if ( M1('wgCanonicalNamespace') == 'User_talk' ) {
//			FTT.debug('searchNodeContentsLoop: user talk namespace, add possible barnstar elements to list of elements to search');
			FTT.processElementArrayElements = FTT.processElementArrayElements + ',#mw-content-text td';
		}
		if ( FTT.settings.extendedSigDetect ) { //DiscussionTools. Sooooo many spans. Adds +/- 15% to detection time, increases detection rate on the Wiktionary RfD test page from 1170 to 1172 with two comments by Dentonius (19:19, 4 October 2020 (UTC) and 19:25, 4 October 2020 (UTC)) that were MANUALLY ALTERED to be encapsulated in <s><span style="color:gray">.
			FTT.processElementArrayElements = FTT.processElementArrayElements + ',#mw-content-text dd span,#mw-content-text p span,#mw-content-text li span';
		}
		if ( $('#mw-content-text .localcomments:eq(0)')[0] && ! FTT.settings.extendedSigDetect ) {//Gadget-CommentsInLocalTime HACK
			FTT.processElementArrayElements = FTT.processElementArrayElements + ',#mw-content-text .localcomments';
		}
		//detect https://meta.wikimedia.org/wiki/User:ESanders_(WMF)/commentlinks.js.
		try{FTT.detectCommentLinks = $('.ext-discussiontools-init-replylink-buttons')[0].previousElementSibling.href.match(new RegExp(mw.util.escapeRegExp($('link[rel=canonical]').attr('href')||'')+'#c\-'));} catch (e) {}
		if ( FTT.detectCommentLinks ) {
			FTT.processElementArrayElements = FTT.processElementArrayElements + ',#mw-content-text *:not(.FTTCmt)>a'; // Will definitely hurt loading time because links added by commentlinks.js can't be identified by a class or anything, so we have to scan ALL links and also exclude locators to avoid double icons
		}
	}
	FTT.processElementArray = Array.from($(FTT.processElementArrayElements));
	FTT.DTMillStone = '*:not(.mw-heading)>.mw-editsection,#mw-content-text .ext-discussiontools-init-replybutton .oo-ui-buttonElement-button,.ext-discussiontools-init-section-subscribeButton>a';
	if ( FTT.settings.extendedSigDetect ) {
		FTT.DTMillStone = FTT.DTMillStone+',.ext-discussiontools-init-replylink-buttons,.ext-discussiontools-init-replylink-buttons span';
	}
	FTT.DTMillStoneArr = Array.from($(FTT.DTMillStone));
	FTT.DTMillLength = FTT.DTMillStoneArr.length;
	for ( FTT.DTMillInt=0;FTT.DTMillInt<FTT.DTMillLength;FTT.DTMillInt++ ) {
		FTT.DTMillIndex = FTT.processElementArray.indexOf(FTT.DTMillStoneArr[FTT.DTMillInt]);
		if ( FTT.DTMillIndex != -1 ) {
			delete FTT.processElementArray[FTT.DTMillIndex];
		}
	}
	FTT.processElArrL = FTT.processElementArray.length;
	FTT.processElArrLMin = FTT.processElementArray.length -1;
	FTT.pageTitleToEditInt = -1;
	FTT.pageTitleToEdit = M1('wgPageName'); //in case the first comment comes before any section edit link we define the page title
	FTT.sectionTitle = ""; //same for the ==Section title==
	FTT.legacyCommentCount = 0;
	FTT.killSwitchInt = 0;
	FTT.killSwitchTriggeredThisTime = false;
	FTT.sectionHeaderLinksAdded = false;
	FTT.addedReplyLinks = [];
	FTT.sectionsFound = [];
	FTT.PRMEdit = {};
	FTT.keyPRMEdit = Object.keys(FTT)[Object.values(FTT).indexOf(FTT.PRMEdit)];
	FTT.PRMHeadingEdit = {};
	FTT.headerEditLinks = {};
	FTT.participantsHTML = {};
	FTT.participantsWikitext = {};
	FTT.participantsWikitextParsed = {};
	FTT.processLastEl = function() {
		FTT.commentersInSectionBySection[FTT.pageTitleToEditInt] = FTT.commentersInSection; //normally collected when the next section is encountered, but as this is the last element there won't be a next section
		if ( FTT.replyToSectionLinkNeeded && FTT.firstReplyInSection && FTT.commentersInSection.length > 3 ) {
			FTT.firstReplyInSectionLastElement = true;
			FTT.addReplyLinkTo(FTT.firstReplyInSection);
		}
		if ( FTT.settings.discussionActivity && FTT.commentersInSection.length > 0 ) {
			FTT.addDiscussionActivity(FTT.pageTitleToEditInt);
		}
	};
	if ( ! FTT.hidDTReply && FTT.settings.hideDT && mw.user.options.get('discussiontools-replytool') == 1 ) { //do not apply if already hidden in preferences
		mw.util.addCSS('.ext-discussiontools-init-replylink-buttons{display:none !important;}');
		FTT.hidDTReply = true;
	}
	if ( ! FTT.hidDTSub && FTT.settings.hideDTSub && FTT.settings.stalkAddSubLinks ) {
		mw.util.addCSS('.ext-discussiontools-init-section-subscribe,.ext-discussiontools-init-section-subscribeButton{display:none !important;}');
		FTT.hidDTSub = true;
	}
	for (nodeInt = 0; nodeInt < FTT.processElArrL; nodeInt++) {
//		FTT.debug('searchNodeContentsLoop: Process element #' + nodeInt + '/' + FTT.processElArrLMin + ' from processElementArray');
		FTT.isLastEl = ( nodeInt == FTT.processElArrLMin );
		FTT.isEl = (typeof FTT.processElementArray[nodeInt] == 'object');
		if ( ! FTT.isEl ) {
//			FTT.debug('searchNodeContentsLoop: skipping empty entry in processElementArray (performance)');
			if ( FTT.isLastEl ) { //last element, last section
				FTT.processLastEl();
			}
			continue;
		}
		FTT.searchNodeContents(nodeInt);
		if ( FTT.isLastEl ) {
			FTT.processLastEl();
		}
		if ( FTT.killSwitchTriggered == false && FTT.settings.killswitch ) { //if automatic loading gets killed, don't kill manual loading afterwards
			FTT.killSwitchInt++;
			if ( FTT.killSwitchInt == 50 ) { //only check for timeout every 50 elements so the check itself doesn't impact performance much
				FTT.killSwitchInt = 0;
				if ( FTT.loadTimeAddLinksStart < (new Date().getTime() - 8000) ) {
					console.log('FTT: KILL SWITCH TRIGGERED, loading took more than 8000ms, abort adding links.');
					FTT.killSwitchTriggered = true;
					FTT.killSwitchTriggeredThisTime = true;
					break;
				}
			}
		}
	} //end for
	FTT.isDiscussionPageWithCmts = ( FTT.isDiscussionPage && ($('.LegacyCmt')[0] || $('.FTTCmt')[0]) ); // .LegacyCmt is available now
	FTT.addRefListMarkers();
	if ( FTT.settings.editFullSection && FTT.settings.MFAdjeditFS && M1('skin') == 'minerva' ) {
		$('#page-actions-edit #ca-edit').removeClass('mw-ui-icon-wikimedia-edit-base20').addClass('FTTSVGEditIconM');
		$('#page-actions-edit #ca-edit').off();
		$('#page-actions-edit #ca-edit').on('click',function(event){event.preventDefault();});
		$('#page-actions-edit').on('click',function(event){event.stopPropagation();FTT.openReplyForm(FTT.PRMLede);});
		$('#FTTEditLede').addClass('FTTNoDisplay');
	}
	if ( FTT.detectCommentLinks ) { // m:User:ESanders_(WMF)/commentlinks.js workaround
		$('#mw-content-text .FTTCommentLinks').on('click',function(event){event.stopPropagation();});
	}
	if (
		( FTT.settings.stalkAddSubLinks || FTT.settings.stalkAutoSub ) && //if you have no features enables that use subscriptions, what's the point
		( M1('wgTitle') == 'FTTSubs' || M1('wgCanonicalSpecialPageName') == 'Watchlist' || //do run on watchlist and Special:FTTSubs even if last check was recent
			( typeof FTT.FTTSubInfo != 'undefined' && //FTT.FTTSubInfo is last check date
				(FTT.FTTSubInfo + (FTT.settings.stalkInterval * 60000) ) < new Date().getTime() //if it's more than x minutes ago, run
			) 
		) 
	) {
//		FTT.debug('run subscription check. if the check results in a new notification the notifications will be shown.');
		if ( M1('wgTitle') == 'FTTSubs' ) {
			$('#firstHeading')[0].innerText = FTT.msgs.subsHeader;
			$('head title')[0].innerText = FTT.msgs.subsHeader;
			$('#mw-content-text')[0].innerHTML = '';
		}
		FTT.stalkCheckSubscriptions(); //had to wait for isDiscussionPageWithCmts to become available. will also launch tackOnEchoIconHack
		FTT.stalkCheckSubscriptionsRan = true;
	} else if ( FTT.settings.stalkTackOnEcho ) {
//		FTT.debug('subscription check not running right now, load existing new message notification (if any)');
		FTT.tackOnEchoIconHack();
	}
//	FTT.resetDbg(true); //the load loop usually exceeds whatever limit is set (FTT.debug)
	FTT.addnSecBottomLink();
	if ( M1('wgCurRevisionId') == 0 || !$('#mw-content-text .mw-headline')[0] ) {
		$('body').addClass('FTTNoCollapsible');
		$('body').addClass('FTTNoHeadingLinks');
		FTT.loadLinksEnd();
		FTT.loadTimeSectionSplitting = new Date().getTime() - FTT.loadTimeAddLinksEnd;
		FTT.applyModules('afterLinkLoad');
		if ( FTT.automaticallyOpenFormParams ) {
			FTT.openReplyForm(FTT.automaticallyOpenFormParams);
		}
		return;
	}
	if ( FTT.settings.hideArchived ) {
		$('.archived .FTTCmtA,.boilerplate .FTTCmtA').addClass('FTTNoDisplay');
	}
	if ( FTT.settings.editFullSHide ) { //todo: test various skins/settings
		$('#mw-content-text .mw-editsection>.mw-editsection-bracket:first-child,#mw-content-text .mw-editsection>a:first-of-type,#mw-content-text .mw-editsection>.mw-editsection-bracket:nth-of-type(2)').addClass('FTTNoDisplayMWEd');
	}
	if ( FTT.settings.editFullSHref ) {
		$('.FTTHeadingLinks .FTTSecEdit').on('click',function(event){
			event.preventDefault();
		});
	}
	if ( FTT.settings.collapIcons ) {
		FTT.cmtSectCollapIcon = document.createElement('a');
		FTT.cmtSectCollapIcon.classList = 'FTTCollapMini';
		FTT.cmtSectCollapIcon.tabIndex = '0';
		FTT.cmtSectCollapIcon.innerHTML = '<span title="'+FTT.B1['collapsible-collapse']+'" class="FTTReplyLink FTTSVGChevronIconMini FTTSVGChevronIcon180 FTTSVG"><span class="FTTScreenReaderLabel" data-content="'+FTT.B1['collapsible-collapse']+'"></span></span>';
		$('#mw-content-text .FTTCommentLinks:not(.FTTFirstReply)').append(FTT.cmtSectCollapIcon);
		$('#mw-content-text .FTTCommentLinks a.FTTCollapMini').on('click',function(event) {
			FTT.testCollapEl = event.target;
			FTT.testCollapElInt = 0;
			while ( FTT.testCollapEl && FTT.testCollapElInt < 200 ) {
//				FTT.debug('test element:');
//				FTT.debug(FTT.testCollapEl);
				FTT.testCollapElInt++;
				if ( FTT.testCollapEl.classList.contains('FTTH2SectContainer') || FTT.testCollapEl.classList.contains('FTTH1SectContainer') ) {
//					FTT.debug('collapsing section after clicking collapse icon after comment');
					FTT.scrHeader = true;
					FTT.testCollapEl.querySelectorAll('.sectioncollapse.FTTSVGChevronIcon')[0].click();
					break;
				}
				FTT.testCollapEl = FTT.testCollapEl.parentElement;
			}
		});
	}
	if ( ( FTT.settings.scrollTop || FTT.settings.scrollPrev || FTT.settings.scrollNext ) && !$('.FTTHeadingLinks')[0] ) {
		FTT.headingLinks = document.createElement('span');
		FTT.headingLinks.className = 'FTTLinks FTTHeadingLinks';
		if ( M1('skin') != 'minerva' ) {
			$(FTT.mwEditSec).append(FTT.headingLinks);
		} else {
			$(FTT.mwEditSec).prepend(FTT.headingLinks);
		}
	}
	if ( $('div.section-links.noprint>a.internal')[0] && $('.FTTHeadingLinks')[0] ) { // w:de:Benutzer:Schnark/js/fliegelflagel workaround
		$(FTT.mwEditSec).addClass('FTTYesDisplay');
	}
	FTT.nextSecBtn = function(event,next) {
		if ( ! FTT.headingLinksArr ) {
			FTT.headingLinksArr = Array.from($('.FTTHeadingLinks'));
		}
		FTT.nextHeader = FTT.headingLinksArr.indexOf(event.target.parentElement.parentElement);
		if ( next ) {
			FTT.nextHeader++;
		} else {
			FTT.nextHeader--;
		}
		FTT.nextHeaderEl = FTT.headingLinksArr[FTT.nextHeader];
		while ( FTT.nextHeaderEl && ! FTT.nextHeaderEl.nodeName.match(/^H[1-6]/) ) {
			FTT.nextHeaderEl = FTT.nextHeaderEl.parentElement;
		}
		if ( FTT.nextHeaderEl ) {
			FTT.HLscroll(FTT.nextHeaderEl);
		}
	};
	FTT.scrollMargin = ' FTTscrollMargin';
	if ( FTT.settings.scrollTop && ( !FTT.isMobile || !FTT.settings.MFAdjscrollTop) ) {
		FTT.scrollTopIcon = document.createElement('a');
		FTT.scrollTopIcon.classList = 'FTTScrollTop'+FTT.scrollMargin;
		FTT.scrollMargin='';
		FTT.scrollTopIcon.tabIndex = '0';
		FTT.scrollTopIcon.innerHTML = '<span title="'+FTT.msgs.backToTop+'" class="FTTReplyLink FTTSVGChevronTopIcon FTTSVGChevronIcon180 FTTSVG"><span class="FTTScreenReaderLabel" data-content="'+FTT.msgs.backToTop+'"></span></span>';
		$(FTT.mwEditSec.replace(/mw\-editsection/g,'mw-editsection .FTTHeadingLinks')).append(FTT.scrollTopIcon);
		$('#mw-content-text a.FTTScrollTop').on('click',function(event) {
			$('html')[0].scrollIntoView(FTT.smoothScroll);
		});
	}
	if ( FTT.settings.scrollPrev && ( !FTT.isMobile || !FTT.settings.MFAdjscrollPrev)) {
		FTT.scrollPrevIcon = document.createElement('a');
		FTT.scrollPrevIcon.classList = 'FTTScrollPrev'+FTT.scrollMargin;
		FTT.scrollMargin='';
		FTT.scrollPrevIcon.tabIndex = '0';
		FTT.scrollPrevIcon.innerHTML = '<span title="'+FTT.msgs.scrollPrevHeader+'" class="FTTReplyLink FTTSVGChevronIconMini FTTSVGChevronIcon180 FTTSVG"><span class="FTTScreenReaderLabel" data-content="'+FTT.msgs.scrollPrevHeader+'"></span></span>';
		$(FTT.mwEditSec.replace(/mw\-editsection/g,'mw-editsection .FTTHeadingLinks')).append(FTT.scrollPrevIcon);
		try{$('.FTTScrollPrev')[0].style.visibility = 'hidden';} catch (event) {}
		$('#mw-content-text .FTTHeadingLinks a.FTTScrollPrev').on('click',function(event) {
		FTT.nextSecBtn(event);
		});
	}
	if ( FTT.settings.scrollNext && ( !FTT.isMobile || !FTT.settings.MFAdjscrollNext)) {
		FTT.scrollNextIcon = document.createElement('a');
		FTT.scrollNextIcon.classList = 'FTTScrollNext'+FTT.scrollMargin;
		FTT.scrollMargin='';
		FTT.scrollNextIcon.tabIndex = '0';
		FTT.scrollNextIcon.innerHTML = '<span title="'+FTT.msgs.scrollNextHeader+'" class="FTTReplyLink FTTSVGChevronIconMini FTTSVG"><span class="FTTScreenReaderLabel" data-content="'+FTT.msgs.scrollNextHeader+'"></span></span>';
		$(FTT.mwEditSec.replace(/mw\-editsection/g,'mw-editsection .FTTHeadingLinks')).append(FTT.scrollNextIcon);
		try{$('.FTTScrollNext')[$('.FTTScrollNext').length -1].style.visibility = 'hidden';} catch (event) {}
		$('#mw-content-text .FTTHeadingLinks a.FTTScrollNext').on('click',function(event) {
			FTT.nextSecBtn(event,1);
		});
	}
	if ( FTT.settings.hideArchivedAll ) {
		$('.archived .FTTReplyLink,.boilerplate .FTTReplyLink,.archived .FTTBracket,.boilerplate .FTTBracket').addClass('FTTNoDisplay');
	}
	$('.hide-legacy-cmts .LegacyCmt .FTTLinks').addClass('FTTNoDisplay'); //hide reply links for legacy signatures in .hide-legacy-cmts class. Useful for sectionless transclusion as such comments couldn't be replied to anyway.
	if ( FTT.settings.hideDTStats && mw.user.options.get('discussiontools-visualenhancements') ) {
//		FTT.debug('searchNodeContentsLoop: hiding DT\'s "Show discussion activity"');
		mw.util.addCSS('.ext-discussiontools-init-section-bar {display:none !important}');
		$('.ext-discussiontools-init-section').removeClass('ext-discussiontools-init-section');
	}
	FTT.scrollToNextNewCmt = function(cmtClass) {
		if ( $('#mw-content-text .' + cmtClass)[0] ) {
			FTT.HLCmt($('#mw-content-text .' + cmtClass)[0],undefined,undefined,5000,1);
			$('#mw-content-text .' + cmtClass)[0].classList.remove('FTTNewCmt','FTTNewCmtSubscribed');
			if ( $('#FTTNewCmtBtn .cmtCount')[0] ) {
				$('#FTTNewCmtBtn .cmtCount')[0].innerText = $('#mw-content-text .FTTNewCmt').length;
			}
			if ( $('#FTTNewCmtSubscribedBtn .cmtCount')[0] ) {
				$('#FTTNewCmtSubscribedBtn .cmtCount')[0].innerText = $('#mw-content-text .FTTNewCmtSubscribed').length;
			}
//		} else {//FTT.addScrewedLink('cant scroll cmt','No DOM object found, unable to scroll to next comment.'); //FTT.debug
		}
		FTT.checkClearCycleBtn();
	};
	FTT.checkClearCycleBtn = function() {
		if ( $('#FTTNewCmtBtn')[0] && $('#mw-content-text .FTTNewCmt').length == 0 ) {
			$('#FTTNewCmtBtn')[0].remove();
		}
		if ( $('#FTTNewCmtSubscribedBtn')[0] && $('#mw-content-text .FTTNewCmtSubscribed').length == 0 ) {
			$('#FTTNewCmtSubscribedBtn')[0].remove();
		}
		if ( $('.FTTCycleBtnsFixed')[0] && $('.FTTCycleBtnsFixed')[0].childNodes.length == 0 ) {
			$('.FTTCycleBtnsFixed')[0].remove();
		}
	};
	$('.FTTNewCmt').on('click',function(){this.classList.remove('FTTNewCmt','FTTNewCmtSubscribed');}); //"this" refers to the .FTTNewCmt that was clicked, marks it as read on click
	if ( ( FTT.settings.stalkAddCycleBtn || FTT.settings.stalkAddCycleBtnSubbed ) && $('.FTTNewCmt')[0] ) {
		FTT.stalkCycleBtns = document.createElement('div');
		FTT.stalkCycleBtns.classList.add('FTTCycleBtnsFixed');
		FTT.stalkCycleButtons = '';
		if ( FTT.settings.stalkAddCycleBtn && $('#mw-content-text .FTTNewCmt')[0] ) {
			FTT.stalkCycleButtons = '<div id="FTTNewCmtBtn" title="'+FTT.msgs.stalkAddCycleBtn+'"><div class="cmtCount FTTNewCmt" style="width:2.5em;text-align:center;padding:0.6em 0 0.6em 0">' + $('#mw-content-text .FTTNewCmt').length + '</div></div>';
		}
		if ( FTT.settings.stalkAddCycleBtnSubbed && $('#mw-content-text .FTTNewCmtSubscribed')[0] ) {
			FTT.stalkCycleButtons = FTT.stalkCycleButtons + '<div id="FTTNewCmtSubscribedBtn" title="'+FTT.msgs.stalkAddCycleBtnSubbed+'"><div class="cmtCount FTTNewCmtSubscribed" style="width:2.5em;text-align:center;padding:0.6em 0 0.6em 0">' + $('#mw-content-text .FTTNewCmtSubscribed').length + '</div></div>';
		}
		FTT.stalkCycleBtns.innerHTML = FTT.stalkCycleButtons;
		$('body')[0].prepend(FTT.stalkCycleBtns);
		$('#FTTNewCmtBtn').on('click',function(){FTT.scrollToNextNewCmt('FTTNewCmt');});
		$('#FTTNewCmtSubscribedBtn').on('click',function(){FTT.scrollToNextNewCmt('FTTNewCmtSubscribed');});
		if ( $('#FTTNewCmtBtn .cmtCount')[0] ) {
			$('.FTTNewCmt').on('click',function(){if($('#FTTNewCmtBtn .cmtCount')[0]){$('#FTTNewCmtBtn .cmtCount')[0].innerText = $('#mw-content-text .FTTNewCmt').length;FTT.checkClearCycleBtn();}});
		}
		if ( $('#FTTNewCmtSubscribedBtn .cmtCount')[0] ) {
			$('.FTTNewCmtSubscribed').on('click',function(){if($('#FTTNewCmtSubscribedBtn .cmtCount')[0]){$('#FTTNewCmtSubscribedBtn .cmtCount')[0].innerText = $('#mw-content-text .FTTNewCmtSubscribed').length;FTT.checkClearCycleBtn();}});
		}
		if ( FTT.settings.stalkMarkReadScroll ) {
			FTT.scrollReadActive = false;
			document.addEventListener('scroll',function(){
				if ( FTT.scrollReadActive || ! $('.FTTNewCmt')[0] ){//don't run a million times for a little scroll, only one concurrent instance of this function. (reading scrollReadActive is cheap) also don't run if there are no .FTTNewCmt left (more expensive but only evaluated at the start of the scroll)
					return;
				}
				FTT.scrollReadActive = true;
//				FTT.debug('checkClearCycleBtn: you scrolled');
				var DelayedScrollRead = setInterval(function () { //we don't want to mark comments in the viewport as read INSTANTLY if they zoom past
					clearInterval(DelayedScrollRead);
//					FTT.debug('checkClearCycleBtn: checking which (if any) .FTTNewCmt elements are in view');
					FTT.scrollReadInViewTop = $(window).scrollTop() + ( $(window).height() / 100 * 15 ); //adding 10% of the viewport height as a margin so elements must be in the center 80% to count as in view
					FTT.scrollReadInViewBot = $(window).scrollTop() + ( $(window).height() / 100 * 85 );
					if ( FTT.scrollReadInViewTop < ( FTT.scrollReadViewPortHeight / 100 * 15 ) ) { //you are (near) the top of the page. The top 15% is typically occupied by toolbars etc anyway, but if not somehow we'll treat any elements starting from the very top as being in view if you are scrolled less than 15% of the viewport from the top
						FTT.scrollReadInViewTop = 0;
					}
					for(FTT.scrollReadInt=0;FTT.scrollReadInt<$('#mw-content-text .FTTNewCmt').length;FTT.scrollReadInt++){
						FTT.scrollReadElementPos = $('#mw-content-text .FTTNewCmt:eq(' + FTT.scrollReadInt + ')').offset().top;
						if ( FTT.scrollReadElementPos > FTT.scrollReadInViewTop && FTT.scrollReadElementPos < FTT.scrollReadInViewBot ) {
//							FTT.debug('checkClearCycleBtn: #mw-content-text .FTTNewCmt #' + FTT.scrollReadInt + ' is in view, marking as read');
							$('#mw-content-text .FTTNewCmt')[FTT.scrollReadInt].classList.remove('FTTNewCmtSubscribed','FTTNewCmt');
							FTT.scrollReadInt = FTT.scrollReadInt -1;
						}
					}
					if ( $('#FTTNewCmtBtn .cmtCount')[0] ) {
						$('#FTTNewCmtBtn .cmtCount')[0].innerText = $('#mw-content-text .FTTNewCmt').length;
					}
					if ( $('#FTTNewCmtSubscribedBtn .cmtCount')[0] ) {
						$('#FTTNewCmtSubscribedBtn .cmtCount')[0].innerText = $('#mw-content-text .FTTNewCmtSubscribed').length;
					}
					FTT.checkClearCycleBtn();
					FTT.scrollReadActive = false;
				},3000);
			});
		}
	}
	FTT.loadLinksEnd();
	if ( FTT.settings.autonum ) { //add numbering to .mw-headline
		FTT.autonum();
	}
	if ( ( FTT.settings.collapArticle && ! FTT.isDiscussionPageWithCmts ) || ( ( FTT.settings.collapsible || FTT.settings.reverseSectionOrder ) && FTT.isDiscussionPageWithCmts ) ) { //this can ONLY be done after adding links
		//I'll just keep saying it: putting discussion activity inside the header, yet displaying it below it. Great job. Thanks DT. T314714
		//Discussion activity doesn't seem to render on mobile ever (?) so skip this for Minerva. God knows what'll happen when they enable that
		if ( mw.user.options.get('discussiontools-visualenhancements') && M1('skin') != 'minerva' && $('.ext-discussiontools-init-section-bar')[0] && ! $('.ext-discussiontools-init-section-bar')[0].classList.contains('FTTNoDisplay') ) {
			mw.util.addCSS('.ext-discussiontools-init-section-bar{margin-top:0 !important}');
			for ( FTT.DTCrapInt=0;FTT.DTCrapInt<$('.ext-discussiontools-init-section-bar').length;FTT.DTCrapInt++) {
				FTT.insertAfter($('.ext-discussiontools-init-section-bar')[FTT.DTCrapInt].parentElement,$('.ext-discussiontools-init-section-bar')[FTT.DTCrapInt]);
			}
		}
		FTT.sectionsAreCollapsed = true;
		FTT.collapsingArticle = ( FTT.settings.collapArticle && FTT.viewingArticle );
		if ( FTT.isMobile && ( ( FTT.collapsingArticle && FTT.settings.collapArticle ) || FTT.settings.collapsible ) ) {
			mw.util.addCSS('.skin-minerva .section-heading.collapsible-heading .mw-ui-icon-mf-expand{display:none}'); //Minerva on mobile also collapses sections. confusing to have two chevrons. As the collapsing feature of FTT works on all skins, in the interest of consistency, we hide Minerva's chevrons.
			$('.skin-minerva #mw-content-text .collapsible-block').removeClass('collapsible-block'); //also, stop interfering please
		}
		delete FTT.hashElExist;
		try {if(window.location.hash!=''){FTT.hashElExist = document.getElementById(D2(window.location.hash.slice(1)));}} catch (e) {}
		FTT.splitHTMLSections('1','#mw-content-text .mw-parser-output:eq(0)');
		if ( $('.FTTH1SectionsContainer')[0] ) {
			for (splitH1Div = 0; splitH1Div < $('.FTTH1SectionsContainer').children().length; splitH1Div++) {
				FTT.splitHTMLSections('2','#' + $('.FTTH1SectionsContainer').children()[splitH1Div].id,splitH1Div);
			}
		} else {
			FTT.splitHTMLSections('2','#mw-content-text .mw-parser-output:eq(0)');
		}
		if ( FTT.settings.collapsible && $('.FTFirstReplyLast')[0] && $('.FTTH2SectContainer,FTTH1SectContainer')[0] ) {
//			FTT.debug('move last extra speech bubble for section starter');
			$('.FTTH2SectContainer,FTTH1SectContainer')[$('.FTTH2SectContainer,FTTH1SectContainer').length -1].append($('.FTFirstReplyLast')[0]);
		}
		FTT.reverseChildNodes = function(element){
			for(FTT.reverseChildNodesInt=0;FTT.reverseChildNodesInt<element.childNodes.length;FTT.reverseChildNodesInt++){
				element.insertBefore(element.childNodes[element.childNodes.length -1],element.childNodes[FTT.reverseChildNodesInt]);
			}
		};
		if ( FTT.settings.reverseSectionOrder && ! FTT.collapsingArticle ) {
//			FTT.debug('searchNodeContentsLoop: reversing the TOC');
			if ( $('#toc ul .toclevel-1')[0] ) {
				FTT.reverseChildNodes($('#toc ul')[0]);
				if ( $('.FTTH1SectionsContainer')[0] && $('.FTTH2SectionsContainer')[0] ) {
					for(FTT.reverseTOCLevel2Int=0;FTT.reverseTOCLevel2Int<$('#toc .toclevel-1 ul').length;FTT.reverseTOCLevel2Int++){
						if ($('#toc .toclevel-1 ul')[FTT.reverseTOCLevel2Int].childNodes.length){//skip toclevel-1 elements without childNodes to sort
							FTT.reverseChildNodes($('#toc .toclevel-1 ul')[FTT.reverseTOCLevel2Int]);
						}
					}
				}
			}
		}
		if ( ! FTT.hashElExist && FTT.collapsingArticle && ( (!FTT.isMobile && FTT.settings.collapArticleDefault) || (FTT.isMobile && FTT.settings.collapArticleDefaultMF) ) && ( ! window.location.hash || ! document.getElementById(window.location.hash.slice(1)) ) ) {
			FTT.toggledAllSections = true;
			FTT.toggleAllSections();
		}
		$('body').addClass('FTTCollapsible');
	} else {
		$('body').addClass('FTTNoCollapsible');
	}
	if ( M1('skin') == 'minerva' || FTT.settings.collapsible || FTT.settings.autoCollapse || FTT.settings.collapArticle ) { //Links added to sections by FTT cause sections to collapse/expand. MediaWiki uses stopPropagation on the edit button to stop the collapsing there but scripts that add links to H1/H2 are just fucked so here's another fun hack.
		$('.mw-editsection').on('click', function(event){event.stopPropagation();});
	}
	if ( ! FTT.settings.hideDT && ( FTT.settings.collapsible || FTT.settings.reverseSectionOrder ) && mw.user.options.get('discussiontools-visualenhancements') ) {
//		FTT.debug('DT started cutting itself, apply band-aid to restore lines');
		$('.FTTH2SectContainer,.FTTH1SectContainer').prepend('<span></span>'); //make headers no longer first-child. stupid solution, but CSS rules can't be removed
	}
	if ( FTT.settings.floatingToC ) {
		FTT.floatTheTOC();
	} else if ( FTT.settings.hideToC && $('#toc')[0] ) {
		$('#toc').addClass('FTTNoDisplay');
	}
	if ( ! FTT.collapsingArticle && FTT.isDiscussionPageWithCmts && FTT.settings.autoCollapse && ! FTT.toggledAllSections && ! FTT.hashElExist && ! M2('FTTScrToTime') ) { //autocollapse is disabled when there are anchors of any kind. autocollapse *should* respect those anchors, but doesn't currently seem to respect them reliably. Maybe race condition.
//		FTT.debug('searchNodeContentsLoop: collapse sections without new comments in them');
		FTT.toggleAllH1SectionsTotal = $('#mw-content-text .FTTH1SectContainer');
		FTT.toggleAllH2SectionsTotal = $('#mw-content-text .FTTH2SectContainer');
		for (FTT.toggleAllSectionsInt=0;FTT.toggleAllSectionsInt<FTT.toggleAllH1SectionsTotal.length;FTT.toggleAllSectionsInt++){
			if ( typeof $('#' + FTT.toggleAllH1SectionsTotal[FTT.toggleAllSectionsInt].id + ' .FTTNewCmt')[0] == 'undefined' ) {
				FTT.collapseSection(FTT.toggleAllH1SectionsTotal[FTT.toggleAllSectionsInt].id,1);
			}
		}
		for (FTT.toggleAllSectionsInt=0;FTT.toggleAllSectionsInt<FTT.toggleAllH2SectionsTotal.length;FTT.toggleAllSectionsInt++){
			if ( typeof $('#' + FTT.toggleAllH2SectionsTotal[FTT.toggleAllSectionsInt].id + ' .FTTNewCmt')[0] == 'undefined' ) {
				FTT.collapseSection(FTT.toggleAllH2SectionsTotal[FTT.toggleAllSectionsInt].id,2);
			}
		}
		FTT.uncollapsedSectionCount = $('#mw-content-text .FTTH2SectContainer:not(.collapsedSection),#mw-content-text .FTTH1SectContainer:not(.collapsedSection)').length;
		if ( FTT.uncollapsedSectionCount == 0 && $('#firstHeading .sectioncollapse span')[0] ) {
//			FTT.debug('searchNodeContentsLoop: change toggle all sections arrow to up as apparently we collapsed everything');
			$('#firstHeading .sectioncollapse span')[0].classList.add('FTTSVGChevronIconRot');
			$('#firstHeading .sectioncollapse span')[0].title = FTT.B1['collapsible-expand'];
			$('#firstHeading .sectioncollapse span .FTTScreenReaderLabel')[0].innerText = FTT.B1['collapsible-expand'];
		}
	}
	FTT.loadTimeSectionSplitting = new Date().getTime() - FTT.loadTimeAddLinksEnd;
	if ( FTT.settings.mobileMWCollapsible && FTT.isMobile && $('.mw-collapsible')[0] ) {
		mw.loader.using('jquery.makeCollapsible').then( function () {
			//mw.loader.load(M1('wgServer') + '/w/load.php?lang='+M1('wgUserLanguage')+'&modules=site.styles&only=styles&skin=' + M1('skin'),'text/css'); //no relative path, wgServer on a mobile domain points to the non-mobile domain, the mobile domain won't serve the requested CSS because [INSERT REASON HERE]. Commented out as the following CSS addition seems to work better ("show" is black otherwise) and is cheaper:
			mw.util.addCSS('.mw-parser-output .mw-collapsible-toggle a{color:#3366cc}.mw-parser-output .mw-collapsible-toggle{font-weight:normal;}');
			$($('.mw-collapsible')).makeCollapsible(); //T111565 FTFY
		});
	}
	if ( FTT.settings.MFAdjeditFS && ! FTT.HidMinervaEdit && M1('skin') == 'minerva' ) {
		FTT.HidMinervaEdit = 1;
		mw.util.addCSS('.skin-minerva .mw-editsection a.edit-page{display:none !important}');
	}
	if ( ! FTT.automaticallyOpenFormParams && FTT.settings.hideDT && FTT.settings.nSecLink && M1('wgAction') == 'view' && ( mw.user.options.get('discussiontools-newtopictool') == 1 || M2('dtenable') ) && M2('action') == 'edit' && M2('section') == 'new' ) {
		FTT.automaticallyOpenFormParams = FTT.PRMnSec;
		mw.util.addCSS('.ext-discussiontools-ui-newTopic{display:none}');
	}
	if ( $('.FTTHeadingLinks')[0] ) {
		$('body').addClass('FTTWithHeadingLinks');
	} else {
		$('body').addClass('FTTNoHeadingLinks');
	}
	FTT.applyModules('afterLinkLoad');
	if ( FTT.automaticallyOpenFormParams ) {
		FTT.openReplyForm(FTT.automaticallyOpenFormParams);
	}
	if ( typeof FTTShowLoadTime != 'undefined' ) { console.log('added links in ' + FTT.loadTimeAddLinks + 'ms'); }
	if ( M2('FTTScrToTime') ) {
		FTT.scrollToComment(M2('FTTScrToUsr'),M2('FTTScrToTime'));
	}
	if ( mode == 'reparse' && document.getElementById(D2(window.location.hash.slice(1))) && D2(window.location.hash).match(/:[0-9]{13,14}:/) ) {
		FTT.HLCmt(document.getElementById(D2(window.location.hash.slice(1))),undefined,undefined,5000,1);
	}
	document.getSelection().collapse($('body')[0]); //cancel selection that may be caused by double click
};//end searchNodeContentsLoop
FTT.timestampInitEnd = new Date().getTime();
if ( window.location.hash.match(/^#f[at](t|ctotum)edit$/i) ) {
		FTT.openReplyForm(FTT.PRMFP);
} else if ( FTT.settings.sectionIsNewTO && FTT.PRMnSec && M2('action') == 'edit' && M1('wgAction') == 'edit' && M2('section') == 'new' && M2('loadFTT') != '0' ) {//action and wgAction CAN differ if DT is fucking with us with the new topic tool
	FTT.PRMnSec.redirect = true;
	FTT.openReplyForm(FTT.PRMnSec);
}
FTT.onHashChange = function() {
	if ( window.location.hash.slice(1) != '' ) {
//		FTT.debug('onHashChange: new hash: '+window.location.hash.slice(1));
		FTT.hashDec = D2(window.location.hash.slice(1));
		FTT.hashEl = document.getElementById(FTT.hashDec);
		if ( ! FTT.hashEl && ! FTT.hashDecAlt ) { //check for hashDecAlt to prevent any possibility of infinite loops here
			FTT.hashDecAlt = FTT.hashDec.replace(/(:[0-9]{13,14}:).*/,'$1'+M1('wgPageName').replace(/:/g,'FTTCLN'));
			if ( document.getElementById(FTT.hashDecAlt) ) {
//				FTT.debug('onHashChange: found hash element after changing its pagetitle to the title of the current page, probably in a section that was moved using Factotum and had its locator updated.');
				window.location.hash = FTT.hashDecAlt; //triggers onhashchange which will re-trigger onHashChange but this time the element will exist
				return;
			}
		}
		delete FTT.hashDecAlt; //hashchange not triggered so we can safely remove this now in case we need to trigger the alternative hash thing again
		if ( FTT.settings.collapsible && FTT.hashEl && getComputedStyle(FTT.hashEl).display == 'none' ) {
//			FTT.debug('onHashChange: trying to scroll to a display:none element (possibly in a collapsed section)');
			FTT.uncollapDamnYou = 0;
			var DelayUncollapse = setInterval(function () { //I have no fucking clue why a delay is needed for this, but it is.
				FTT.HLUncollapse(FTT.hashEl);
				if ( FTT.uncollapDamnYou > 30 || FTT.hashEl && getComputedStyle(FTT.hashEl).display != 'none' ) {
					FTT.magicScroll(FTT.hashEl);
					clearInterval(DelayUncollapse);
				}
				FTT.uncollapDamnYou++;
			}, 100);
			return;
		}
		if ( FTT.hashEl && FTT.hashEl.classList.contains('FTTCmt') ) {
			FTT.HLCmt(FTT.hashEl,undefined,undefined,5000,1);
		} else if ( ! FTT.hashEl && M2('redirect') != 'no' && D2(window.location.hash).match(/:([0-9]{13,14}):/) ) { // that looks an awful lot like a locator, but apparently the element ain't here, so where is it?
			FTT.locateLocatorParams = {action:'query',format:'json',formatversion: 2,list:'search',srsearch:'insource:'+D2(window.location.hash).match(/:([0-9]{13,14}):/)[1]+' insource:/id=\\"[^\\"]+:'+D2(window.location.hash).match(/:([0-9]{13,14}):/)[1]+':[^\\"]+\\"/',srnamespace:[1,3,4,5,7,9,11,13,15,101,119,711,829,2301,2303],srlimit:1};
			api.get(FTT.locateLocatorParams).then(function(data) {
				if ( data.query.searchinfo.totalhits == 1 ) { //we could check data.query.search[0].snippet at this point to verify this is exactly the locator we were looking for. Maybe later, super edge case
					window.location = M1('wgArticlePath').replace('$1',data.query.search[0].title)+'?redirect=no'+D2(window.location.hash);
				}
			});
		}	else if ( FTT.hashEl && ! FTT.hashEl.classList.contains('mw-headline') ) { //could be any random anchor that might be part of a comment
			FTT.HLCmt(FTT.hashEl,undefined,undefined,0,1);
		}
	}
	if ( FTT.sectionsAreCollapsed && FTT.hashEl ) { //when you click a section link in the ToC, try to uncollapse it
		try {
			FTT.hashElParent = FTT.hashEl;
			while ( typeof FTT.hashElParent == 'object' && FTT.hashElParent.id != 'mw-content-text' ) {
//				FTT.debug('onHashChange: see if element should be uncollapsed:');
//				FTT.debug(FTT.hashElParent);
				FTT.hashElParent = FTT.hashElParent.parentElement;
				if ( FTT.hashElParent.classList.contains('FTTH1SectContainer') || FTT.hashElParent.classList.contains('FTTH2SectContainer') ) {
//					FTT.debug('onHashChange: click FTTSVGChevronIconRot');
					FTT.hashElParent.querySelectorAll('.FTTSVGChevronIconRot')[0].click();
				}
			}
			FTT.hashEl.scrollIntoView(); //the uncollapsing can mess with the browser scrolling so we re-scroll
		} catch (e) {}
	}
};
addEventListener('hashchange',function(){FTT.onHashChange();});
FTT.onHashChange();
if ( ( ! FTT.goNinja || M2('FTTScrToTime') ) && FTT.viewingPage && FTT.B1 && FTT.B1.timestamp != '' ) { //don't load links on special pages etc, don't load if translations not loaded yet
//	FTT.debug('run searchNodeContentsLoop (initial, usual)');
	FTT.searchNodeContentsLoop();
}
if ( ! FTT.settings.markNewCmts && ! FTT.settings.markNewCmtsSubbed && FTT.settings.autoCollapse ) {
	$('.FTTNewCmt').removeClass('FTTNewCmt');
}
if ( window.location.hash.match(/:[0-9]{13,14}:/) && document.getElementById(window.location.hash.slice(1)) ) {
//	FTT.debug('anchor to locator found');
	FTT.highlightHTMLRegExp = new RegExp('([^]*)(' + E1(document.getElementById(window.location.hash.slice(1)).outerHTML) + ')');
	FTT.highlightElement = document.getElementById(window.location.hash.slice(1)).parentElement;
	var DelayJump = setInterval(function () {
		clearInterval(DelayJump);
		if ( FTT.highlightElement ) {FTT.highlightElement.scrollIntoView(FTT.smoothScroll);}
	}, 20);
	FTT.highlightElement.classList.add('FTTEaseIn','FTTPurpleBG');
	var DelayNormalDisplay = setInterval(function () {
		clearInterval(DelayNormalDisplay);
		FTT.highlightElement.classList.remove('FTTPurpleBG');
	}, 1000);
}
FTT.reJump = function() {
	$(document).ready(function() {
		if ( window.location.hash != '' && document.getElementById(D2(window.location.hash.slice(1))) ) {
//			FTT.debug('url contains an anchor. as the page may have jumped, re-jump to anchor');
			document.getElementById(D2(window.location.hash.slice(1))).scrollIntoView({behavior: 'smooth'});
		}
	});
};
FTT.cureDTBlueStreak = function() {
if ( window.location.hash != '' ) {
	FTT.waitingForBSint = 0;
	FTT.waitingForBS = function() {
		FTT.waitingForBSint++;
		var DelayedBS = setInterval(function () {
			clearInterval(DelayedBS);
			if ( $('.ext-discussiontools-init-targetcomment')[0] ) {
				FTT.HLCmt($('.ext-discussiontools-init-targetcomment')[0]);
				$('.ext-discussiontools-init-targetcomment').addClass('FTTEaseIn');
				$('.ext-discussiontools-init-targetcomment').removeClass('ext-discussiontools-init-targetcomment');
				var DelayBlueCure = setInterval(function () {
					clearInterval(DelayBlueCure);
					FTT.UNHLCmt();
				}, 5000);
			} else if ( FTT.waitingForBSint < 200 ) {
				FTT.waitingForBS();
			}
		}, 50);
	};
	FTT.waitingForBS();
}
};
if ( FTT.settings.cureDTBlueStreak ) {
	addEventListener('hashchange',function(){FTT.cureDTBlueStreak();});
	FTT.cureDTBlueStreak();
}
FTT.HLDiffCmtEl = function(contentEl,timestamp,cInt) {
	for (cInt=0;cInt<contentEl.children.length;cInt++) {
		if ( ['DD','DL','P','OL','LI','DIV'].includes(contentEl.children[cInt].nodeName) && FTT.cleanTimestamp(contentEl.children[cInt].innerText).match(timestamp) ) {
//			FTT.debug('HLDiffCmtEl: found match:');
//			FTT.debug(contentEl.children[cInt]);
			return contentEl.children[cInt]; //return element that contains timestamp
		}
	}
	return false;
};
FTT.HLDiffCmt = function(diffCmtInt,tryElInt,tryEl) {
//	FTT.debug('HLDiffCmt: try to highlight comment from diff');
	for (diffCmtInt=0;diffCmtInt<$('#mw-content-text>table.diff:eq(0) .diff-addedline').length;diffCmtInt++) {
		FTT.checkDiffSig = FTT.cleanTimestamp($('#mw-content-text>table.diff:eq(0) .diff-addedline')[diffCmtInt].innerText).match(FTT.signDateRegExpLocalMonths);
		if ( FTT.checkDiffSig ) {
//			FTT.debug('HLDiffCmt: found timestamp');
			tryEl = $('#mw-content-text')[0];
			tryElInt = 0;
			while ( tryEl && tryElInt < 500 ) {
				tryElInt++;
//				FTT.debug('HLDiffCmt: search elements for timestamp '+FTT.checkDiffSig);
				tryEl = FTT.HLDiffCmtEl(tryEl, new RegExp(E1(FTT.checkDiffSig[0])));
//				FTT.debug('HLDiffCmt: try element:');
//				FTT.debug(tryEl);
				if ( tryEl == false && FTT.HLDiffCmtPrevEl && FTT.HLDiffCmtPrevEl.id != 'mw-content-text' ) {
//					FTT.debug('HLDiffCmt: highlighting element that matches timestamp from diff');
					window.location.hash = ''; // stop reJump from jumping back to #mw-diff-ntitle1
					FTT.HLCmt(FTT.HLDiffCmtPrevEl,undefined,undefined,5000,1);
					return;
				}
				FTT.HLDiffCmtPrevEl = tryEl;
			}
		}
	}
};
if ( window.location.hash == '#mw-diff-ntitle1' ) { //likely diff-permalink to legacy comment, try highlighting it
	FTT.HLDiffCmt();
}
FTT.loadTimeTotal = new Date().getTime() - FTT.timestampInit;
if ( typeof FTT.loadTimeAddLinks == 'undefined' ) {
	FTT.loadTimeAddLinks = 0;
	FTT.loadTimeSectionSplitting = 0;
}
if ( FTT.killSwitchTriggered ) { FTT.maybeSkullNBones = '☠️'; } else { FTT.maybeSkullNBones = ''; }
if ( typeof FTTShowLoadTime != 'undefined' ) { //this isn't a regular debug message because enabling debug can increase load time significantly. Declare FTTShowLoadTime in your common.js
	$(document).ready(function() {
		FTT.loadTimeTotalReady = new Date().getTime() - FTT.timestampInit;
		console.log('load time (initialisation): ' + (FTT.timestampInitEnd - FTT.timestampInit) + 'ms, adding links: ' + FTT.loadTimeAddLinks + 'ms, making sections collapsible/reversed: ' + FTT.loadTimeSectionSplitting + 'ms, total: ' + FTT.loadTimeTotal + ', document ready: ' + FTT.loadTimeTotalReady + 'ms');
	});
}
if ( ! M2('FTTScrToTime') && ! FTT.disableRejump ) { //reJump doesn't do anything without an anchor and FTTScrToTime links typically wouldn't have one, but maybe if you clicked a section after visiting such a link and copy-pasted the new link from the address bar
	FTT.reJump(); //jumping seems to wait for the document to be ready, messing with the load time numbers, so we jump after the load time has been recorded
}
FTT.nowikiClose = '</nowiki>';
}; //end FTT.run
FTT.JSError = function(e){
	FTT.errStack = '';
	if ( e.stack ) {
		FTT.errStack = e.stack.split(/\n/)[0].slice(0,400);
	}
	FTT.errLineNo = '';
	if ( e.lineNumber ) {
		FTT.errLineNo = ', line '+(e.lineNumber||'?')+':'+(e.columnNumber||'?');
	}
	if ( window.FTTjskitty || mw.util.getParamValue('FTTjskitty') ) {
		try{FTT.addScrewedLink('JS error'+FTT.errLineNo+': '+e.toString(),FTT.errStack);} catch (event) {}
	}
//	FTT.debug('JSError: uh-oh');
	throw e;
};
if ( mw.util.getParamValue('disableFTT') == null && mw.util.getParamValue('disableftt') == null ) {
	$.when( mw.loader.using( ['mediawiki.util','mediawiki.api','mediawiki.Uri'] ), $.ready ).then( function () {
		FTT.convertLSArr = ['Bawl','BawlBasicLang','BawlDrafts','BawlLang','BawlLV','BawlPrefQueue','BawlQueueUpdatePrefInstanceID','BawlReminderSubsLastCheck','BawlSubs','BawlThanks'];
		for (FTT.convertLSInt=0;FTT.convertLSInt<FTT.convertLSArr.length;FTT.convertLSInt++) {
			if ( typeof window.localStorage[FTT.convertLSArr[FTT.convertLSInt]] != 'undefined' ) {
				mw.storage.set(FTT.convertLSArr[FTT.convertLSInt].replace(/^Bawl/,'FTT'),mw.storage.get(FTT.convertLSArr[FTT.convertLSInt]).replace(/bawl/ig,'FTT'));
				mw.storage.remove(FTT.convertLSArr[FTT.convertLSInt]);
			}
		}
		try{
			FTT.run();
		} catch (e) {
			FTT.JSError(e);
		}
	});
} else {
	console.log('FTT: skipped loading');
}
} else {
	console.log('FTT: you seem to be trying to load me twice. No dice.');
}