'use strict'; var app = window.app || {}; app.isFocused = true; app.currentRoom = null; app.widgets = {}; app.cacheBuster = null; (function () { var params = utils.params(); var showWelcomeMessage = !!params.loggedin; var registerMessage = params.register; var isTouchDevice = utils.isTouchDevice(); require(['benchpress'], function (Benchpress) { Benchpress.setGlobal('config', config); if (Object.defineProperty) { Object.defineProperty(window, 'templates', { configurable: true, enumerable: true, get: function () { console.warn('[deprecated] Accessing benchpress (formerly known as templates.js) globally is deprecated. Use `require(["benchpress"], function (Benchpress) { ... })` instead'); return Benchpress; }, }); } else { window.templates = Benchpress; } }); app.cacheBuster = config['cache-buster']; bootbox.setDefaults({ locale: config.userLang, }); app.load = function () { app.loadProgressiveStylesheet(); overrides.overrideTimeago(); var url = ajaxify.start(window.location.pathname.slice(1) + window.location.search + window.location.hash); ajaxify.updateHistory(url, true); ajaxify.parseData(); ajaxify.end(url, app.template); handleStatusChange(); if (config.searchEnabled) { app.handleSearch(); } $('body').on('click', '#new_topic', function (e) { e.preventDefault(); app.newTopic(); }); $('#header-menu .container').on('click', '[component="user/logout"]', app.logout); Visibility.change(function (event, state) { if (state === 'visible') { app.isFocused = true; app.alternatingTitle(''); } else if (state === 'hidden') { app.isFocused = false; } }); createHeaderTooltips(); app.showEmailConfirmWarning(); app.showCookieWarning(); socket.removeAllListeners('event:nodebb.ready'); socket.on('event:nodebb.ready', function (data) { if ((data.hostname === app.upstreamHost) && (!app.cacheBuster || app.cacheBuster !== data['cache-buster'])) { app.cacheBuster = data['cache-buster']; app.alert({ alert_id: 'forum_updated', title: '[[global:updated.title]]', message: '[[global:updated.message]]', clickfn: function () { window.location.reload(); }, type: 'warning', }); } }); socket.on('event:livereload', function () { if (app.user.isAdmin && !ajaxify.currentPage.match(/admin/)) { window.location.reload(); } }); require(['taskbar', 'helpers', 'forum/pagination'], function (taskbar, helpers, pagination) { taskbar.init(); helpers.register(); pagination.init(); $(window).trigger('action:app.load'); }); }; app.updateHeader = function (data, callback) { /** * data: * header (obj) * config (obj) * next (string) */ require([ 'benchpress', 'translator', 'forum/unread', 'forum/header/notifications', 'forum/header/chat', ], function (Benchpress, translator, Unread, Notifications, Chat) { app.user = data.header.user; data.header.config = data.config; config = data.config; Benchpress.setGlobal('config', config); var htmlEl = $('html'); htmlEl.attr('data-dir', data.header.languageDirection); htmlEl.css('direction', data.header.languageDirection); // Manually reconnect socket.io socket.close(); socket.open(); // Re-render top bar menu var toRender = { 'slideout-menu': $('.slideout-menu'), menu: $('#header-menu .container'), 'chats-menu': $('#chats-menu'), }; Promise.all(Object.keys(toRender).map(function (tpl) { return Benchpress.render('partials/' + tpl, data.header).then(function (render) { return translator.Translator.create().translate(render); }); })).then(function (html) { Object.values(toRender).forEach(function (element, idx) { element.html(html[idx]); }); Unread.initUnreadTopics(); Notifications.prepareDOM(); Chat.prepareDOM(); app.reskin(data.header.bootswatchSkin); translator.switchTimeagoLanguage(callback); bootbox.setLocale(config.userLang); if (config.searchEnabled) { app.handleSearch(); } handleStatusChange(); $(window).trigger('action:app.updateHeader'); }); }); }; app.logout = function (e) { if (e) { e.preventDefault(); } $(window).trigger('action:app.logout'); /* Set session refresh flag (otherwise the session check will trip and throw invalid session modal) We know the session is/will be invalid (uid mismatch) because the user is logging out */ app.flags = app.flags || {}; app.flags._sessionRefresh = true; $.ajax(config.relative_path + '/logout', { type: 'POST', headers: { 'x-csrf-token': config.csrf_token, }, success: function (data) { // ACP logouts go to frontend via page load, not ajaxify if (ajaxify.data.template.name.startsWith('admin/')) { $(window).trigger('action:app.loggedOut', data); window.location.href = config.relative_path + (data.next || '/'); return; } app.updateHeader(data, function () { // Overwrite in hook (below) to redirect elsewhere data.next = data.next || undefined; $(window).trigger('action:app.loggedOut', data); if (data.next) { if (data.next.startsWith('http')) { window.location.href = data.next; return; } ajaxify.go(data.next); } else { ajaxify.refresh(); } }); }, }); }; app.alert = function (params) { require(['alerts'], function (alerts) { alerts.alert(params); }); }; app.removeAlert = function (id) { require(['alerts'], function (alerts) { alerts.remove(id); }); }; app.alertSuccess = function (message, timeout) { app.alert({ title: '[[global:alert.success]]', message: message, type: 'success', timeout: timeout || 5000, }); }; app.alertError = function (message, timeout) { message = message.message || message; if (message === '[[error:invalid-session]]') { return app.handleInvalidSession(); } app.alert({ title: '[[global:alert.error]]', message: message, type: 'danger', timeout: timeout || 10000, }); }; app.handleInvalidSession = function () { if (app.flags && app.flags._sessionRefresh) { return; } app.flags = app.flags || {}; app.flags._sessionRefresh = true; socket.disconnect(); require(['translator'], function (translator) { translator.translate('[[error:invalid-session-text]]', function (translated) { bootbox.alert({ title: '[[error:invalid-session]]', message: translated, closeButton: false, callback: function () { window.location.reload(); }, }); }); }); }; app.enterRoom = function (room, callback) { callback = callback || function () {}; if (socket && app.user.uid && app.currentRoom !== room) { var previousRoom = app.currentRoom; app.currentRoom = room; socket.emit('meta.rooms.enter', { enter: room, }, function (err) { if (err) { app.currentRoom = previousRoom; return app.alertError(err.message); } callback(); }); } }; app.leaveCurrentRoom = function () { if (!socket) { return; } var previousRoom = app.currentRoom; app.currentRoom = ''; socket.emit('meta.rooms.leaveCurrent', function (err) { if (err) { app.currentRoom = previousRoom; return app.alertError(err.message); } }); }; function highlightNavigationLink() { $('#main-nav li') .removeClass('active') .find('a') .filter(function (i, x) { return window.location.pathname.startsWith(x.getAttribute('href')); }) .parent() .addClass('active'); } app.createUserTooltips = function (els, placement) { if (isTouchDevice) { return; } els = els || $('body'); els.find('.avatar,img[title].teaser-pic,img[title].user-img,div.user-icon,span.user-icon').each(function () { $(this).tooltip({ placement: placement || $(this).attr('title-placement') || 'top', title: $(this).attr('title'), }); }); }; app.createStatusTooltips = function () { if (!isTouchDevice) { $('body').tooltip({ selector: '.fa-circle.status', placement: 'top', }); } }; app.processPage = function () { highlightNavigationLink(); $('.timeago').timeago(); utils.makeNumbersHumanReadable($('.human-readable-number')); utils.addCommasToNumbers($('.formatted-number')); app.createUserTooltips(); app.createStatusTooltips(); // Scroll back to top of page if (!ajaxify.isCold()) { window.scrollTo(0, 0); } }; app.showMessages = function () { var messages = { login: { format: 'alert', title: '[[global:welcome_back]] ' + app.user.username + '!', message: '[[global:you_have_successfully_logged_in]]', }, register: { format: 'modal', }, }; function showAlert(type, message) { switch (messages[type].format) { case 'alert': app.alert({ type: 'success', title: messages[type].title, message: messages[type].message, timeout: 5000, }); break; case 'modal': require(['translator'], function (translator) { translator.translate(message || messages[type].message, function (translated) { bootbox.alert({ title: messages[type].title, message: translated, }); }); }); break; } } if (showWelcomeMessage) { showWelcomeMessage = false; $(document).ready(function () { showAlert('login'); }); } if (registerMessage) { $(document).ready(function () { showAlert('register', decodeURIComponent(registerMessage)); registerMessage = false; }); } }; app.openChat = function (roomId, uid) { if (!app.user.uid) { return app.alertError('[[error:not-logged-in]]'); } require(['chat'], function (chat) { function loadAndCenter(chatModal) { chat.load(chatModal.attr('data-uuid')); chat.center(chatModal); chat.focusInput(chatModal); } if (chat.modalExists(roomId)) { loadAndCenter(chat.getModal(roomId)); } else { socket.emit('modules.chats.loadRoom', { roomId: roomId, uid: uid || app.user.uid }, function (err, roomData) { if (err) { return app.alertError(err.message); } roomData.users = roomData.users.filter(function (user) { return user && parseInt(user.uid, 10) !== parseInt(app.user.uid, 10); }); roomData.uid = uid || app.user.uid; roomData.isSelf = true; chat.createModal(roomData, loadAndCenter); }); } }); }; app.newChat = function (touid, callback) { function createChat() { socket.emit('modules.chats.newRoom', { touid: touid }, function (err, roomId) { if (err) { return app.alertError(err.message); } if (!ajaxify.data.template.chats) { app.openChat(roomId); } else { ajaxify.go('chats/' + roomId); } callback(false, roomId); }); } callback = callback || function () {}; if (!app.user.uid) { return app.alertError('[[error:not-logged-in]]'); } if (parseInt(touid, 10) === parseInt(app.user.uid, 10)) { return app.alertError('[[error:cant-chat-with-yourself]]'); } socket.emit('modules.chats.isDnD', touid, function (err, isDnD) { if (err) { return app.alertError(err.message); } if (!isDnD) { return createChat(); } bootbox.confirm('[[modules:chat.confirm-chat-with-dnd-user]]', function (ok) { if (ok) { createChat(); } }); }); }; var titleObj = { active: false, interval: undefined, titles: [], }; app.alternatingTitle = function (title) { if (typeof title !== 'string') { return; } if (title.length > 0 && !app.isFocused) { if (!titleObj.titles[0]) { titleObj.titles[0] = window.document.title; } require(['translator'], function (translator) { translator.translate(title, function (translated) { titleObj.titles[1] = translated; if (titleObj.interval) { clearInterval(titleObj.interval); } titleObj.interval = setInterval(function () { var title = titleObj.titles[titleObj.titles.indexOf(window.document.title) ^ 1]; if (title) { window.document.title = $('
').html(title).text(); } }, 2000); }); }); } else { if (titleObj.interval) { clearInterval(titleObj.interval); } if (titleObj.titles[0]) { window.document.title = $('
').html(titleObj.titles[0]).text(); } } }; app.refreshTitle = function (title) { if (!title) { return; } require(['translator'], function (translator) { title = config.titleLayout.replace(/{/g, '{').replace(/}/g, '}') .replace('{pageTitle}', function () { return title; }) .replace('{browserTitle}', function () { return config.browserTitle; }); // Allow translation strings in title on ajaxify (#5927) title = translator.unescape(title); translator.translate(title, function (translated) { titleObj.titles[0] = translated; app.alternatingTitle(''); }); }); }; app.toggleNavbar = function (state) { var navbarEl = $('.navbar'); if (navbarEl) { navbarEl.toggleClass('hidden', !state); } }; function createHeaderTooltips() { var env = utils.findBootstrapEnvironment(); if (env === 'xs' || env === 'sm' || isTouchDevice) { return; } $('#header-menu li a[title]').each(function () { $(this).tooltip({ placement: 'bottom', trigger: 'hover', title: $(this).attr('title'), }); }); $('#search-form').parent().tooltip({ placement: 'bottom', trigger: 'hover', title: $('#search-button i').attr('title'), }); $('#user_dropdown').tooltip({ placement: 'bottom', trigger: 'hover', title: $('#user_dropdown').attr('title'), }); } app.enableTopicSearch = function (options) { var quickSearchResults = options.resultEl; var inputEl = options.inputEl; var template = options.template || 'partials/quick-search-results'; var searchTimeoutId = 0; inputEl.on('keyup', function () { if (searchTimeoutId) { clearTimeout(searchTimeoutId); searchTimeoutId = 0; } if (inputEl.val().length < 3) { return; } searchTimeoutId = setTimeout(function () { if (!inputEl.is(':focus')) { return quickSearchResults.addClass('hidden'); } require(['search'], function (search) { var data = { term: inputEl.val(), in: 'titles', searchOnly: 1, }; search.api(data, function (data) { if (!data.matchCount) { quickSearchResults.html('').addClass('hidden'); return; } data.posts.forEach(function (p) { p.snippet = utils.escapeHTML($(p.content).text().slice(0, 80) + '...'); }); app.parseAndTranslate(template, data, function (html) { html.find('.timeago').timeago(); quickSearchResults.html(html).removeClass('hidden').show(); }); }); }); }, 250); }); }; app.handleSearch = function () { var searchButton = $('#search-button'); var searchFields = $('#search-fields'); var searchInput = $('#search-fields input'); var quickSearchResults = $('#quick-search-results'); $('#search-form .advanced-search-link').on('mousedown', function () { ajaxify.go('/search'); }); $('#search-form').on('submit', function () { searchInput.blur(); }); searchInput.on('blur', dismissSearch); searchInput.on('focus', function () { if (searchInput.val() && quickSearchResults.children().length) { quickSearchResults.removeClass('hidden').show(); } }); app.enableTopicSearch({ inputEl: searchInput, resultEl: quickSearchResults, }); function dismissSearch() { searchFields.addClass('hidden'); searchButton.removeClass('hidden'); setTimeout(function () { quickSearchResults.addClass('hidden'); }, 200); } searchButton.on('click', function (e) { if (!config.loggedIn && !app.user.privileges['search:content']) { app.alert({ message: '[[error:search-requires-login]]', timeout: 3000, }); ajaxify.go('login'); return false; } e.stopPropagation(); app.prepareSearch(); return false; }); $('#search-form').on('submit', function () { var input = $(this).find('input'); require(['search'], function (search) { var data = search.getSearchPreferences(); data.term = input.val(); search.query(data, function () { input.val(''); }); }); return false; }); }; app.prepareSearch = function () { $('#search-fields').removeClass('hidden'); $('#search-button').addClass('hidden'); $('#search-fields input').focus(); }; function handleStatusChange() { $('[component="header/usercontrol"] [data-status]').off('click').on('click', function (e) { var status = $(this).attr('data-status'); socket.emit('user.setStatus', status, function (err) { if (err) { return app.alertError(err.message); } $('[data-uid="' + app.user.uid + '"] [component="user/status"], [component="header/profilelink"] [component="user/status"]') .removeClass('away online dnd offline') .addClass(status); $('[component="header/usercontrol"] [data-status]').each(function () { $(this).find('span').toggleClass('bold', $(this).attr('data-status') === status); }); app.user.status = status; }); e.preventDefault(); }); } app.updateUserStatus = function (el, status) { if (!el.length) { return; } require(['translator'], function (translator) { translator.translate('[[global:' + status + ']]', function (translated) { el.removeClass('online offline dnd away') .addClass(status) .attr('title', translated) .attr('data-original-title', translated); }); }); }; app.newTopic = function (cid, tags) { $(window).trigger('action:composer.topic.new', { cid: cid || ajaxify.data.cid || 0, tags: tags || (ajaxify.data.tag ? [ajaxify.data.tag] : []), }); }; app.loadJQueryUI = function (callback) { if (typeof $().autocomplete === 'function') { return callback(); } var scriptEl = document.createElement('script'); scriptEl.type = 'text/javascript'; scriptEl.src = config.relative_path + '/assets/vendor/jquery/js/jquery-ui.js?' + config['cache-buster']; scriptEl.onload = callback; document.head.appendChild(scriptEl); }; app.showEmailConfirmWarning = function (err) { if (!config.requireEmailConfirmation || !app.user.uid) { return; } var msg = { alert_id: 'email_confirm', type: 'warning', timeout: 0, }; if (!app.user.email) { msg.message = '[[error:no-email-to-confirm]]'; msg.clickfn = function () { app.removeAlert('email_confirm'); ajaxify.go('user/' + app.user.userslug + '/edit'); }; app.alert(msg); } else if (!app.user['email:confirmed'] && !app.user.isEmailConfirmSent) { msg.message = err ? err.message : '[[error:email-not-confirmed]]'; msg.clickfn = function () { app.removeAlert('email_confirm'); socket.emit('user.emailConfirm', {}, function (err) { if (err) { return app.alertError(err.message); } app.alertSuccess('[[notifications:email-confirm-sent]]'); }); }; app.alert(msg); } else if (!app.user['email:confirmed'] && app.user.isEmailConfirmSent) { msg.message = '[[error:email-not-confirmed-email-sent]]'; app.alert(msg); } }; app.parseAndTranslate = function (template, blockName, data, callback) { require(['translator', 'benchpress'], function (translator, Benchpress) { function translate(html, callback) { translator.translate(html, function (translatedHTML) { translatedHTML = translator.unescape(translatedHTML); callback($(translatedHTML)); }); } if (typeof blockName === 'string') { Benchpress.parse(template, blockName, data, function (html) { translate(html, callback); }); } else { callback = data; data = blockName; Benchpress.parse(template, data, function (html) { translate(html, callback); }); } }); }; app.loadProgressiveStylesheet = function () { var linkEl = document.createElement('link'); linkEl.rel = 'stylesheet'; linkEl.href = config.relative_path + '/assets/js-enabled.css?' + app.cacheBuster; document.head.appendChild(linkEl); }; app.showCookieWarning = function () { require(['translator', 'storage'], function (translator, storage) { if (!config.cookies.enabled || !navigator.cookieEnabled) { // Skip warning if cookie consent subsystem disabled (obviously), or cookies not in use return; } else if (window.location.pathname.startsWith(config.relative_path + '/admin')) { // No need to show cookie consent warning in ACP return; } else if (storage.getItem('cookieconsent') === '1') { return; } config.cookies.message = translator.unescape(config.cookies.message); config.cookies.dismiss = translator.unescape(config.cookies.dismiss); config.cookies.link = translator.unescape(config.cookies.link); app.parseAndTranslate('partials/cookie-consent', config.cookies, function (html) { $(document.body).append(html); $(document.body).addClass('cookie-consent-open'); var warningEl = $('.cookie-consent'); var dismissEl = warningEl.find('button'); dismissEl.on('click', function () { // Save consent cookie and remove warning element storage.setItem('cookieconsent', '1'); warningEl.remove(); $(document.body).removeClass('cookie-consent-open'); }); }); }); }; app.reskin = function (skinName) { var clientEl = Array.prototype.filter.call(document.querySelectorAll('link[rel="stylesheet"]'), function (el) { return el.href.indexOf(config.relative_path + '/assets/client') !== -1; })[0] || null; if (!clientEl) { return; } var currentSkinClassName = $('body').attr('class').split(/\s+/).filter(function (className) { return className.startsWith('skin-'); }); if (!currentSkinClassName[0]) { return; } var currentSkin = currentSkinClassName[0].slice(5); currentSkin = currentSkin !== 'noskin' ? currentSkin : ''; // Stop execution if skin didn't change if (skinName === currentSkin) { return; } var linkEl = document.createElement('link'); linkEl.rel = 'stylesheet'; linkEl.type = 'text/css'; linkEl.href = config.relative_path + '/assets/client' + (skinName ? '-' + skinName : '') + '.css'; linkEl.onload = function () { clientEl.parentNode.removeChild(clientEl); // Update body class with proper skin name $('body').removeClass(currentSkinClassName.join(' ')); $('body').addClass('skin-' + (skinName || 'noskin')); }; document.head.appendChild(linkEl); }; app.updateTags = function () { var metaWhitelist = ['title', 'description', /og:.+/, /article:.+/].map(function (val) { return new RegExp(val); }); var linkWhitelist = ['canonical', 'alternate', 'up']; // Delete the old meta tags Array.prototype.slice .call(document.querySelectorAll('head meta')) .filter(function (el) { var name = el.getAttribute('property') || el.getAttribute('name'); return metaWhitelist.some(function (exp) { return !!exp.test(name); }); }) .forEach(function (el) { document.head.removeChild(el); }); // Add new meta tags ajaxify.data._header.tags.meta .filter(function (tagObj) { var name = tagObj.name || tagObj.property; return metaWhitelist.some(function (exp) { return !!exp.test(name); }); }) .forEach(function (tagObj) { var metaEl = document.createElement('meta'); Object.keys(tagObj).forEach(function (prop) { metaEl.setAttribute(prop, tagObj[prop]); }); document.head.appendChild(metaEl); }); // Delete the old link tags Array.prototype.slice .call(document.querySelectorAll('head link')) .filter(function (el) { var name = el.getAttribute('rel'); return linkWhitelist.some(function (item) { return item === name; }); }) .forEach(function (el) { document.head.removeChild(el); }); // Add new link tags ajaxify.data._header.tags.link .filter(function (tagObj) { return linkWhitelist.some(function (item) { return item === tagObj.rel; }); }) .forEach(function (tagObj) { var linkEl = document.createElement('link'); Object.keys(tagObj).forEach(function (prop) { linkEl.setAttribute(prop, tagObj[prop]); }); document.head.appendChild(linkEl); }); }; }());