javascript-mode.php 82 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906
  1. <?php namespace HashOver;
  2. // Copyright (C) 2010-2017 Jacob Barkdull
  3. // This file is part of HashOver.
  4. //
  5. // HashOver is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU Affero General Public License as
  7. // published by the Free Software Foundation, either version 3 of the
  8. // License, or (at your option) any later version.
  9. //
  10. // HashOver is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU Affero General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with HashOver. If not, see <http://www.gnu.org/licenses/>.
  17. // Display source code
  18. if (basename ($_SERVER['PHP_SELF']) === basename (__FILE__)) {
  19. if (isset ($_GET['source'])) {
  20. header ('Content-type: text/plain; charset=UTF-8');
  21. exit (file_get_contents (basename (__FILE__)));
  22. } else {
  23. exit ('<b>HashOver</b>: This isn\'t a standalone file.');
  24. }
  25. }
  26. // Count according to `$showsReplyCount` setting
  27. $show_number_comments = $hashover->getCommentCount ('show-number-comments');
  28. // Text for "Show X Other Comment(s)" link
  29. if ($hashover->setup->collapsesComments !== false) {
  30. // Check if at least 1 comment is to be shown
  31. if ($hashover->setup->collapseLimit >= 1) {
  32. // If so, use the "Show X Other Comments" locale
  33. $more_link_locale = $hashover->locale->get ('show-other-comments');
  34. // Shorter variables
  35. $total_count = $hashover->readComments->totalCount;
  36. $collapse_limit = $hashover->setup->collapseLimit;
  37. // Get number of comments after collapse limit
  38. $other_count = ($total_count - 1) - $collapse_limit;
  39. // Subtract deleted comment counts
  40. if ($hashover->setup->countIncludesDeleted === false) {
  41. $other_count -= $hashover->readComments->collapsedDeletedCount;
  42. }
  43. // Decide if count is pluralized
  44. $more_link_plural = ($other_count !== 1) ? 1 : 0;
  45. $more_link_text = $more_link_locale[$more_link_plural];
  46. // And inject the count into the locale string
  47. $more_link_text = sprintf ($more_link_text, $other_count);
  48. } else {
  49. // If not, show count according to `$showsReplyCount` setting
  50. $more_link_text = $show_number_comments;
  51. }
  52. }
  53. // Some short variables (FIXME: this is cosmetic)
  54. $allowsLikes = !!$hashover->setup->allowsLikes;
  55. $allowsDislikes = !!$hashover->setup->allowsDislikes;
  56. $likesOrDislikes = ($allowsLikes or $allowsDislikes);
  57. // Return a boolean as a string
  58. function string_boolean ($boolean, $value = true)
  59. {
  60. return ($boolean === $value) ? 'true' : 'false';
  61. }
  62. // Return a boolean as a string, preferring true
  63. function string_true ($boolean)
  64. {
  65. return ($boolean !== false) ? 'true' : 'false';
  66. }
  67. // Encodes JSON, returns output that conforms to coding standard
  68. function js_json ($string, $pretty_print = true, $tabs = 1)
  69. {
  70. $json_options = 0;
  71. $search = array ('\\/', "'", '"', "','", ' ', PHP_EOL);
  72. $replace = array ('/', "\'", "'", "', '", "\t", PHP_EOL . str_repeat ("\t", $tabs));
  73. // Enable pretty print where possible
  74. if ($pretty_print !== false and defined ('JSON_PRETTY_PRINT')) {
  75. $json_options |= JSON_PRETTY_PRINT;
  76. }
  77. // Check if Unicode escaping can be disabled
  78. if (defined ('JSON_UNESCAPED_UNICODE')) {
  79. // If so, encode string as JSON without Unicode escaping
  80. $json = json_encode ($string, $json_options | JSON_UNESCAPED_UNICODE);
  81. } else {
  82. // If not, encode string as JSON normally
  83. $json = json_encode ($string, $json_options);
  84. // And decode Unicode escaped characters
  85. $json = preg_replace_callback ('/\\\u([0-9a-f]{3,4})/i', function ($groups) {
  86. return html_entity_decode ('&#x' . $groups[1] . ';');
  87. }, $json);
  88. }
  89. // Conform JSON to coding standard
  90. $json = str_replace ($search, $replace, $json);
  91. return $json;
  92. }
  93. // Returns a regular expression in JavaScript syntax
  94. function js_regex ($regex, $strings)
  95. {
  96. $regex = preg_replace ('/\\\\([0-9]+)/', '$\\1', $regex);
  97. if ($strings !== true) {
  98. $regex .= 'g';
  99. }
  100. return $regex;
  101. }
  102. // Returns an array of regular expressions in JavaScript syntax
  103. function js_regex_array ($regexes, $strings, $tabs = "\t")
  104. {
  105. // Convert capturing groups to JavaScript syntax
  106. for ($i = 0, $il = count ($regexes); $i < $il; $i++) {
  107. $regexes[$i] = js_regex ($regexes[$i], $strings);
  108. }
  109. // Return array as strings in JavaScript syntax
  110. if ($strings === true) {
  111. return js_json ($regexes);
  112. }
  113. // Join array items as regular expressions
  114. $js_array = implode (',' . PHP_EOL . $tabs . $tabs, $regexes);
  115. // Return array in JavaScript syntax
  116. return '[' . PHP_EOL . $tabs . $tabs . $js_array . PHP_EOL . $tabs . ']';
  117. }
  118. ?>
  119. // @licstart The following is the entire license notice for the
  120. // JavaScript code in this page.
  121. //
  122. // Copyright (C) 2010-2017 Jacob Barkdull
  123. // This file is part of HashOver.
  124. //
  125. // HashOver is free software: you can redistribute it and/or modify
  126. // it under the terms of the GNU Affero General Public License as
  127. // published by the Free Software Foundation, either version 3 of the
  128. // License, or (at your option) any later version.
  129. //
  130. // HashOver is distributed in the hope that it will be useful,
  131. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  132. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  133. // GNU Affero General Public License for more details.
  134. //
  135. // You should have received a copy of the GNU Affero General Public License
  136. // along with HashOver. If not, see <http://www.gnu.org/licenses/>.
  137. //
  138. // @licend The above is the entire license notice for the
  139. // JavaScript code in this page.
  140. // Initial HashOver object
  141. var HashOver = {};
  142. // FIXME: This will be split into multiple functions in separate files
  143. HashOver.init = function ()
  144. {
  145. "use strict";
  146. var execStart = Date.now ();
  147. var httpRoot = '<?php echo $hashover->misc->jsEscape ($hashover->setup->httpRoot); ?>';
  148. var URLRegex = '((http|https|ftp):\/\/[a-z0-9-@:;%_\+.~#?&\/=]+)';
  149. var URLParts = window.location.href.split ('#');
  150. var elementsById = {};
  151. var trimRegex = /^[\r\n]+|[\r\n]+$/g;
  152. var streamMode = <?php echo string_boolean ($hashover->setup->replyMode, 'stream'); ?>;
  153. var streamDepth = <?php echo $hashover->misc->jsEscape ($hashover->setup->streamDepth); ?>;
  154. var blockCodeRegex = <?php echo js_regex ($hashover->markdown->blockCodeRegex, false); ?>;
  155. var inlineCodeRegex = <?php echo js_regex ($hashover->markdown->inlineCodeRegex, false); ?>;
  156. var blockCodeMarker = /CODE_BLOCK\[([0-9]+)\]/g;
  157. var inlineCodeMarker = /CODE_INLINE\[([0-9]+)\]/g;
  158. var collapsedCount = 0;
  159. var collapseLimit = <?php echo $hashover->misc->jsEscape ($hashover->setup->collapseLimit); ?>;
  160. var defaultName = '<?php echo $hashover->misc->jsEscape ($hashover->setup->defaultName); ?>';
  161. var allowsDislikes = <?php echo string_true ($allowsDislikes); ?>;
  162. var allowsLikes = <?php echo string_true ($allowsLikes); ?>;
  163. var timeFormat = <?php echo js_json ($hashover->setup->timeFormat, false); ?>;
  164. var linkRegex = new RegExp (URLRegex + '( {0,1})', 'ig');
  165. var imageRegex = new RegExp ('\\[img\\]<a.*?>' + URLRegex + '</a>\\[/img\\]', 'ig');
  166. var imageExtensions = <?php echo js_json ($hashover->setup->imageTypes, false); ?>;
  167. var imagePlaceholder = '<?php echo $hashover->misc->jsEscape ($hashover->setup->httpImages); ?>/place-holder.<?php echo $hashover->misc->jsEscape ($hashover->setup->imageFormat); ?>';
  168. var codeOpenRegex = /<code>/i;
  169. var codeTagRegex = /(<code>)([\s\S]*?)(<\/code>)/ig;
  170. var preOpenRegex = /<pre>/i;
  171. var preTagRegex = /(<pre>)([\s\S]*?)(<\/pre>)/ig;
  172. var lineRegex = /(?:\r\n|\r|\n)/g;
  173. var paragraphRegex = /(?:\r\n|\r|\n){2}/g;
  174. var serverEOL = '<?php echo str_replace (array ("\r", "\n"), array ('\r', '\n'), PHP_EOL); ?>';
  175. var doubleEOL = serverEOL + serverEOL;
  176. var codeTagMarkerRegex = /CODE_TAG\[([0-9]+)\]/g;
  177. var preTagMarkerRegex = /PRE_TAG\[([0-9]+)\]/g;
  178. var messageTimeouts = {};
  179. var userIsLoggedIn = <?php echo string_boolean ($hashover->login->userIsLoggedIn); ?>;
  180. var primaryCount = <?php echo $hashover->misc->jsEscape ($hashover->readComments->primaryCount - 1); ?>;
  181. var totalCount = <?php echo $hashover->misc->jsEscape ($hashover->readComments->totalCount - 1); ?>;
  182. var AJAXPost = null;
  183. var AJAXEdit = null;
  184. var httpScripts = '<?php echo $hashover->misc->jsEscape ($hashover->setup->httpScripts); ?>';
  185. var commentStatuses = ['approved', 'pending', 'deleted'];
  186. var moreLink = null;
  187. var sortDiv = null;
  188. var moreDiv = null;
  189. var showingMore = false;
  190. var pageURL = '<?php echo $hashover->misc->jsEscape ($hashover->html->pageURL); ?>';
  191. var threadRegex = /^(c[0-9r]+)r[0-9\-pop]+$/;
  192. var appendCSS = true;
  193. var themeCSS = httpRoot + '/themes/<?php echo $hashover->misc->jsEscape ($hashover->setup->theme); ?>/style.css';
  194. var head = document.head || document.getElementsByTagName ('head')[0];
  195. var URLHref = URLParts[0];
  196. var HashOverDiv = document.getElementById ('hashover');
  197. var hashoverScript = <?php echo js_json ($hashover->setup->executingScript, false); ?>;
  198. var deviceType = '<?php echo $hashover->setup->isMobile === true ? 'mobile' : 'desktop'; ?>';
  199. var HashOverForm = null;
  200. var collapseComments = <?php echo string_true ($hashover->setup->collapsesComments); ?>;
  201. var URLHash = URLParts[1] || '';
  202. // Array for inline code and code block markers
  203. var codeMarkers = {
  204. block: { marks: [], count: 0 },
  205. inline: { marks: [], count: 0 }
  206. };
  207. // Some locales, stored in JavaScript to avoid using a lot of PHP tags
  208. var locale = {
  209. cancel: '<?php echo $hashover->locale->get ('cancel'); ?>',
  210. dateTime: <?php echo js_json ($hashover->locale->get ('date-time'), false); ?>,
  211. dislikeComment: '<?php echo $hashover->locale->get ('dislike-comment'); ?>',
  212. dislikedComment: '<?php echo $hashover->locale->get ('disliked-comment'); ?>',
  213. disliked: '<?php echo $hashover->locale->get ('disliked'); ?>',
  214. dislike: <?php echo js_json ($hashover->locale->get ('dislike', false), false); ?>,
  215. email: '<?php echo $hashover->locale->get ('email'); ?>',
  216. externalImageTip: '<?php echo $hashover->locale->get ('external-image-tip'); ?>',
  217. fieldNeeded: '<?php echo $hashover->locale->get ('field-needed'); ?>',
  218. likeComment: '<?php echo $hashover->locale->get ('like-comment'); ?>',
  219. likedComment: '<?php echo $hashover->locale->get ('liked-comment'); ?>',
  220. liked: '<?php echo $hashover->locale->get ('liked'); ?>',
  221. like: <?php echo js_json ($hashover->locale->get ('like', false), false); ?>,
  222. name: '<?php echo $hashover->locale->get ('name'); ?>',
  223. password: '<?php echo $hashover->locale->get ('password'); ?>',
  224. postCommentOn: '<?php echo $hashover->html->postCommentOn; ?>',
  225. today: '<?php echo $hashover->locale->get ('date-today'); ?>',
  226. unlike: '<?php echo $hashover->locale->get ('unlike'); ?>',
  227. website: '<?php echo $hashover->locale->get ('website'); ?>'
  228. };
  229. // Markdown patterns to search for
  230. var markdownSearch = <?php echo js_regex_array ($hashover->markdown->search, false); ?>;
  231. // HTML replacements for markdown patterns
  232. var markdownReplace = <?php echo js_regex_array ($hashover->markdown->replace, true); ?>;
  233. // Tags that will have their innerHTML trimmed
  234. var trimTagRegexes = {
  235. blockquote: {
  236. test: /<blockquote>/,
  237. replace: /(<blockquote>)([\s\S]*?)(<\/blockquote>)/ig
  238. },
  239. ul: {
  240. test: /<ul>/,
  241. replace: /(<ul>)([\s\S]*?)(<\/ul>)/ig
  242. },
  243. ol: {
  244. test: /<ol>/,
  245. replace: /(<ol>)([\s\S]*?)(<\/ol>)/ig
  246. }
  247. };
  248. // Field options
  249. var fieldOptions = <?php echo js_json ($hashover->setup->fieldOptions); ?>;
  250. // Shorthand for Document.getElementById ()
  251. function getElement (id, force)
  252. {
  253. if (force === true) {
  254. return document.getElementById (id);
  255. }
  256. if (!elementsById[id]) {
  257. elementsById[id] = document.getElementById (id);
  258. }
  259. return elementsById[id];
  260. }
  261. // Execute callback function if element isn't false
  262. function ifElement (element, callback)
  263. {
  264. if (element = getElement (element, true)) {
  265. return callback (element);
  266. }
  267. return false;
  268. }
  269. // Trims leading and trailing newlines from a string
  270. function EOLTrim (string)
  271. {
  272. return string.replace (trimRegex, '');
  273. }
  274. // Trims whitespace from an HTML tag's inner HTML
  275. function tagTrimmer (fullTag, openTag, innerHTML, closeTag)
  276. {
  277. return openTag + EOLTrim (innerHTML) + closeTag;
  278. }
  279. // Find a comment by its permalink
  280. function findByPermalink (permalink, comments)
  281. {
  282. var comment;
  283. // Loop through all comments
  284. for (var i = 0, il = comments.length; i < il; i++) {
  285. // Return comment if its permalink matches
  286. if (comments[i].permalink === permalink) {
  287. return comments[i];
  288. }
  289. // Recursively check replies when present
  290. if (comments[i].replies !== undefined) {
  291. comment = findByPermalink (permalink, comments[i].replies);
  292. if (comment !== null) {
  293. return comment;
  294. }
  295. }
  296. }
  297. // Otherwise return null
  298. return null;
  299. }
  300. // Returns the permalink of a comment's parent
  301. function getParentPermalink (permalink, flatten)
  302. {
  303. flatten = flatten || false;
  304. var parent = permalink.split ('r');
  305. var length = parent.length - 1;
  306. // Limit depth if in stream mode
  307. if (streamMode === true && flatten === true) {
  308. length = Math.min (streamDepth, length);
  309. }
  310. // Check if there is a parent after flatten
  311. if (length > 0) {
  312. // If so, remove child from permalink
  313. parent = parent.slice (0, length);
  314. // Return parent permalink as string
  315. return parent.join ('r');
  316. }
  317. return null;
  318. }
  319. // Replaces markdown for inline code with a marker
  320. function codeReplace (fullTag, first, second, third, display)
  321. {
  322. var markName = 'CODE_' + display.toUpperCase ();
  323. var markCount = codeMarkers[display].count++;
  324. var codeMarker;
  325. if (display !== 'block') {
  326. codeMarker = first + markName + '[' + markCount + ']' + third;
  327. codeMarkers[display].marks[markCount] = EOLTrim (second);
  328. } else {
  329. codeMarker = markName + '[' + markCount + ']';
  330. codeMarkers[display].marks[markCount] = EOLTrim (first);
  331. }
  332. return codeMarker;
  333. }
  334. // Parses a string as markdown
  335. function parseMarkdown (string)
  336. {
  337. // Reset marker arrays
  338. codeMarkers = {
  339. block: { marks: [], count: 0 },
  340. inline: { marks: [], count: 0 }
  341. };
  342. // Replace code blocks with markers
  343. string = string.replace (blockCodeRegex, function (fullTag, first, second, third) {
  344. return codeReplace (fullTag, first, second, third, 'block');
  345. });
  346. // Break string into paragraphs
  347. var paragraphs = string.split (paragraphRegex);
  348. // Run through each paragraph replacing markdown patterns
  349. for (var i = 0, il = paragraphs.length; i < il; i++) {
  350. // Replace code tags with marker text
  351. paragraphs[i] = paragraphs[i].replace (inlineCodeRegex, function (fullTag, first, second, third) {
  352. return codeReplace (fullTag, first, second, third, 'inline');
  353. });
  354. // Perform each markdown regular expression on the current paragraph
  355. for (var r = 0, rl = markdownSearch.length; r < rl; r++) {
  356. // Replace markdown patterns
  357. paragraphs[i] = paragraphs[i].replace (markdownSearch[r], markdownReplace[r]);
  358. }
  359. // Return the original markdown code with HTML replacement
  360. paragraphs[i] = paragraphs[i].replace (inlineCodeMarker, function (marker, number) {
  361. return '<code class="hashover-inline">' + codeMarkers.inline.marks[number] + '</code>';
  362. });
  363. }
  364. // Join paragraphs
  365. string = paragraphs.join (doubleEOL);
  366. // Replace code block markers with original markdown code
  367. string = string.replace (blockCodeMarker, function (marker, number) {
  368. return '<code>' + codeMarkers.block.marks[number] + '</code>';
  369. });
  370. return string;
  371. }
  372. // Adds properties to an element
  373. function addProperties (element, properties)
  374. {
  375. element = element || document.createElement ('span');
  376. properties = properties || {};
  377. // Add each property to element
  378. for (var property in properties) {
  379. if (properties.hasOwnProperty (property) === false) {
  380. continue;
  381. }
  382. // If the property is an object add each item to existing property
  383. if (!!properties[property] && properties[property].constructor === Object) {
  384. addProperties (element[property], properties[property]);
  385. continue;
  386. }
  387. element[property] = properties[property];
  388. }
  389. return element;
  390. }
  391. // Create an element with attributes
  392. function createElement (tagName, attributes)
  393. {
  394. tagName = tagName || 'span';
  395. attributes = attributes || {};
  396. // Create element
  397. var element = document.createElement (tagName);
  398. // Add properties to element
  399. element = addProperties (element, attributes);
  400. return element;
  401. }
  402. <?php if ($hashover->setup->usesUserTimezone !== false): ?>
  403. // Simple PHP date function port
  404. function formatDate (format, date)
  405. {
  406. format = format || 'DATE_ISO8601';
  407. date = date || new Date ();
  408. var dayNames = <?php echo js_json ($hashover->locale->get ('date-day-names'), true, 2); ?>;
  409. var monthNames = <?php echo js_json ($hashover->locale->get ('date-month-names'), true, 2); ?>;
  410. var hours = date.getHours ();
  411. var ampm = (hours >= 12) ? 'pm' : 'am';
  412. var day = date.getDate ();
  413. var weekDay = date.getDay ();
  414. var dayName = dayNames[weekDay];
  415. var monthIndex = date.getMonth ();
  416. var monthName = monthNames[monthIndex];
  417. var hours12 = (hours % 12) ? hours % 12 : 12;
  418. var minutes = date.getMinutes ();
  419. var month = monthIndex + 1;
  420. var offsetHours = (date.getTimezoneOffset() / 60) * 100;
  421. var offset = ((offsetHours < 1000) ? '0' : '') + offsetHours;
  422. var offsetColon = offset.match (/[0-9]{2}/g).join (':');
  423. var offsetPositivity = (offsetHours > 0) ? '-' : '+';
  424. var seconds = date.getSeconds ();
  425. var year = date.getFullYear ();
  426. var formatParts = format.split ('');
  427. var dateConstant;
  428. var characters = {
  429. a: ampm,
  430. A: ampm.toUpperCase (),
  431. d: (day < 10) ? '0' + day : day,
  432. D: dayName.substr (0, 3),
  433. F: monthName,
  434. g: hours12,
  435. G: hours,
  436. h: (hours12 < 10) ? '0' + hours12 : hours12,
  437. H: (hours < 10) ? '0' + hours : hours,
  438. i: (minutes < 10) ? '0' + minutes : minutes,
  439. j: day,
  440. l: dayName,
  441. m: (month < 10) ? '0' + month : month,
  442. M: monthName.substr (0, 3),
  443. n: month,
  444. N: weekDay + 1,
  445. O: offsetPositivity + offset,
  446. P: offsetPositivity + offsetColon,
  447. s: (seconds < 10) ? '0' + seconds : seconds,
  448. w: weekDay,
  449. y: ('' + year).substr (2),
  450. Y: year
  451. };
  452. dateConstant = format.replace (/-/g, '_');
  453. dateConstant = dateConstant.toUpperCase ();
  454. switch (dateConstant) {
  455. case 'DATE_ATOM':
  456. case 'DATE_RFC3339':
  457. case 'DATE_W3C': {
  458. format = 'Y-m-d\TH:i:sP';
  459. break;
  460. }
  461. case 'DATE_COOKIE': {
  462. format = 'l, d-M-Y H:i:s';
  463. break;
  464. }
  465. case 'DATE_ISO8601': {
  466. format = 'Y-m-d\TH:i:sO';
  467. break;
  468. }
  469. case 'DATE_RFC822':
  470. case 'DATE_RFC1036': {
  471. format = 'D, d M y H:i:s O';
  472. break;
  473. }
  474. case 'DATE_RFC850': {
  475. format = 'l, d-M-y H:i:s';
  476. break;
  477. }
  478. case 'DATE_RFC1123':
  479. case 'DATE_RFC2822':
  480. case 'DATE_RSS': {
  481. format = 'D, d M Y H:i:s O';
  482. break;
  483. }
  484. case 'GNOME_DATE': {
  485. format = 'D M d, g:i A';
  486. break;
  487. }
  488. case 'US_DATE': {
  489. format = 'm/d/Y';
  490. break;
  491. }
  492. case 'STANDARD_DATE': {
  493. format = 'Y-m-d';
  494. break;
  495. }
  496. case '12H_TIME': {
  497. format = 'g:ia';
  498. break;
  499. }
  500. case '24H_TIME': {
  501. format = 'H:i';
  502. break;
  503. }
  504. }
  505. for (var i = 0, c, il = formatParts.length; i < il; i++) {
  506. if (i > 0 && formatParts[i - 1] === '\\') {
  507. formatParts[i - 1] = '';
  508. continue;
  509. }
  510. c = formatParts[i];
  511. formatParts[i] = characters[c] || c;
  512. }
  513. return formatParts.join ('');
  514. }
  515. <?php endif; ?>
  516. // Add comment content to HTML template
  517. function parseComment (comment, parent, collapse, sort, method, popular)
  518. {
  519. parent = parent || null;
  520. collapse = collapse || false;
  521. sort = sort || false;
  522. method = method || 'ascending';
  523. popular = popular || false;
  524. var permalink = comment.permalink;
  525. var nameClass = 'hashover-name-plain';
  526. var template = { permalink: permalink };
  527. var isReply = (parent !== null);
  528. var parentPermalink;
  529. var commentDate = comment.date;
  530. var codeTagCount = 0;
  531. var codeTags = [];
  532. var preTagCount = 0;
  533. var preTags = [];
  534. var classes = '';
  535. var replies = '';
  536. // Text for avatar image alt attribute
  537. var permatext = permalink.slice (1);
  538. permatext = permatext.split ('r');
  539. permatext = permatext.pop ();
  540. // Get parent comment via permalink
  541. if (isReply === false && permalink.indexOf ('r') > -1) {
  542. parentPermalink = getParentPermalink (permalink);
  543. parent = findByPermalink (parentPermalink, PHPContent.comments);
  544. isReply = (parent !== null);
  545. }
  546. // Check if this comment is a popular comment
  547. if (popular === true) {
  548. // Remove "-pop" from text for avatar
  549. permatext = permatext.replace ('-pop', '');
  550. } else {
  551. // Check if comment is a reply
  552. if (isReply === true) {
  553. // Check that comments are being sorted
  554. if (!sort || method === 'ascending') {
  555. // Append class to indicate comment is a reply
  556. classes += ' hashover-reply';
  557. }
  558. }
  559. <?php if ($hashover->setup->collapsesComments !== false): ?>
  560. // Append class to indicate collapsed comment
  561. if (totalCount > 0) {
  562. if (collapse === true && collapsedCount >= collapseLimit) {
  563. classes += ' hashover-hidden';
  564. } else {
  565. collapsedCount++;
  566. }
  567. }
  568. <?php endif; ?>
  569. }
  570. // Add avatar image to template
  571. template.avatar = '<?php echo $hashover->html->userAvatar ('permatext', 'permalink', 'comment.avatar'); ?>';
  572. if (comment.notice === undefined) {
  573. var name = comment.name || defaultName;
  574. var website = comment.website;
  575. var isTwitter = false;
  576. // Check if user's name is a Twitter handle
  577. if (name.charAt (0) === '@') {
  578. name = name.slice (1);
  579. nameClass = 'hashover-name-twitter';
  580. isTwitter = true;
  581. var nameLength = name.length;
  582. // Check if Twitter handle is valid length
  583. if (nameLength > 1 && nameLength <= 30) {
  584. // Set website to Twitter profile if a specific website wasn't given
  585. if (website === undefined) {
  586. website = 'http://twitter.com/' + name;
  587. }
  588. }
  589. }
  590. // Check whether user gave a website
  591. if (website !== undefined) {
  592. if (isTwitter === false) {
  593. nameClass = 'hashover-name-website';
  594. }
  595. // If so, display name as a hyperlink
  596. var nameLink = '<?php echo $hashover->html->nameElement ('a', 'name', 'permalink', 'website'); ?>';
  597. } else {
  598. // If not, display name as plain text
  599. var nameLink = '<?php echo $hashover->html->nameElement ('span', 'name', 'permalink'); ?>';
  600. }
  601. // Construct thread hyperlink
  602. if (isReply === true) {
  603. var parentThread = parent.permalink;
  604. var parentName = parent.name || defaultName;
  605. // Add thread parent hyperlink to template
  606. template['thread-link'] = '<?php echo $hashover->html->threadLink ('permalink', 'parentThread', 'parentName'); ?>';
  607. }
  608. if (comment['user-owned'] !== undefined) {
  609. // Append class to indicate comment is from logged in user
  610. classes += ' hashover-user-owned';
  611. // Define "Reply" link with original poster title
  612. var replyTitle = '<?php echo $hashover->locale->get ('commenter-tip'); ?>';
  613. var replyClass = 'hashover-no-email';
  614. // Add "Edit" hyperlink to template
  615. template['edit-link'] = '<?php echo $hashover->html->formLink ('edit', 'permalink'); ?>';
  616. } else {
  617. // Check if commenter is subscribed
  618. if (comment.subscribed === true) {
  619. // If so, set subscribed title
  620. var replyTitle = name + ' <?php echo $hashover->locale->get ('subscribed-tip'); ?>';
  621. var replyClass = 'hashover-has-email';
  622. } else{
  623. // If not, set unsubscribed title
  624. var replyTitle = name + ' <?php echo $hashover->locale->get ('unsubscribed-tip'); ?>';
  625. var replyClass = 'hashover-no-email';
  626. }
  627. <?php if ($allowsLikes !== false): ?>
  628. // Check whether this comment was liked by the visitor
  629. if (comment.liked !== undefined) {
  630. // If so, set various attributes to indicate comment was liked
  631. var likeClass = 'hashover-liked';
  632. var likeTitle = locale.likedComment;
  633. var likeText = locale.liked;
  634. } else {
  635. // If not, set various attributes to indicate comment can be liked
  636. var likeClass = 'hashover-like';
  637. var likeTitle = locale.likeComment;
  638. var likeText = locale.like[0];
  639. }
  640. // Append class to indicate dislikes are enabled
  641. if (allowsDislikes === true) {
  642. likeClass += ' hashover-dislikes-enabled';
  643. }
  644. // Add like link to HTML template
  645. template['like-link'] = '<?php echo $hashover->html->likeLink ('like', 'permalink', 'likeClass', 'likeTitle', 'likeText'); ?>';
  646. <?php endif; ?>
  647. <?php if ($allowsDislikes === true): ?>
  648. // Check whether this comment was disliked by the visitor
  649. if (comment.disliked !== undefined) {
  650. // If so, set various attributes to indicate comment was disliked
  651. var dislikeClass = 'hashover-disliked';
  652. var dislikeTitle = locale.dislikedComment;
  653. var dislikeText = locale.disliked;
  654. } else {
  655. // If not, set various attributes to indicate comment can be disliked
  656. var dislikeClass = 'hashover-dislike';
  657. var dislikeTitle = locale.dislikeComment;
  658. var dislikeText = locale.dislike[0];
  659. }
  660. // Append class to indicate likes are enabled
  661. if (allowsLikes === true) {
  662. dislikeClass += ' hashover-likes-enabled';
  663. }
  664. // Add dislike link to HTML template
  665. template['dislike-link'] = '<?php echo $hashover->html->likeLink ('dislike', 'permalink', 'dislikeClass', 'dislikeTitle', 'dislikeText'); ?>';
  666. <?php endif; ?>
  667. }
  668. <?php if ($allowsLikes !== false): ?>
  669. // Get number of likes, append "Like(s)" locale
  670. if (comment.likes !== undefined) {
  671. var likeCount = comment.likes + ' ' + locale.like[(comment.likes === 1 ? 0 : 1)];
  672. }
  673. // Add like count to HTML template
  674. template['like-count'] = '<?php echo $hashover->html->likeCount ('likes', 'permalink', '(likeCount || \'\')'); ?>';
  675. <?php endif; ?>
  676. <?php if ($allowsDislikes === true): ?>
  677. // Get number of dislikes, append "Dislike(s)" locale
  678. if (comment.dislikes !== undefined) {
  679. var dislikeCount = comment.dislikes + ' ' + locale.dislike[(comment.dislikes === 1 ? 0 : 1)];
  680. }
  681. // Add dislike count to HTML template
  682. template['dislike-count'] = '<?php echo $hashover->html->likeCount ('dislikes', 'permalink', '(dislikeCount || \'\')'); ?>';
  683. <?php endif; ?>
  684. // Add name HTML to template
  685. template.name = '<?php echo $hashover->html->nameWrapper ('nameLink', 'nameClass'); ?>';
  686. <?php if ($hashover->setup->usesUserTimezone !== false): ?>
  687. // Local comment post date
  688. var postDate = new Date (comment['sort-date'] * 1000);
  689. <?php if ($hashover->setup->usesShortDates !== false): ?>
  690. // Local comment post date to remove time from
  691. var postDateCopy = new Date (postDate.getTime ());
  692. // Local date
  693. var localDate = new Date ();
  694. // Format local time if the comment was posted today
  695. if (postDateCopy.setHours (0, 0, 0, 0) === localDate.setHours (0, 0, 0, 0)) {
  696. commentDate = locale.today.replace ('%s', formatDate (timeFormat, postDate));
  697. }
  698. <?php else: ?>
  699. // Format a long local date/time
  700. commentDate = formatDate (locale.dateTime, postDate);
  701. <?php endif; ?>
  702. <?php endif; ?>
  703. // Add date from comment as permalink hyperlink to template
  704. template.date = '<?php echo $hashover->html->dateLink ('permalink', 'commentDate'); ?>';
  705. // Add "Reply" hyperlink to template
  706. template['reply-link'] = '<?php echo $hashover->html->formLink ('reply', 'permalink', 'replyClass', 'replyTitle'); ?>';
  707. // Add reply count to template
  708. if (comment.replies !== undefined) {
  709. template['reply-count'] = comment.replies.length;
  710. if (template['reply-count'] > 0) {
  711. if (template['reply-count'] !== 1) {
  712. template['reply-count'] += ' <?php echo $hashover->locale->get ('replies'); ?>';
  713. } else {
  714. template['reply-count'] += ' <?php echo $hashover->locale->get ('reply'); ?>';
  715. }
  716. }
  717. }
  718. // Add HTML anchor tag to URLs
  719. var body = comment.body.replace (linkRegex, '<a href="$1" rel="noopener noreferrer" target="_blank">$1</a>');
  720. // Replace [img] tags with external image placeholder if enabled
  721. body = body.replace (imageRegex, function (fullURL, url) {
  722. <?php if ($hashover->setup->allowsImages !== false): ?>
  723. // Get image extension from URL
  724. var urlExtension = url.split ('#')[0];
  725. urlExtension = urlExtension.split ('?')[0];
  726. urlExtension = urlExtension.split ('.');
  727. urlExtension = urlExtension.pop ();
  728. // Check if the image extension is an allowed type
  729. if (imageExtensions.indexOf (urlExtension) > -1) {
  730. // If so, create a wrapper element for the embedded image
  731. var embeddedImage = createElement ('span', {
  732. className: 'hashover-embedded-image-wrapper'
  733. });
  734. // Append an image tag to the embedded image wrapper
  735. embeddedImage.appendChild (createElement ('img', {
  736. className: 'hashover-embedded-image',
  737. src: imagePlaceholder,
  738. title: locale.externalImageTip,
  739. alt: 'External Image',
  740. dataset: {
  741. placeholder: imagePlaceholder,
  742. url: url
  743. }
  744. }));
  745. // And return the embedded image HTML
  746. return embeddedImage.outerHTML;
  747. }
  748. <?php endif; ?>
  749. // Convert image URL into an anchor tag
  750. return '<a href="' + url + '" rel="noopener noreferrer" target="_blank">' + url + '</a>';
  751. });
  752. // Parse markdown in comment
  753. body = parseMarkdown (body);
  754. // Check for code tags
  755. if (codeOpenRegex.test (body) === true) {
  756. // Replace code tags with marker text
  757. body = body.replace (codeTagRegex, function (fullTag, openTag, innerHTML, closeTag) {
  758. var codeMarker = openTag + 'CODE_TAG[' + codeTagCount + ']' + closeTag;
  759. codeTags[codeTagCount] = EOLTrim (innerHTML);
  760. codeTagCount++;
  761. return codeMarker;
  762. });
  763. }
  764. // Check for pre tags
  765. if (preOpenRegex.test (body) === true) {
  766. // Replace pre tags with marker text
  767. body = body.replace (preTagRegex, function (fullTag, openTag, innerHTML, closeTag) {
  768. var preMarker = openTag + 'PRE_TAG[' + preTagCount + ']' + closeTag;
  769. preTags[preTagCount] = EOLTrim (innerHTML);
  770. preTagCount++;
  771. return preMarker;
  772. });
  773. }
  774. // Check for various multi-line tags
  775. for (var trimTag in trimTagRegexes) {
  776. if (trimTagRegexes.hasOwnProperty (trimTag) === true
  777. && trimTagRegexes[trimTag]['test'].test (body) === true)
  778. {
  779. // Trim whitespace
  780. body = body.replace (trimTagRegexes[trimTag]['replace'], tagTrimmer);
  781. }
  782. }
  783. // Break comment into paragraphs
  784. var paragraphs = body.split (paragraphRegex);
  785. var pdComment = '';
  786. // Wrap comment in paragraph tag
  787. // Replace single line breaks with break tags
  788. for (var i = 0, il = paragraphs.length; i < il; i++) {
  789. pdComment += '<p>' + paragraphs[i].replace (lineRegex, '<br>') + '</p>' + serverEOL;
  790. }
  791. // Replace code tag markers with original code tag HTML
  792. if (codeTagCount > 0) {
  793. pdComment = pdComment.replace (codeTagMarkerRegex, function (marker, number) {
  794. return codeTags[number];
  795. });
  796. }
  797. // Replace pre tag markers with original pre tag HTML
  798. if (preTagCount > 0) {
  799. pdComment = pdComment.replace (preTagMarkerRegex, function (marker, number) {
  800. return preTags[number];
  801. });
  802. }
  803. // Add comment data to template
  804. template.comment = pdComment;
  805. } else {
  806. // Append notice class
  807. classes += ' hashover-notice ' + comment['notice-class'];
  808. // Add notice to template
  809. template.comment = comment.notice;
  810. // Add name HTML to template
  811. template.name = '<?php echo $hashover->html->nameWrapper ('comment.title', 'nameClass'); ?>';
  812. }
  813. // Comment HTML template
  814. <?php
  815. echo $hashover->html->asJSVar ($hashover->templater->parseTemplate (), 'html', "\t\t");
  816. ?>
  817. // Recursively parse replies
  818. if (comment.replies !== undefined) {
  819. for (var reply = 0, total = comment.replies.length; reply < total; reply++) {
  820. replies += parseComment (comment.replies[reply], comment, collapse);
  821. }
  822. }
  823. return '<?php echo $hashover->html->commentWrapper ('permalink', 'classes', 'html + replies'); ?>';
  824. }
  825. // Generate file from permalink
  826. function fileFromPermalink (permalink)
  827. {
  828. var file = permalink.slice (1);
  829. file = file.replace (/r/g, '-');
  830. file = file.replace ('-pop', '');
  831. return file;
  832. }
  833. // Change and hyperlink, like "Edit" or "Reply", into a "Cancel" hyperlink
  834. function cancelSwitcher (form, link, wrapper, permalink)
  835. {
  836. // Initial state properties of hyperlink
  837. var reset = {
  838. textContent: link.textContent,
  839. title: link.title,
  840. onclick: link.onclick
  841. };
  842. function linkOnClick ()
  843. {
  844. // Remove fields from form wrapper
  845. wrapper.textContent = '';
  846. // Reset button
  847. link.textContent = reset.textContent;
  848. link.title = reset.title;
  849. link.onclick = reset.onclick;
  850. return false;
  851. }
  852. // Change hyperlink to "Cancel" hyperlink
  853. link.textContent = locale.cancel;
  854. link.title = locale.cancel;
  855. // This resets the "Cancel" hyperlink to initial state onClick
  856. link.onclick = linkOnClick;
  857. <?php if ($hashover->setup->usesCancelButtons !== false): ?>
  858. // Get "Cancel" button
  859. var cancelButtonId = 'hashover-' + form + '-cancel-' + permalink;
  860. var cancelButton = getElement (cancelButtonId, true);
  861. // Attach event listeners to "Cancel" button
  862. cancelButton.onclick = linkOnClick;
  863. <?php endif; ?>
  864. }
  865. // Returns false if key event is the enter key
  866. function enterCheck (event)
  867. {
  868. return (event.keyCode === 13) ? false : true;
  869. }
  870. // Prevents enter key on inputs from submitting form
  871. function preventSubmit (form)
  872. {
  873. // Get login info inputs
  874. var infoInputs = form.getElementsByClassName ('hashover-input-info');
  875. // Set enter key press to return false
  876. for (var i = 0, il = infoInputs.length; i < il; i++) {
  877. infoInputs[i].onkeypress = enterCheck;
  878. }
  879. }
  880. // Check whether browser has classList support
  881. if (document.documentElement.classList) {
  882. // If so, wrap relevant functions
  883. // classList.contains () method
  884. var containsClass = function (element, className)
  885. {
  886. return element.classList.contains (className);
  887. };
  888. // classList.add () method
  889. var addClass = function (element, className)
  890. {
  891. element.classList.add (className);
  892. };
  893. // classList.remove () method
  894. var removeClass = function (element, className)
  895. {
  896. element.classList.remove (className);
  897. };
  898. } else {
  899. // If not, define fallback functions
  900. // classList.contains () method
  901. var containsClass = function (element, className)
  902. {
  903. if (!element || !element.className) {
  904. return false;
  905. }
  906. var regex = new RegExp ('(^|\\s)' + className + '(\\s|$)');
  907. return regex.test (element.className);
  908. };
  909. // classList.add () method
  910. var addClass = function (element, className)
  911. {
  912. if (!element) {
  913. return false;
  914. }
  915. if (!containsClass (element, className)) {
  916. element.className += (element.className ? ' ' : '') + className;
  917. }
  918. };
  919. // classList.remove () method
  920. var removeClass = function (element, className)
  921. {
  922. if (!element || !element.className) {
  923. return false;
  924. }
  925. var regex = new RegExp ('(^|\\s)' + className + '(\\s|$)', 'g');
  926. element.className = element.className.replace (regex, '$2');
  927. };
  928. }
  929. // Gets a computed element style by property
  930. function compatComputedStyle (element, proterty, type)
  931. {
  932. var computedStyle;
  933. // IE, other
  934. if (window.getComputedStyle === undefined) {
  935. computedStyle = element.currentStyle[proterty];
  936. }
  937. // Mozilla Firefox, Google Chrome
  938. computedStyle = window.getComputedStyle (element, null);
  939. computedStyle = computedStyle.getPropertyValue (proterty);
  940. // Cast value to specified type
  941. switch (type) {
  942. case 'int': {
  943. computedStyle = computedStyle.replace (/px|em/, '');
  944. computedStyle = parseInt (computedStyle) || 0;
  945. break;
  946. }
  947. case 'float': {
  948. computedStyle = computedStyle.replace (/px|em/, '');
  949. computedStyle = parseFloat (computedStyle) || 0.0;
  950. break;
  951. }
  952. }
  953. return computedStyle;
  954. }
  955. // Gets the client height of a message element
  956. function getMessageHeight (element, setChild)
  957. {
  958. setChild = setChild || false;
  959. var firstChild = element.children[0];
  960. var maxHeight = 80;
  961. // If so, set max-height style to initial
  962. firstChild.style.maxHeight = 'initial';
  963. // Get various computed styles
  964. var borderTop = compatComputedStyle (firstChild, 'border-top-width', 'int');
  965. var borderBottom = compatComputedStyle (firstChild, 'border-bottom-width', 'int');
  966. var marginBottom = compatComputedStyle (firstChild, 'margin-bottom', 'int');
  967. var border = borderTop + borderBottom;
  968. // Calculate its client height
  969. maxHeight = firstChild.clientHeight + border + marginBottom;
  970. // Set its max-height style as well if told to
  971. if (setChild === true) {
  972. firstChild.style.maxHeight = maxHeight + 'px';
  973. } else {
  974. firstChild.style.maxHeight = '';
  975. }
  976. return maxHeight;
  977. }
  978. // Open a message element
  979. function openMessage (element)
  980. {
  981. // Add classes to indicate message element is open
  982. removeClass (element, 'hashover-message-animated');
  983. addClass (element, 'hashover-message-open');
  984. var maxHeight = getMessageHeight (element);
  985. var firstChild = element.children[0];
  986. // Remove class indicating message element is open
  987. removeClass (element, 'hashover-message-open');
  988. setTimeout (function () {
  989. // Add class to indicate message element is open
  990. addClass (element, 'hashover-message-open');
  991. addClass (element, 'hashover-message-animated');
  992. // Set max-height styles
  993. element.style.maxHeight = maxHeight + 'px';
  994. firstChild.style.maxHeight = maxHeight + 'px';
  995. // Set max-height style to initial after transition
  996. setTimeout (function () {
  997. element.style.maxHeight = 'initial';
  998. firstChild.style.maxHeight = 'initial';
  999. }, 150);
  1000. }, 150);
  1001. }
  1002. // Close a message element
  1003. function closeMessage (element)
  1004. {
  1005. // Set max-height style to specific height before transition
  1006. element.style.maxHeight = getMessageHeight (element, true) + 'px';
  1007. setTimeout (function () {
  1008. // Remove max-height style from message elements
  1009. element.children[0].style.maxHeight = '';
  1010. element.style.maxHeight = '';
  1011. // Remove classes indicating message element is open
  1012. removeClass (element, 'hashover-message-open');
  1013. removeClass (element, 'hashover-message-error');
  1014. }, 150);
  1015. }
  1016. // Handle message element(s)
  1017. function showMessage (messageText, type, permalink, error, isReply, isEdit)
  1018. {
  1019. type = type || 'main';
  1020. permalink = permalink || '';
  1021. error = error || true;
  1022. isReply = isReply || false;
  1023. isEdit = isEdit || false;
  1024. var container;
  1025. var message;
  1026. // Decide which message element to use
  1027. if (isEdit === true) {
  1028. // An edit form message
  1029. container = getElement ('hashover-edit-message-container-' + permalink, true);
  1030. message = getElement ('hashover-edit-message-' + permalink, true);
  1031. } else {
  1032. if (isReply !== true) {
  1033. // The primary comment form message
  1034. container = getElement ('hashover-message-container', true);
  1035. message = getElement ('hashover-message', true);
  1036. } else {
  1037. // Of a reply form message
  1038. container = getElement ('hashover-reply-message-container-' + permalink, true);
  1039. message = getElement ('hashover-reply-message-' + permalink, true);
  1040. }
  1041. }
  1042. if (messageText !== undefined && messageText !== '') {
  1043. // Add message text to element
  1044. message.textContent = messageText;
  1045. // Add class to indicate message is an error if set
  1046. if (error === true) {
  1047. addClass (container, 'hashover-message-error');
  1048. }
  1049. }
  1050. // Add class to indicate message element is open
  1051. openMessage (container);
  1052. // Add the comment to message counts
  1053. if (messageTimeouts[permalink] === undefined) {
  1054. messageTimeouts[permalink] = {};
  1055. }
  1056. // Clear necessary timeout
  1057. if (messageTimeouts[permalink][type] !== undefined) {
  1058. clearTimeout (messageTimeouts[permalink][type]);
  1059. }
  1060. // Add timeout to close message element after 10 seconds
  1061. messageTimeouts[permalink][type] = setTimeout (function () {
  1062. closeMessage (container);
  1063. }, 10000);
  1064. }
  1065. // Handles display of various warnings when user attempts to post or login
  1066. function emailValidator (form, subscribe, type, permalink, isReply, isEdit)
  1067. {
  1068. if (form.email === undefined) {
  1069. return true;
  1070. }
  1071. // Whether the e-mail form is empty
  1072. if (form.email.value === '') {
  1073. // Return true if user unchecked the subscribe checkbox
  1074. if (getElement (subscribe, true).checked === false) {
  1075. return true;
  1076. }
  1077. // If so, warn the user that they won't receive reply notifications
  1078. if (confirm ('<?php echo $hashover->locale->get ('no-email-warning'); ?>') === false) {
  1079. form.email.focus ();
  1080. return false;
  1081. }
  1082. } else {
  1083. var message;
  1084. var emailRegex = /\S+@\S+/;
  1085. // If not, check if the e-mail is valid
  1086. if (emailRegex.test (form.email.value) === false) {
  1087. // Return true if user unchecked the subscribe checkbox
  1088. if (getElement (subscribe, true).checked === false) {
  1089. form.email.value = '';
  1090. return true;
  1091. }
  1092. message = '<?php echo $hashover->locale->get ('invalid-email'); ?>';
  1093. showMessage (message, type, permalink, true, isReply, isEdit);
  1094. form.email.focus ();
  1095. return false;
  1096. }
  1097. }
  1098. return true;
  1099. }
  1100. // Validate a comment form e-mail field
  1101. function validateEmail (type, permalink, form, isReply, isEdit)
  1102. {
  1103. type = type || 'main';
  1104. permalink = permalink || null;
  1105. isReply = isReply || false;
  1106. isEdit = isEdit || false;
  1107. var subscribe;
  1108. // Check whether comment is an edit
  1109. if (isEdit === true) {
  1110. // If it is, validate edit form e-mail
  1111. subscribe = 'hashover-subscribe-' + permalink;
  1112. } else {
  1113. // If it is not, validate as primary or reply
  1114. if (isReply !== true) {
  1115. // Validate primary form e-mail
  1116. subscribe = 'hashover-subscribe';
  1117. } else {
  1118. // Validate reply form e-mail
  1119. subscribe = 'hashover-subscribe-' + permalink;
  1120. }
  1121. }
  1122. // Validate form fields
  1123. return emailValidator (form, subscribe, type, permalink, isReply, isEdit);
  1124. }
  1125. // Validate a comment form
  1126. function commentValidator (form, skipComment, isReply)
  1127. {
  1128. skipComment = skipComment || false;
  1129. // Check each input field for if they are required
  1130. for (var field in fieldOptions) {
  1131. // Skip other people's prototypes
  1132. if (fieldOptions.hasOwnProperty (field) !== true) {
  1133. continue;
  1134. }
  1135. // Check if the field is required, and that the input exists
  1136. if (fieldOptions[field] === 'required' && form[field] !== undefined) {
  1137. // Check if it has a value
  1138. if (form[field].value === '') {
  1139. // If not, add a class indicating a failed post
  1140. addClass (form[field], 'hashover-emphasized-input');
  1141. // Focus the input
  1142. form[field].focus ();
  1143. // Return error message to display to the user
  1144. return locale.fieldNeeded.replace ('%s', locale[field]);
  1145. }
  1146. // Remove class indicating a failed post
  1147. removeClass (form[field], 'hashover-emphasized-input');
  1148. }
  1149. }
  1150. // Check if a comment was given
  1151. if (skipComment !== true && form.comment.value === '') {
  1152. // If not, add a class indicating a failed post
  1153. addClass (form.comment, 'hashover-emphasized-input');
  1154. // Focus the comment textarea
  1155. form.comment.focus ();
  1156. // Error message to display to the user
  1157. if (isReply === true) {
  1158. var errorMessage = '<?php echo $hashover->locale->get ('reply-needed'); ?>';
  1159. } else {
  1160. var errorMessage = '<?php echo $hashover->locale->get ('comment-needed'); ?>';
  1161. }
  1162. // Return a error message to display to the user
  1163. return errorMessage;
  1164. }
  1165. return true;
  1166. }
  1167. // Validate required comment credentials
  1168. function validateComment (skipComment, form, type, permalink, isReply, isEdit)
  1169. {
  1170. skipComment = skipComment || false;
  1171. type = type || 'main';
  1172. permalink = permalink || null;
  1173. isReply = isReply || false;
  1174. isEdit = isEdit || false;
  1175. // Validate comment form
  1176. var message = commentValidator (form, skipComment, isReply);
  1177. // Display the validator's message
  1178. if (message !== true) {
  1179. showMessage (message, type, permalink, true, isReply, isEdit);
  1180. return false;
  1181. }
  1182. // Validate e-mail if user isn't logged in or is editing
  1183. if (userIsLoggedIn === false || isEdit === true) {
  1184. // Return false on any failure
  1185. if (validateEmail (type, permalink, form, isReply, isEdit) === false) {
  1186. return false;
  1187. }
  1188. }
  1189. return true;
  1190. }
  1191. // For posting comments, both traditionally and via AJAX
  1192. function postComment (destination, form, button, callback, type, permalink, close, isReply, isEdit)
  1193. {
  1194. type = type || 'main';
  1195. permalink = permalink || '';
  1196. close = close || null;
  1197. isReply = isReply || false;
  1198. isEdit = isEdit || false;
  1199. // Return false if comment is invalid
  1200. if (validateComment (false, form, type, permalink, isReply, isEdit) === false) {
  1201. return false;
  1202. }
  1203. // Disable button
  1204. setTimeout (function () {
  1205. button.disabled = true;
  1206. }, 500);
  1207. <?php if ($hashover->setup->usesAJAX !== false): ?>
  1208. var httpRequest = new XMLHttpRequest ();
  1209. var formElements = form.elements;
  1210. var elementsLength = formElements.length;
  1211. var queries = [];
  1212. // AJAX response handler
  1213. function commentHandler ()
  1214. {
  1215. // Do nothing if request wasn't successful in a meaningful way
  1216. if (this.readyState !== 4 || this.status !== 200) {
  1217. return;
  1218. }
  1219. // Parse AJAX response as JSON
  1220. var json = JSON.parse (this.responseText);
  1221. var scrollToElement;
  1222. // Check if JSON includes a comment
  1223. if (json.comment !== undefined) {
  1224. // If so, execute callback function
  1225. callback (json, permalink, destination, isReply);
  1226. // Execute callback function if one was provided
  1227. if (close !== null) {
  1228. close ();
  1229. }
  1230. // Scroll comment into view
  1231. scrollToElement = getElement (json.comment.permalink, true);
  1232. scrollToElement.scrollIntoView ({ behavior: 'smooth' });
  1233. // Clear form
  1234. form.comment.value = '';
  1235. } else {
  1236. // If not, display the message return instead
  1237. showMessage (json.message, type, permalink, (json.type === 'error'), isReply, isEdit);
  1238. return false;
  1239. }
  1240. // Re-enable button on success
  1241. setTimeout (function () {
  1242. button.disabled = false;
  1243. }, 1000);
  1244. }
  1245. // Sends a request to post a comment
  1246. function sendRequest ()
  1247. {
  1248. // Handle AJAX request return data
  1249. httpRequest.onreadystatechange = commentHandler;
  1250. // Send post comment request
  1251. httpRequest.open ('POST', form.action, true);
  1252. httpRequest.setRequestHeader ('Content-type', 'application/x-www-form-urlencoded');
  1253. httpRequest.send (queries.join ('&'));
  1254. }
  1255. // Get all form input names and values
  1256. for (var i = 0; i < elementsLength; i++) {
  1257. // Skip login/logout input
  1258. if (formElements[i].name === 'login' || formElements[i].name === 'logout') {
  1259. continue;
  1260. }
  1261. // Skip unchecked checkboxes
  1262. if (formElements[i].type === 'checkbox' && formElements[i].checked !== true) {
  1263. continue;
  1264. }
  1265. // Skip delete input
  1266. if (formElements[i].name === 'delete') {
  1267. continue;
  1268. }
  1269. // Add query to queries array
  1270. queries.push (formElements[i].name + '=' + encodeURIComponent (formElements[i].value));
  1271. }
  1272. // Add AJAX query to queries array
  1273. queries.push ('ajax=yes');
  1274. <?php if ($hashover->setup->usesAutoLogin !== false): ?>
  1275. // Check if the user is logged in
  1276. if (userIsLoggedIn !== true || isEdit === true) {
  1277. // If not, send a login request
  1278. var loginRequest = new XMLHttpRequest ();
  1279. var loginQueries = queries.concat (['login=Login']);
  1280. // Handle AJAX request return data
  1281. loginRequest.onreadystatechange = function ()
  1282. {
  1283. // Do nothing if request wasn't successful in a meaningful way
  1284. if (this.readyState !== 4 || this.status !== 200) {
  1285. return;
  1286. }
  1287. // Send post comment request after login
  1288. userIsLoggedIn = true;
  1289. sendRequest ();
  1290. };
  1291. // Send login request
  1292. loginRequest.open ('POST', form.action, true);
  1293. loginRequest.setRequestHeader ('Content-type', 'application/x-www-form-urlencoded');
  1294. loginRequest.send (loginQueries.join ('&'));
  1295. } else {
  1296. // If so, send post comment request normally
  1297. sendRequest ();
  1298. }
  1299. <?php else: ?>
  1300. // Send post comment request
  1301. sendRequest ();
  1302. <?php endif; ?>
  1303. // Re-enable button after 20 seconds
  1304. setTimeout (function () {
  1305. // Abort unfinish request
  1306. httpRequest.abort ();
  1307. // Re-enable button
  1308. button.disabled = false;
  1309. }, 20000);
  1310. return false;
  1311. <?php else: ?>
  1312. // Re-enable button after 20 seconds
  1313. setTimeout (function () {
  1314. button.disabled = false;
  1315. }, 20000);
  1316. return true;
  1317. <?php endif; ?>
  1318. }
  1319. <?php if ($hashover->setup->usesAJAX !== false): ?>
  1320. // Converts an HTML string to DOM NodeList
  1321. function HTMLToNodeList (html)
  1322. {
  1323. return createElement ('div', { innerHTML: html }).childNodes;
  1324. }
  1325. // Increase comment counts
  1326. function incrementCounts (isReply)
  1327. {
  1328. // Count top level comments
  1329. if (isReply === false) {
  1330. primaryCount++;
  1331. }
  1332. // Increase all count
  1333. totalCount++;
  1334. }
  1335. // For adding new comments to comments array
  1336. function addComments (comment, isReply, index)
  1337. {
  1338. isReply = isReply || false;
  1339. index = index || null;
  1340. // Check that comment is not a reply
  1341. if (isReply !== true) {
  1342. // If so, add to primary comments
  1343. if (index !== null) {
  1344. PHPContent.comments.splice (index, 0, comment);
  1345. return;
  1346. }
  1347. PHPContent.comments.push (comment);
  1348. return;
  1349. }
  1350. // If not, fetch parent comment
  1351. var parentPermalink = getParentPermalink (comment.permalink);
  1352. var parent = findByPermalink (parentPermalink, PHPContent.comments);
  1353. // Check if the parent comment exists
  1354. if (parent !== null) {
  1355. // If so, check if comment has replies
  1356. if (parent.replies !== undefined) {
  1357. // If so, add comment to reply array
  1358. if (index !== null) {
  1359. parent.replies.splice (index, 0, comment);
  1360. return;
  1361. }
  1362. parent.replies.push (comment);
  1363. return;
  1364. }
  1365. // If not, create reply array
  1366. parent.replies = [comment];
  1367. }
  1368. // Otherwise, add to primary comments
  1369. PHPContent.comments.push (comment);
  1370. }
  1371. // For posting comments
  1372. AJAXPost = function (json, permalink, destination, isReply)
  1373. {
  1374. // If there aren't any comments, replace first comment message
  1375. if (totalCount === 0) {
  1376. PHPContent.comments[0] = json.comment;
  1377. destination.innerHTML = parseComment (json.comment);
  1378. } else {
  1379. // Add comment to comments array
  1380. addComments (json.comment, isReply);
  1381. // Create div element for comment
  1382. var commentNode = HTMLToNodeList (parseComment (json.comment));
  1383. // Append comment to parent element
  1384. if (streamMode === true && permalink.split('r').length > streamDepth) {
  1385. destination.parentNode.insertBefore (commentNode[0], destination.nextSibling);
  1386. } else {
  1387. destination.appendChild (commentNode[0]);
  1388. }
  1389. }
  1390. // Add controls to the new comment
  1391. addControls (json.comment);
  1392. // Update comment count
  1393. getElement ('hashover-count').textContent = json.count;
  1394. incrementCounts (isReply);
  1395. };
  1396. // For editing comments
  1397. AJAXEdit = function (json, permalink, destination, isReply)
  1398. {
  1399. // Get old comment element nodes
  1400. var comment = getElement (permalink, true);
  1401. var oldNodes = comment.childNodes;
  1402. var oldComment = findByPermalink (permalink, PHPContent.comments);
  1403. // Get new comment element nodes
  1404. var newNodes = HTMLToNodeList (parseComment (json.comment));
  1405. newNodes = newNodes[0].childNodes;
  1406. // Replace old comment with edited comment
  1407. for (var i = 0, il = newNodes.length; i < il; i++) {
  1408. if (typeof (oldNodes[i]) === 'object'
  1409. && typeof (newNodes[i]) === 'object')
  1410. {
  1411. comment.replaceChild (newNodes[i], oldNodes[i]);
  1412. }
  1413. }
  1414. // Add controls back to the comment
  1415. addControls (json.comment);
  1416. // Update old in array comment with edited comment
  1417. for (var attribute in json.comment) {
  1418. oldComment[attribute] = json.comment[attribute];
  1419. }
  1420. };
  1421. <?php endif; ?>
  1422. // Attach click event to accepted HTML revealer hyperlinks
  1423. function acceptedHTMLOnclick (type, permalink)
  1424. {
  1425. permalink = (permalink !== undefined) ? '-' + permalink : '';
  1426. // Get accepted HTML message elements
  1427. var acceptedHTMLID = 'hashover-' + type + '-formatting';
  1428. var acceptedHTML = getElement (acceptedHTMLID + permalink, true);
  1429. var acceptedHTMLMessage = getElement (acceptedHTMLID + '-message' + permalink, true);
  1430. // Attach click event to accepted HTML revealer hyperlink
  1431. acceptedHTML.onclick = function ()
  1432. {
  1433. if (containsClass (acceptedHTMLMessage, 'hashover-message-open')) {
  1434. closeMessage (acceptedHTMLMessage);
  1435. return false;
  1436. }
  1437. openMessage (acceptedHTMLMessage);
  1438. return false;
  1439. }
  1440. }
  1441. // Displays reply form
  1442. function hashoverReply (permalink)
  1443. {
  1444. // Get reply link element
  1445. var link = getElement ('hashover-reply-link-' + permalink, true);
  1446. // Get file
  1447. var file = fileFromPermalink (permalink);
  1448. // Create reply form element
  1449. var form = createElement ('form', {
  1450. id: 'hashover-reply-' + permalink,
  1451. className: 'hashover-reply-form',
  1452. method: 'post',
  1453. action: httpScripts + '/postcomments.php'
  1454. });
  1455. <?php
  1456. echo $hashover->html->asJSVar ($hashover->html->replyForm ('permalink', 'file'), 'formHTML', "\t\t");
  1457. ?>
  1458. // Place reply fields into form
  1459. form.innerHTML = formHTML;
  1460. // Prevent input submission
  1461. preventSubmit (form);
  1462. // Add form to page
  1463. var replyForm = getElement ('hashover-placeholder-reply-form-' + permalink, true);
  1464. replyForm.appendChild (form);
  1465. // Change "Reply" link to "Cancel" link
  1466. cancelSwitcher ('reply', link, replyForm, permalink);
  1467. // Attach event listeners to "Post Reply" button
  1468. var postReply = getElement ('hashover-reply-post-' + permalink, true);
  1469. // Get the element of comment being replied to
  1470. var destination = getElement (permalink, true);
  1471. // Attach click event to accepted HTML revealer hyperlink
  1472. acceptedHTMLOnclick ('reply', permalink);
  1473. // Onclick
  1474. postReply.onclick = function ()
  1475. {
  1476. return postComment (destination, form, this, AJAXPost, 'reply', permalink, link.onclick, true, false);
  1477. };
  1478. // Onsubmit
  1479. postReply.onsubmit = function ()
  1480. {
  1481. return postComment (destination, form, this, AJAXPost, 'reply', permalink, link.onclick, true, false);
  1482. };
  1483. // Focus comment field
  1484. form.comment.focus ();
  1485. return true;
  1486. }
  1487. // Displays edit form
  1488. function hashoverEdit (comment)
  1489. {
  1490. if (comment['user-owned'] !== true) {
  1491. return false;
  1492. }
  1493. // Get permalink from comment JSON object
  1494. var permalink = comment.permalink;
  1495. // Get edit link element
  1496. var link = getElement ('hashover-edit-link-' + permalink, true);
  1497. // Get file
  1498. var file = fileFromPermalink (permalink);
  1499. // Get name and website
  1500. var name = comment.name || '';
  1501. var website = comment.website || '';
  1502. // Get and clean comment body
  1503. var body = comment.body.replace (linkRegex, '$1');
  1504. // Create edit form element
  1505. var form = createElement ('form', {
  1506. id: 'hashover-edit-' + permalink,
  1507. className: 'hashover-edit-form',
  1508. method: 'post',
  1509. action: httpScripts + '/postcomments.php'
  1510. });
  1511. <?php
  1512. echo $hashover->html->asJSVar ($hashover->html->editForm ('permalink', 'file', 'name', 'website', 'body'), 'formHTML', "\t\t");
  1513. ?>
  1514. // Place edit form fields into form
  1515. form.innerHTML = formHTML;
  1516. // Prevent input submission
  1517. preventSubmit (form);
  1518. // Add edit form to page
  1519. var editForm = getElement ('hashover-placeholder-edit-form-' + permalink, true);
  1520. editForm.appendChild (form);
  1521. // Set status dropdown menu option to comment status
  1522. ifElement ('hashover-edit-status-' + permalink, function (status) {
  1523. if (comment.status !== undefined) {
  1524. status.selectedIndex = commentStatuses.indexOf (comment.status);
  1525. }
  1526. });
  1527. // Blank out password field
  1528. setTimeout (function () {
  1529. if (form.password !== undefined) {
  1530. form.password.value = '';
  1531. }
  1532. }, 100);
  1533. // Uncheck subscribe checkbox if user isn't subscribed
  1534. if (comment.subscribed !== true) {
  1535. getElement ('hashover-subscribe-' + permalink, true).checked = null;
  1536. }
  1537. // Displays onClick confirmation dialog for comment deletion
  1538. getElement ('hashover-edit-delete-' + permalink, true).onclick = function ()
  1539. {
  1540. return confirm ('<?php echo $hashover->locale->get ('delete-comment'); ?>');
  1541. };
  1542. // Change "Edit" link to "Cancel" link
  1543. cancelSwitcher ('edit', link, editForm, permalink);
  1544. // Attach event listeners to "Save Edit" button
  1545. var saveEdit = getElement ('hashover-edit-post-' + permalink, true);
  1546. // Get the element of comment being replied to
  1547. var destination = getElement (permalink, true);
  1548. // Attach click event to accepted HTML revealer hyperlink
  1549. acceptedHTMLOnclick ('edit', permalink);
  1550. // Onclick
  1551. saveEdit.onclick = function ()
  1552. {
  1553. return postComment (destination, form, this, AJAXEdit, 'edit', permalink, link.onclick, false, true);
  1554. };
  1555. // Onsubmit
  1556. saveEdit.onsubmit = function ()
  1557. {
  1558. return postComment (destination, form, this, AJAXEdit, 'edit', permalink, link.onclick, false, true);
  1559. };
  1560. return false;
  1561. }
  1562. <?php if ($hashover->setup->collapsesComments !== false): ?>
  1563. // For showing more comments, via AJAX or removing a class
  1564. function hideMoreLink (finishedCallback)
  1565. {
  1566. finishedCallback = finishedCallback || null;
  1567. // Add class to hide the more hyperlink
  1568. addClass (moreLink, 'hashover-hide-more-link');
  1569. setTimeout (function () {
  1570. // Remove the more hyperlink from page
  1571. if (sortDiv.contains (moreLink) === true) {
  1572. sortDiv.removeChild (moreLink);
  1573. }
  1574. // Show comment count and sort options
  1575. getElement ('hashover-count-wrapper').style.display = '';
  1576. // Show popular comments section
  1577. ifElement ('hashover-popular-section', function (popularSection) {
  1578. popularSection.style.display = '';
  1579. });
  1580. // Get each hidden comment element
  1581. var collapsed = sortDiv.getElementsByClassName ('hashover-hidden');
  1582. // Remove hidden comment class from each comment
  1583. for (var i = collapsed.length - 1; i >= 0; i--) {
  1584. removeClass (collapsed[i], 'hashover-hidden');
  1585. }
  1586. // Execute callback function
  1587. if (finishedCallback !== null) {
  1588. finishedCallback ();
  1589. }
  1590. }, 350);
  1591. }
  1592. <?php if ($hashover->setup->usesAJAX !== false): ?>
  1593. // For appending new comments to the thread on page
  1594. function appendComments (comments)
  1595. {
  1596. var comment;
  1597. var isReply;
  1598. var element;
  1599. var parent;
  1600. for (var i = 0, il = comments.length; i < il; i++) {
  1601. // Skip existing comments
  1602. if (findByPermalink (comments[i].permalink, PHPContent.comments) !== null) {
  1603. // Check comment's replies
  1604. if (comments[i].replies !== undefined) {
  1605. appendComments (comments[i].replies);
  1606. }
  1607. continue;
  1608. }
  1609. // Check if comment is a reply
  1610. isReply = (comments[i].permalink.indexOf ('r') > -1);
  1611. // Add comment to comments array
  1612. addComments (comments[i], isReply, i);
  1613. // Parse comment, convert HTML to DOM node
  1614. comment = HTMLToNodeList (parseComment (comments[i], null, true));
  1615. // Check that comment is not a reply
  1616. if (isReply !== true) {
  1617. // If so, append to primary comments
  1618. element = moreDiv;
  1619. } else {
  1620. // If not, append to its parent's element
  1621. parent = getParentPermalink (comments[i].permalink, true);
  1622. element = getElement (parent, true) || moreDiv;
  1623. }
  1624. // Otherwise append it to the primary element
  1625. element.appendChild (comment[0]);
  1626. // Add controls to the comment
  1627. addControls (comments[i]);
  1628. }
  1629. }
  1630. <?php endif; ?>
  1631. // onClick event for more button
  1632. function showMoreComments (element, finishedCallback)
  1633. {
  1634. finishedCallback = finishedCallback || null;
  1635. // Do nothing if already showing all comments
  1636. if (showingMore === true) {
  1637. // Execute callback function
  1638. if (finishedCallback !== null) {
  1639. finishedCallback ();
  1640. }
  1641. return false;
  1642. }
  1643. <?php if ($hashover->setup->usesAJAX !== false): ?>
  1644. var httpRequest = new XMLHttpRequest ();
  1645. var queries = ['url=' + encodeURIComponent (pageURL), 'start=' + collapseLimit, 'ajax=yes'];
  1646. // Handle AJAX request return data
  1647. httpRequest.onreadystatechange = function ()
  1648. {
  1649. // Do nothing if request wasn't successful in a meaningful way
  1650. if (this.readyState !== 4 || this.status !== 200) {
  1651. return;
  1652. }
  1653. // Parse AJAX response as JSON
  1654. var json = JSON.parse (this.responseText);
  1655. // Display the comments
  1656. appendComments (json.comments);
  1657. // Remove loading class from element
  1658. removeClass (element, 'hashover-loading');
  1659. // Hide the more hyperlink and display the comments
  1660. hideMoreLink (finishedCallback);
  1661. };
  1662. // Open and send request
  1663. httpRequest.open ('POST', httpRoot + '/api/json.php', true);
  1664. httpRequest.setRequestHeader ('Content-type', 'application/x-www-form-urlencoded');
  1665. httpRequest.send (queries.join ('&'));
  1666. // Set class to indicate loading to element
  1667. addClass (element, 'hashover-loading');
  1668. <?php else: ?>
  1669. // Hide the more hyperlink and display the comments
  1670. hideMoreLink (finishedCallback);
  1671. <?php endif; ?>
  1672. // Set all comments as shown
  1673. showingMore = true;
  1674. return false;
  1675. }
  1676. <?php endif; ?>
  1677. // Callback to close the embedded image
  1678. function closeEmbeddedImage (image) {
  1679. // Reset source
  1680. image.src = image.dataset.placeholder;
  1681. // Reset title
  1682. image.title = locale.externalImageTip;
  1683. // Remove loading class from wrapper
  1684. removeClass (image.parentNode, 'hashover-loading');
  1685. }
  1686. // Onclick callback function for embedded images
  1687. function embeddedImageCallback ()
  1688. {
  1689. // If embedded image is open, close it and return false
  1690. if (this.src === this.dataset.url) {
  1691. closeEmbeddedImage (this);
  1692. return false;
  1693. }
  1694. // Set title
  1695. this.title = '<?php echo $hashover->locale->get ('loading'); ?>';
  1696. // Add loading class to wrapper
  1697. addClass (this.parentNode, 'hashover-loading');
  1698. // Change title and remove load event handler once image is loaded
  1699. this.onload = function ()
  1700. {
  1701. this.title = '<?php echo $hashover->locale->get ('click-to-close'); ?>';
  1702. this.onload = null;
  1703. // Remove loading class from wrapper
  1704. removeClass (this.parentNode, 'hashover-loading');
  1705. };
  1706. // Close embedded image if any error occurs
  1707. this.onerror = function ()
  1708. {
  1709. closeEmbeddedImage (this);
  1710. };
  1711. // Set placeholder image to embedded source
  1712. this.src = this.dataset.url;
  1713. }
  1714. // Changes Element.textContent onmouseover and reverts onmouseout
  1715. function mouseOverChanger (element, over, out)
  1716. {
  1717. if (over === null || out === null) {
  1718. element.onmouseover = null;
  1719. element.onmouseout = null;
  1720. return false;
  1721. }
  1722. element.onmouseover = function ()
  1723. {
  1724. this.textContent = over;
  1725. };
  1726. element.onmouseout = function ()
  1727. {
  1728. this.textContent = out;
  1729. };
  1730. }
  1731. <?php if ($likesOrDislikes !== false): ?>
  1732. // For liking comments
  1733. function likeComment (action, permalink)
  1734. {
  1735. // Get file
  1736. var file = fileFromPermalink (permalink);
  1737. var actionLink = getElement ('hashover-' + action + '-' + permalink, true);
  1738. var likesElement = getElement ('hashover-' + action + 's-' + permalink, true);
  1739. var dislikesClass = (action === 'like') ? '<?php if ($allowsDislikes === true) echo ' hashover-dislikes-enabled'; ?>' : '';
  1740. // Load "like.php"
  1741. var like = new XMLHttpRequest ();
  1742. var queries;
  1743. // When loaded update like count
  1744. like.onreadystatechange = function ()
  1745. {
  1746. var likeResponse;
  1747. var likesKey, likes = 0;
  1748. // Do nothing if request wasn't successful in a meaningful way
  1749. if (this.readyState !== 4 || this.status !== 200) {
  1750. return;
  1751. }
  1752. // Get JSON response
  1753. likeResponse = JSON.parse (this.responseText);
  1754. // If a message is returned display it to the user
  1755. if (likeResponse.message !== undefined) {
  1756. alert (likeResponse.message);
  1757. return;
  1758. }
  1759. // If an error is returned display a stand error to the user
  1760. if (likeResponse.error !== undefined) {
  1761. alert ('Error! Something went wrong!');
  1762. return;
  1763. }
  1764. // Get number of likes
  1765. likesKey = (action !== 'dislikes') ? 'likes' : 'dislikes';
  1766. likes = likeResponse[likesKey] || 0;
  1767. // Change "Like" button title and class
  1768. if (actionLink.className === 'hashover-' + action + dislikesClass) {
  1769. // Change class to indicate the comment has been liked/disliked
  1770. actionLink.className = 'hashover-' + action + 'd' + dislikesClass;
  1771. actionLink.title = (action === 'like') ? locale.likedComment : locale.dislikedComment;
  1772. actionLink.textContent = (action === 'like') ? locale.liked : locale.disliked;
  1773. // Add listener to change link text to "Unlike" on mouse over
  1774. if (action === 'like') {
  1775. mouseOverChanger (actionLink, locale.unlike, locale.liked);
  1776. }
  1777. } else {
  1778. // Change class to indicate the comment is unliked
  1779. actionLink.className = 'hashover-' + action + dislikesClass;
  1780. actionLink.title = (action === 'like') ? locale.likeComment : locale.dislikeComment;
  1781. actionLink.textContent = (action === 'like') ? locale.like[0] : locale.dislike[0];
  1782. // Add listener to change link text to "Unlike" on mouse over
  1783. if (action === 'like') {
  1784. mouseOverChanger (actionLink, null, null);
  1785. }
  1786. }
  1787. if (likes > 0) {
  1788. // Locale plural key
  1789. var plural = (likes !== 1) ? 1 : 0;
  1790. if (action === 'like') {
  1791. var likeCount = likes + ' ' + locale.like[plural];
  1792. } else {
  1793. var likeCount = likes + ' ' + locale.dislike[plural];
  1794. }
  1795. // Change number of likes; set font weight bold
  1796. likesElement.textContent = likeCount;
  1797. likesElement.style.fontWeight = 'bold';
  1798. } else {
  1799. // Remove like count; set font weight normal
  1800. likesElement.textContent = '';
  1801. likesElement.style.fontWeight = '';
  1802. }
  1803. };
  1804. // Set request queries
  1805. queries = 'url=' + encodeURIComponent (pageURL);
  1806. queries += '&thread=<?php echo $hashover->misc->jsEscape ($hashover->setup->threadDirectory); ?>';
  1807. queries += '&comment=' + file;
  1808. queries += '&action=' + action;
  1809. // Send request
  1810. like.open ('POST', httpScripts + '/like.php', true);
  1811. like.setRequestHeader ('Content-type', 'application/x-www-form-urlencoded');
  1812. like.send (queries);
  1813. }
  1814. <?php endif; ?>
  1815. // Add various events to various elements in each comment
  1816. function addControls (json, popular)
  1817. {
  1818. function stepIntoReplies ()
  1819. {
  1820. if (json.replies !== undefined) {
  1821. for (var reply = 0, total = json.replies.length; reply < total; reply++) {
  1822. addControls (json.replies[reply]);
  1823. }
  1824. }
  1825. }
  1826. if (json.notice !== undefined) {
  1827. stepIntoReplies ();
  1828. return false;
  1829. }
  1830. // Get permalink from JSON object
  1831. var permalink = json.permalink;
  1832. // Get embedded image elements
  1833. var embeddedImgs = document.getElementsByClassName ('hashover-embedded-image');
  1834. // Set onclick functions for external images
  1835. for (var i = 0, il = embeddedImgs.length; i < il; i++) {
  1836. embeddedImgs[i].onclick = embeddedImageCallback;
  1837. }
  1838. <?php if ($hashover->setup->collapsesComments !== false): ?>
  1839. // Get thread link of comment
  1840. ifElement ('hashover-thread-link-' + permalink, function (threadLink) {
  1841. // Add onClick event to thread hyperlink
  1842. threadLink.onclick = function ()
  1843. {
  1844. showMoreComments (threadLink, function () {
  1845. var parentThread = permalink.replace (threadRegex, '$1');
  1846. var scrollToElement = getElement (parentThread, true);
  1847. // Scroll to the comment
  1848. scrollToElement.scrollIntoView ({ behavior: 'smooth' });
  1849. });
  1850. return false;
  1851. };
  1852. });
  1853. <?php endif; ?>
  1854. // Get reply link of comment
  1855. ifElement ('hashover-reply-link-' + permalink, function (replyLink) {
  1856. // Add onClick event to "Reply" hyperlink
  1857. replyLink.onclick = function ()
  1858. {
  1859. hashoverReply (permalink);
  1860. return false;
  1861. };
  1862. });
  1863. // Check if logged in user owns the comment
  1864. if (json['user-owned'] === true) {
  1865. ifElement ('hashover-edit-link-' + permalink, function (editLink) {
  1866. // Add onClick event to "Edit" hyperlinks
  1867. editLink.onclick = function ()
  1868. {
  1869. hashoverEdit (json);
  1870. return false;
  1871. };
  1872. });
  1873. <?php if ($likesOrDislikes): ?>
  1874. } else {
  1875. <?php if ($allowsLikes !== false): ?>
  1876. ifElement ('hashover-like-' + permalink, function (likeLink) {
  1877. // Add onClick event to "Like" hyperlinks
  1878. likeLink.onclick = function ()
  1879. {
  1880. likeComment ('like', permalink);
  1881. return false;
  1882. };
  1883. if (containsClass (likeLink, 'hashover-liked') === true) {
  1884. mouseOverChanger (likeLink, locale.unlike, locale.liked);
  1885. }
  1886. });
  1887. <?php endif; ?>
  1888. <?php if ($allowsDislikes === true): ?>
  1889. ifElement ('hashover-dislike-' + permalink, function (dislikeLink) {
  1890. // Add onClick event to "Dislike" hyperlinks
  1891. dislikeLink.onclick = function ()
  1892. {
  1893. likeComment ('dislike', permalink);
  1894. return false;
  1895. };
  1896. });
  1897. <?php endif; ?>
  1898. <?php endif; ?>
  1899. }
  1900. // Recursively execute this function on replies
  1901. stepIntoReplies ();
  1902. }
  1903. // Returns a clone of an object
  1904. function cloneObject (object)
  1905. {
  1906. return JSON.parse (JSON.stringify (object));
  1907. }
  1908. // "Flatten" the comments object
  1909. function getAllComments (comments)
  1910. {
  1911. var commentsCopy = cloneObject (comments);
  1912. var output = [];
  1913. function descend (comment)
  1914. {
  1915. output.push (comment);
  1916. if (comment.replies !== undefined) {
  1917. for (var reply = 0, total = comment.replies.length; reply < total; reply++) {
  1918. descend (comment.replies[reply]);
  1919. }
  1920. delete comment.replies;
  1921. }
  1922. }
  1923. for (var comment = 0, total = commentsCopy.length; comment < total; comment++) {
  1924. descend (commentsCopy[comment]);
  1925. }
  1926. return output;
  1927. }
  1928. // Run all comments in array data through parseComment function
  1929. function parseAll (comments, element, collapse, popular, sort, method)
  1930. {
  1931. popular = popular || false;
  1932. sort = sort || false;
  1933. method = method || 'ascending';
  1934. var commentHTML = '';
  1935. // Parse every comment
  1936. for (var comment = 0, total = comments.length; comment < total; comment++) {
  1937. commentHTML += parseComment (comments[comment], null, collapse, sort, method, popular);
  1938. }
  1939. // Add comments to element's innerHTML
  1940. if ('insertAdjacentHTML' in element) {
  1941. element.insertAdjacentHTML ('beforeend', commentHTML);
  1942. } else {
  1943. element.innerHTML = commentHTML;
  1944. }
  1945. // Add control events
  1946. for (var comment = 0, total = comments.length; comment < total; comment++) {
  1947. addControls (comments[comment]);
  1948. }
  1949. }
  1950. // Comment sorting
  1951. function sortComments (method)
  1952. {
  1953. var tmpArray;
  1954. var sortArray;
  1955. function replyPropertySum (comment, callback)
  1956. {
  1957. var sum = 0;
  1958. if (comment.replies !== undefined) {
  1959. for (var i = 0, il = comment.replies.length; i < il; i++) {
  1960. sum += replyPropertySum (comment.replies[i], callback);
  1961. }
  1962. }
  1963. sum += callback (comment);
  1964. return sum;
  1965. }
  1966. function replyCounter (comment)
  1967. {
  1968. return (comment.replies) ? comment.replies.length : 0;
  1969. }
  1970. function netLikes (comment)
  1971. {
  1972. var likes = comment.likes || 0;
  1973. var dislikes = comment.dislikes || 0;
  1974. return likes - dislikes;
  1975. }
  1976. // Sort methods
  1977. switch (method) {
  1978. case 'descending': {
  1979. tmpArray = getAllComments (PHPContent.comments);
  1980. sortArray = tmpArray.reverse ();
  1981. break;
  1982. }
  1983. case 'by-date': {
  1984. sortArray = getAllComments (PHPContent.comments).sort (function (a, b) {
  1985. if (a['sort-date'] === b['sort-date']) {
  1986. return 1;
  1987. }
  1988. return b['sort-date'] - a['sort-date'];
  1989. });
  1990. break;
  1991. }
  1992. case 'by-likes': {
  1993. sortArray = getAllComments (PHPContent.comments).sort (function (a, b) {
  1994. a.likes = a.likes || 0;
  1995. b.likes = b.likes || 0;
  1996. a.dislikes = a.dislikes || 0;
  1997. b.dislikes = b.dislikes || 0;
  1998. return (b.likes - b.dislikes) - (a.likes - a.dislikes);
  1999. });
  2000. break;
  2001. }
  2002. case 'by-replies': {
  2003. tmpArray = cloneObject (PHPContent.comments);
  2004. sortArray = tmpArray.sort (function (a, b) {
  2005. var ac = (!!a.replies) ? a.replies.length : 0;
  2006. var bc = (!!b.replies) ? b.replies.length : 0;
  2007. return bc - ac;
  2008. });
  2009. break;
  2010. }
  2011. case 'by-discussion': {
  2012. tmpArray = cloneObject (PHPContent.comments);
  2013. sortArray = tmpArray.sort (function (a, b) {
  2014. var replyCountA = replyPropertySum (a, replyCounter);
  2015. var replyCountB = replyPropertySum (b, replyCounter);
  2016. return replyCountB - replyCountA;
  2017. });
  2018. break;
  2019. }
  2020. case 'by-popularity': {
  2021. tmpArray = cloneObject (PHPContent.comments);
  2022. sortArray = tmpArray.sort (function (a, b) {
  2023. var likeCountA = replyPropertySum (a, netLikes);
  2024. var likeCountB = replyPropertySum (b, netLikes);
  2025. return likeCountB - likeCountA;
  2026. });
  2027. break;
  2028. }
  2029. case 'by-name': {
  2030. tmpArray = getAllComments (PHPContent.comments);
  2031. sortArray = tmpArray.sort (function (a, b) {
  2032. var nameA = (a.name || defaultName).toLowerCase ();
  2033. var nameB = (b.name || defaultName).toLowerCase ();
  2034. nameA = (nameA.charAt (0) === '@') ? nameA.slice (1) : nameA;
  2035. nameB = (nameB.charAt (0) === '@') ? nameB.slice (1) : nameB;
  2036. if (nameA > nameB) {
  2037. return 1;
  2038. }
  2039. if (nameA < nameB) {
  2040. return -1;
  2041. }
  2042. return 0;
  2043. });
  2044. break;
  2045. }
  2046. case 'threaded-descending': {
  2047. tmpArray = cloneObject (PHPContent.comments);
  2048. sortArray = tmpArray.reverse ();
  2049. break;
  2050. }
  2051. case 'threaded-by-date': {
  2052. tmpArray = cloneObject (PHPContent.comments);
  2053. sortArray = tmpArray.sort (function (a, b) {
  2054. if (a['sort-date'] === b['sort-date']) {
  2055. return 1;
  2056. }
  2057. return b['sort-date'] - a['sort-date'];
  2058. });
  2059. break;
  2060. }
  2061. case 'threaded-by-likes': {
  2062. tmpArray = cloneObject (PHPContent.comments);
  2063. sortArray = tmpArray.sort (function (a, b) {
  2064. a.likes = a.likes || 0;
  2065. b.likes = b.likes || 0;
  2066. a.dislikes = a.dislikes || 0;
  2067. b.dislikes = b.dislikes || 0;
  2068. return (b.likes - b.dislikes) - (a.likes - a.dislikes);
  2069. });
  2070. break;
  2071. }
  2072. case 'threaded-by-name': {
  2073. tmpArray = cloneObject (PHPContent.comments);
  2074. sortArray = tmpArray.sort (function (a, b) {
  2075. var nameA = (a.name || defaultName).toLowerCase ();
  2076. var nameB = (b.name || defaultName).toLowerCase ();
  2077. nameA = (nameA.charAt (0) === '@') ? nameA.slice (1) : nameA;
  2078. nameB = (nameB.charAt (0) === '@') ? nameB.slice (1) : nameB;
  2079. if (nameA > nameB) {
  2080. return 1;
  2081. }
  2082. if (nameA < nameB) {
  2083. return -1;
  2084. }
  2085. return 0;
  2086. });
  2087. break;
  2088. }
  2089. default: {
  2090. sortArray = PHPContent.comments;
  2091. break;
  2092. }
  2093. }
  2094. parseAll (sortArray, sortDiv, false, false, true, method);
  2095. }
  2096. <?php if ($hashover->setup->appendsCSS !== false): ?>
  2097. // Check if comment theme stylesheet is already in page head
  2098. if (typeof (document.querySelector) === 'function') {
  2099. appendCSS = !document.querySelector ('link[href="' + themeCSS + '"]');
  2100. } else {
  2101. // Fallback for old web browsers without querySelector
  2102. var links = head.getElementsByTagName ('link');
  2103. for (var i = 0, il = links.length; i < il; i++) {
  2104. if (links[i].getAttribute ('href') === themeCSS) {
  2105. appendCSS = false;
  2106. break;
  2107. }
  2108. }
  2109. }
  2110. // Create link element for comment stylesheet
  2111. if (appendCSS === true) {
  2112. var css = createElement ('link', {
  2113. rel: 'stylesheet',
  2114. href: themeCSS,
  2115. type: 'text/css',
  2116. });
  2117. // Append comment stylesheet link element to page head
  2118. head.appendChild (css);
  2119. }
  2120. <?php endif; ?>
  2121. // Put number of comments into "hashover-comment-count" identified HTML element
  2122. if (totalCount !== 0) {
  2123. ifElement ('hashover-comment-count', function (countElement) {
  2124. countElement.textContent = totalCount;
  2125. });
  2126. <?php if ($hashover->setup->APIStatus ('rss') !== 'disabled'): ?>
  2127. // Create link element for comment RSS feed
  2128. var rss = createElement ('link', {
  2129. rel: 'alternate',
  2130. href: httpRoot + '/api/rss.php?url=' + encodeURIComponent (URLHref),
  2131. type: 'application/rss+xml',
  2132. title: 'Comments'
  2133. });
  2134. // Append comment RSS feed link element to page head
  2135. head.appendChild (rss);
  2136. <?php endif; ?>
  2137. }
  2138. // Initial HTML
  2139. <?php
  2140. $initialHTML = $hashover->html->initialHTML ($hashover->popularList, false);
  2141. echo $hashover->html->asJSVar ($initialHTML, 'initialHTML');
  2142. ?>
  2143. // Create div tag for HashOver comments to appear in
  2144. if (HashOverDiv === null) {
  2145. HashOverDiv = createElement ('div', { id: 'hashover' });
  2146. // Place HashOver element on page
  2147. if (hashoverScript !== false) {
  2148. var thisScript = getElement ('hashover-script-' + hashoverScript);
  2149. thisScript.parentNode.insertBefore (HashOverDiv, thisScript);
  2150. } else {
  2151. document.body.appendChild (HashOverDiv);
  2152. }
  2153. }
  2154. // Add class for differentiating desktop and mobile styling
  2155. HashOverDiv.className = 'hashover-' + deviceType;
  2156. // Add class to indicate user login status
  2157. if (userIsLoggedIn === true) {
  2158. addClass (HashOverDiv, 'hashover-logged-in');
  2159. } else {
  2160. addClass (HashOverDiv, 'hashover-logged-out');
  2161. }
  2162. // Add initial HTML to page
  2163. if ('insertAdjacentHTML' in HashOverDiv) {
  2164. HashOverDiv.insertAdjacentHTML ('beforeend', initialHTML);
  2165. } else {
  2166. HashOverDiv.innerHTML = initialHTML;
  2167. }
  2168. // Content passed from PHP
  2169. var PHPContent = HASHOVER_PHP_CONTENT;
  2170. // Get sort div element
  2171. sortDiv = getElement ('hashover-sort-div');
  2172. // Get primary form element
  2173. HashOverForm = getElement ('hashover-form');
  2174. // Display most popular comments
  2175. ifElement ('hashover-top-comments', function (topComments) {
  2176. if (PHPContent.popularComments[0] !== undefined) {
  2177. parseAll (PHPContent.popularComments, topComments, false, true);
  2178. }
  2179. });
  2180. // Add initial event handlers
  2181. parseAll (PHPContent.comments, sortDiv, collapseComments);
  2182. <?php if ($hashover->setup->collapsesUI !== false): ?>
  2183. // Decide button text
  2184. var uncollapseText = (totalCount >= 1) ? '<?php echo $show_number_comments; ?>' : locale.postCommentOn;
  2185. // Create hyperlink to uncollapse the comment UI
  2186. var uncollapseUILink = createElement ('a', {
  2187. href: '#',
  2188. className: 'hashover-more-link',
  2189. title: uncollapseText,
  2190. textContent: uncollapseText,
  2191. onclick: function () {
  2192. // Add class to hide the uncollapse UI hyperlink
  2193. addClass (this, 'hashover-hide-more-link');
  2194. setTimeout (function () {
  2195. // Remove the uncollapse UI hyperlink from page
  2196. if (HashOverDiv.contains (uncollapseUILink) === true) {
  2197. HashOverDiv.removeChild (uncollapseUILink);
  2198. }
  2199. // Element to unhide
  2200. var uncollapseIDs = [
  2201. 'hashover-form-section',
  2202. 'hashover-comments-section',
  2203. 'hashover-end-links'
  2204. ];
  2205. // Show hidden form elements
  2206. for (var i = 0, il = uncollapseIDs.length; i < il; i++) {
  2207. ifElement (uncollapseIDs[i], function (element) {
  2208. element.style.display = '';
  2209. });
  2210. }
  2211. // Show popular comments section
  2212. if (collapseLimit > 0) {
  2213. ifElement ('hashover-popular-section', function (popularSection) {
  2214. popularSection.style.display = '';
  2215. });
  2216. }
  2217. }, 350);
  2218. return false;
  2219. }
  2220. });
  2221. // Add uncollapse hyperlink to HashOver div
  2222. HashOverDiv.appendChild (uncollapseUILink);
  2223. <?php endif; ?>
  2224. <?php if ($hashover->setup->collapsesComments !== false): ?>
  2225. // More button text
  2226. var moreLinkText = '<?php echo $more_link_text; ?>';
  2227. // Check whether there are more than the collapse limit
  2228. if (totalCount > collapseLimit) {
  2229. // Create element for the comments
  2230. moreDiv = createElement ('div', {
  2231. className: 'hashover-more-section'
  2232. });
  2233. // If so, create "More Comments" hyperlink
  2234. moreLink = createElement ('a', {
  2235. href: '#',
  2236. className: 'hashover-more-link',
  2237. title: moreLinkText,
  2238. textContent: moreLinkText,
  2239. onclick: function () {
  2240. return showMoreComments (this);
  2241. }
  2242. });
  2243. // Add more button link to sort div
  2244. sortDiv.appendChild (moreDiv);
  2245. // Add more button link to sort div
  2246. sortDiv.appendChild (moreLink);
  2247. } else {
  2248. // If not, consider all comments shown
  2249. showingMore = true;
  2250. }
  2251. <?php endif; ?>
  2252. // Attach click event to accepted HTML revealer hyperlink
  2253. acceptedHTMLOnclick ('main');
  2254. // Attach event listeners to "Post Comment" button
  2255. var postButton = getElement ('hashover-post-button');
  2256. // Onclick
  2257. postButton.onclick = function ()
  2258. {
  2259. return postComment (sortDiv, HashOverForm, postButton, AJAXPost);
  2260. };
  2261. // Onsubmit
  2262. postButton.onsubmit = function ()
  2263. {
  2264. return postComment (sortDiv, HashOverForm, postButton, AJAXPost);
  2265. };
  2266. <?php if ($hashover->setup->allowsLogin !== false): ?>
  2267. // Attach event listeners to "Login" button
  2268. if (userIsLoggedIn !== true) {
  2269. var loginButton = getElement ('hashover-login-button');
  2270. // Onclick
  2271. loginButton.onclick = function ()
  2272. {
  2273. return validateComment (true, HashOverForm);
  2274. };
  2275. // Onsubmit
  2276. loginButton.onsubmit = function ()
  2277. {
  2278. return validateComment (true, HashOverForm);
  2279. };
  2280. }
  2281. <?php endif; ?>
  2282. // Five method sort
  2283. ifElement ('hashover-sort-select', function (sortSelect) {
  2284. sortSelect.onchange = function ()
  2285. {
  2286. <?php if ($hashover->setup->collapsesComments !== false): ?>
  2287. var sortSelectDiv = getElement ('hashover-sort');
  2288. showMoreComments (sortSelectDiv, function () {
  2289. sortDiv.textContent = '';
  2290. sortComments (sortSelect.value);
  2291. });
  2292. <?php else: ?>
  2293. sortDiv.textContent = '';
  2294. sortComments (sortSelect.value);
  2295. <?php endif; ?>
  2296. };
  2297. });
  2298. // Display reply or edit form when the proper URL queries are set
  2299. if (URLHref.match (/hashover-(reply|edit)=/)) {
  2300. var permalink = URLHref.replace (/.*?hashover-(edit|reply)=(c[0-9r\-pop]+).*?/, '$2');
  2301. if (!URLHref.match ('hashover-edit=')) {
  2302. <?php if ($hashover->setup->collapsesComments !== false): ?>
  2303. // Show more comments
  2304. showMoreComments (moreLink, function () {
  2305. // Then display reply form
  2306. hashoverReply (permalink);
  2307. });
  2308. <?php else: ?>
  2309. // Display reply form
  2310. hashoverReply (permalink);
  2311. <?php endif; ?>
  2312. } else {
  2313. var isPop = permalink.match ('-pop');
  2314. var comments = (isPop) ? PHPContent.popularComments : PHPContent.comments;
  2315. <?php if ($hashover->setup->collapsesComments !== false): ?>
  2316. // Show more comments
  2317. showMoreComments (moreLink, function () {
  2318. // Then display edit form
  2319. hashoverEdit (findByPermalink (permalink, comments));
  2320. });
  2321. <?php else: ?>
  2322. // Display edit form
  2323. hashoverEdit (findByPermalink (permalink, comments));
  2324. <?php endif; ?>
  2325. }
  2326. }
  2327. // Log execution time in JavaScript console
  2328. if (window.console) {
  2329. console.log ('HashOver executed in ' + (Date.now () - execStart) + ' ms.');
  2330. }
  2331. // Callback for scrolling a comment into view on page load
  2332. var scroller = function ()
  2333. {
  2334. setTimeout (function () {
  2335. // Workaround for stupid Chrome bug
  2336. if (URLHash.match (/comments|hashover/)) {
  2337. ifElement (URLHash, function (comments) {
  2338. comments.scrollIntoView ({ behavior: 'smooth' });
  2339. });
  2340. }
  2341. // Jump to linked comment
  2342. if (URLHash.match (/c[0-9]+r*/)) {
  2343. <?php if ($hashover->setup->collapsesComments !== false): ?>
  2344. var existingComment = getElement (URLHash);
  2345. // Check if comment exists on the page and is visable
  2346. if (existingComment !== null
  2347. && containsClass (existingComment, 'hashover-hidden') === false)
  2348. {
  2349. // If so, scroll the comment into view
  2350. existingComment.scrollIntoView ({ behavior: 'smooth' });
  2351. } else {
  2352. // If not, show more comments
  2353. showMoreComments (moreLink, function () {
  2354. ifElement (URLHash, function (comment) {
  2355. comment.scrollIntoView ({ behavior: 'smooth' });
  2356. });
  2357. });
  2358. }
  2359. <?php else: ?>
  2360. ifElement (URLHash, function (comment) {
  2361. comment.scrollIntoView ({ behavior: 'smooth' });
  2362. });
  2363. <?php endif; ?>
  2364. }
  2365. }, 500);
  2366. };
  2367. // Page onload compatibility wrapper
  2368. if (window.addEventListener) {
  2369. // Rest of the world
  2370. window.addEventListener ('load', scroller, false);
  2371. } else {
  2372. // IE ~8
  2373. window.attachEvent ('onload', scroller);
  2374. }
  2375. // Open the message element if there's a message
  2376. if (getElement ('hashover-message').textContent !== '') {
  2377. showMessage ();
  2378. }
  2379. };
  2380. // Initiate HashOver
  2381. HashOver.init ();