2014-02-12 05:23:40 -05:00
|
|
|
logger = require 'logger-sharelatex'
|
|
|
|
fs = require 'fs'
|
|
|
|
crypto = require 'crypto'
|
|
|
|
Settings = require('settings-sharelatex')
|
|
|
|
SubscriptionFormatters = require('../Features/Subscription/SubscriptionFormatters')
|
|
|
|
querystring = require('querystring')
|
2014-07-24 08:24:08 -04:00
|
|
|
SystemMessageManager = require("../Features/SystemMessages/SystemMessageManager")
|
2016-09-05 10:58:31 -04:00
|
|
|
AuthenticationController = require("../Features/Authentication/AuthenticationController")
|
2014-08-13 07:31:14 -04:00
|
|
|
_ = require("underscore")
|
2016-09-27 11:21:04 -04:00
|
|
|
async = require("async")
|
2014-09-08 10:40:46 -04:00
|
|
|
Modules = require "./Modules"
|
2016-07-20 11:10:33 -04:00
|
|
|
Url = require "url"
|
2016-09-22 06:02:20 -04:00
|
|
|
PackageVersions = require "./PackageVersions"
|
2017-07-28 12:31:28 -04:00
|
|
|
htmlEncoder = new require("node-html-encoder").Encoder("numerical")
|
2017-12-12 12:21:01 -05:00
|
|
|
hashedFiles = {}
|
2014-02-12 05:23:40 -05:00
|
|
|
Path = require 'path'
|
2017-11-20 05:47:32 -05:00
|
|
|
Features = require "./Features"
|
2018-04-11 07:36:47 -04:00
|
|
|
Modules = require "./Modules"
|
2016-07-18 11:24:48 -04:00
|
|
|
|
2014-02-12 05:23:40 -05:00
|
|
|
jsPath =
|
|
|
|
if Settings.useMinifiedJs
|
|
|
|
"/minjs/"
|
|
|
|
else
|
|
|
|
"/js/"
|
|
|
|
|
2016-09-22 06:02:20 -04:00
|
|
|
ace = PackageVersions.lib('ace')
|
|
|
|
pdfjs = PackageVersions.lib('pdfjs')
|
2017-12-01 06:22:41 -05:00
|
|
|
fineuploader = PackageVersions.lib('fineuploader')
|
2016-07-18 09:05:07 -04:00
|
|
|
|
2016-09-27 11:21:04 -04:00
|
|
|
getFileContent = (filePath)->
|
|
|
|
filePath = Path.join __dirname, "../../../", "public#{filePath}"
|
2014-03-13 08:38:16 -04:00
|
|
|
exists = fs.existsSync filePath
|
|
|
|
if exists
|
2017-12-14 07:11:13 -05:00
|
|
|
content = fs.readFileSync filePath, "UTF-8"
|
2016-09-27 11:21:04 -04:00
|
|
|
return content
|
2014-03-13 08:38:16 -04:00
|
|
|
else
|
2017-12-14 07:11:13 -05:00
|
|
|
logger.log filePath:filePath, "file does not exist for hashing"
|
2016-09-27 11:21:04 -04:00
|
|
|
return ""
|
|
|
|
|
|
|
|
pathList = [
|
2017-12-14 07:11:13 -05:00
|
|
|
"#{jsPath}libs/require.js"
|
|
|
|
"#{jsPath}ide.js"
|
|
|
|
"#{jsPath}main.js"
|
|
|
|
"#{jsPath}libraries.js"
|
|
|
|
"/stylesheets/style.css"
|
|
|
|
"/stylesheets/ol-style.css"
|
2018-04-11 07:36:47 -04:00
|
|
|
].concat(Modules.moduleAssetFiles(jsPath))
|
2016-09-27 11:21:04 -04:00
|
|
|
|
2017-12-19 08:13:31 -05:00
|
|
|
if !Settings.useMinifiedJs
|
|
|
|
logger.log "not using minified JS, not hashing static files"
|
|
|
|
else
|
|
|
|
logger.log "Generating file hashes..."
|
|
|
|
for path in pathList
|
|
|
|
content = getFileContent(path)
|
|
|
|
hash = crypto.createHash("md5").update(content).digest("hex")
|
|
|
|
|
|
|
|
splitPath = path.split("/")
|
|
|
|
filenameSplit = splitPath.pop().split(".")
|
|
|
|
filenameSplit.splice(filenameSplit.length-1, 0, hash)
|
|
|
|
splitPath.push(filenameSplit.join("."))
|
2017-12-14 07:11:13 -05:00
|
|
|
|
2017-12-19 08:13:31 -05:00
|
|
|
hashPath = splitPath.join("/")
|
|
|
|
hashedFiles[path] = hashPath
|
2016-07-18 12:18:51 -04:00
|
|
|
|
2017-12-19 08:13:31 -05:00
|
|
|
fsHashPath = Path.join __dirname, "../../../", "public#{hashPath}"
|
|
|
|
fs.writeFileSync(fsHashPath, content)
|
2017-12-14 07:11:13 -05:00
|
|
|
|
|
|
|
|
2017-12-19 08:13:31 -05:00
|
|
|
logger.log "Finished hashing static content"
|
2016-07-18 11:24:48 -04:00
|
|
|
|
2016-07-21 10:34:23 -04:00
|
|
|
cdnAvailable = Settings.cdn?.web?.host?
|
2016-07-21 14:06:53 -04:00
|
|
|
darkCdnAvailable = Settings.cdn?.web?.darkHost?
|
2014-02-12 05:23:40 -05:00
|
|
|
|
2017-07-05 09:32:55 -04:00
|
|
|
module.exports = (app, webRouter, privateApiRouter, publicApiRouter)->
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2014-02-12 05:23:40 -05:00
|
|
|
res.locals.session = req.session
|
|
|
|
next()
|
|
|
|
|
2017-04-12 11:00:02 -04:00
|
|
|
addSetContentDisposition = (req, res, next) ->
|
|
|
|
res.setContentDisposition = (type, opts) ->
|
|
|
|
directives = for k, v of opts
|
|
|
|
"#{k}=\"#{encodeURIComponent(v)}\""
|
|
|
|
contentDispositionValue = "#{type}; #{directives.join('; ')}"
|
|
|
|
res.setHeader(
|
|
|
|
'Content-Disposition',
|
|
|
|
contentDispositionValue
|
|
|
|
)
|
|
|
|
next()
|
|
|
|
webRouter.use addSetContentDisposition
|
2017-07-05 09:32:55 -04:00
|
|
|
privateApiRouter.use addSetContentDisposition
|
|
|
|
publicApiRouter.use addSetContentDisposition
|
2017-04-12 11:00:02 -04:00
|
|
|
|
2017-05-15 10:46:24 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2017-11-20 05:47:32 -05:00
|
|
|
req.externalAuthenticationSystemUsed = Features.externalAuthenticationSystemUsed
|
|
|
|
res.locals.externalAuthenticationSystemUsed = Features.externalAuthenticationSystemUsed
|
|
|
|
req.hasFeature = res.locals.hasFeature = Features.hasFeature
|
2018-01-24 11:56:51 -05:00
|
|
|
res.locals.userIsFromOLv1 = (user) ->
|
|
|
|
user.overleaf?.id?
|
|
|
|
res.locals.userIsFromSL = (user) ->
|
|
|
|
!user.overleaf?.id?
|
2017-05-15 10:46:24 -04:00
|
|
|
next()
|
|
|
|
|
2016-09-05 10:58:31 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2016-07-21 10:34:23 -04:00
|
|
|
|
2016-08-19 06:05:35 -04:00
|
|
|
cdnBlocked = req.query.nocdn == 'true' or req.session.cdnBlocked
|
2016-09-22 10:33:50 -04:00
|
|
|
user_id = AuthenticationController.getLoggedInUserId(req)
|
2016-08-19 06:05:35 -04:00
|
|
|
|
|
|
|
if cdnBlocked and !req.session.cdnBlocked?
|
2016-09-22 10:33:50 -04:00
|
|
|
logger.log user_id:user_id, ip:req?.ip, "cdnBlocked for user, not using it and turning it off for future requets"
|
2016-08-19 06:05:35 -04:00
|
|
|
req.session.cdnBlocked = true
|
|
|
|
|
2016-07-21 10:34:23 -04:00
|
|
|
isDark = req.headers?.host?.slice(0,4)?.toLowerCase() == "dark"
|
|
|
|
isSmoke = req.headers?.host?.slice(0,5)?.toLowerCase() == "smoke"
|
|
|
|
isLive = !isDark and !isSmoke
|
2016-08-19 06:53:40 -04:00
|
|
|
|
2016-08-19 06:05:35 -04:00
|
|
|
if cdnAvailable and isLive and !cdnBlocked
|
2016-07-21 10:34:23 -04:00
|
|
|
staticFilesBase = Settings.cdn?.web?.host
|
2016-07-21 14:06:53 -04:00
|
|
|
else if darkCdnAvailable and isDark
|
|
|
|
staticFilesBase = Settings.cdn?.web?.darkHost
|
2016-07-21 10:34:23 -04:00
|
|
|
else
|
|
|
|
staticFilesBase = ""
|
2016-09-05 10:58:31 -04:00
|
|
|
|
2014-02-12 05:23:40 -05:00
|
|
|
res.locals.jsPath = jsPath
|
2016-07-20 11:10:33 -04:00
|
|
|
res.locals.fullJsPath = Url.resolve(staticFilesBase, jsPath)
|
2016-09-22 06:02:20 -04:00
|
|
|
res.locals.lib = PackageVersions.lib
|
2016-07-20 07:58:32 -04:00
|
|
|
|
2017-12-13 08:06:38 -05:00
|
|
|
|
|
|
|
|
2016-07-19 10:10:07 -04:00
|
|
|
res.locals.buildJsPath = (jsFile, opts = {})->
|
2016-07-20 11:10:33 -04:00
|
|
|
path = Path.join(jsPath, jsFile)
|
2016-07-20 07:58:32 -04:00
|
|
|
|
2017-12-19 08:13:31 -05:00
|
|
|
if opts.hashedPath && hashedFiles[path]?
|
2017-12-13 08:06:38 -05:00
|
|
|
path = hashedFiles[path]
|
2016-09-05 10:58:31 -04:00
|
|
|
|
2016-07-19 10:10:07 -04:00
|
|
|
if !opts.qs?
|
|
|
|
opts.qs = {}
|
2016-07-20 07:58:32 -04:00
|
|
|
|
2016-07-21 14:06:53 -04:00
|
|
|
if opts.cdn != false
|
|
|
|
path = Url.resolve(staticFilesBase, path)
|
2016-09-05 10:58:31 -04:00
|
|
|
|
2016-07-19 10:10:07 -04:00
|
|
|
qs = querystring.stringify(opts.qs)
|
2016-07-20 07:58:32 -04:00
|
|
|
|
2017-12-13 09:13:45 -05:00
|
|
|
if opts.removeExtension == true
|
|
|
|
path = path.slice(0,-3)
|
|
|
|
|
2016-07-20 07:58:32 -04:00
|
|
|
if qs? and qs.length > 0
|
2016-07-20 11:10:33 -04:00
|
|
|
path = path + "?" + qs
|
|
|
|
return path
|
2016-07-18 12:18:51 -04:00
|
|
|
|
2018-03-13 11:43:35 -04:00
|
|
|
res.locals.buildWebpackPath = (jsFile, opts = {}) ->
|
|
|
|
if Settings.webpack? and !Settings.useMinifiedJs
|
|
|
|
path = Path.join(jsPath, jsFile)
|
2018-03-23 09:52:48 -04:00
|
|
|
if opts.removeExtension == true
|
|
|
|
path = path.slice(0,-3)
|
2018-03-16 07:15:09 -04:00
|
|
|
return "#{Settings.webpack.url}/public#{path}"
|
2018-03-13 11:43:35 -04:00
|
|
|
else
|
2018-03-15 08:15:00 -04:00
|
|
|
return res.locals.buildJsPath(jsFile, opts)
|
2018-03-13 11:43:35 -04:00
|
|
|
|
2017-12-12 12:21:01 -05:00
|
|
|
res.locals.buildCssPath = (cssFile, opts)->
|
2016-07-21 10:34:23 -04:00
|
|
|
path = Path.join("/stylesheets/", cssFile)
|
2017-12-19 08:13:31 -05:00
|
|
|
if opts?.hashedPath && hashedFiles[path]?
|
2017-12-12 12:21:01 -05:00
|
|
|
hashedPath = hashedFiles[path]
|
|
|
|
return Url.resolve(staticFilesBase, hashedPath)
|
2017-12-14 07:11:13 -05:00
|
|
|
return Url.resolve(staticFilesBase, path)
|
2016-07-18 12:18:51 -04:00
|
|
|
|
|
|
|
res.locals.buildImgPath = (imgFile)->
|
2016-07-21 10:34:23 -04:00
|
|
|
path = Path.join("/img/", imgFile)
|
2016-07-20 11:10:33 -04:00
|
|
|
return Url.resolve(staticFilesBase, path)
|
2016-07-18 12:18:51 -04:00
|
|
|
|
2014-02-12 05:23:40 -05:00
|
|
|
next()
|
|
|
|
|
2016-07-18 12:18:51 -04:00
|
|
|
|
|
|
|
|
2016-09-05 10:58:31 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2014-02-12 05:23:40 -05:00
|
|
|
res.locals.settings = Settings
|
|
|
|
next()
|
|
|
|
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2017-07-28 12:31:28 -04:00
|
|
|
res.locals.translate = (key, vars = {}, htmlEncode = false) ->
|
2015-03-09 08:14:30 -04:00
|
|
|
vars.appName = Settings.appName
|
2017-07-28 12:31:28 -04:00
|
|
|
str = req.i18n.translate(key, vars)
|
|
|
|
if htmlEncode then htmlEncoder.htmlEncode(str) else str
|
2016-09-14 12:08:26 -04:00
|
|
|
# Don't include the query string parameters, otherwise Google
|
|
|
|
# treats ?nocdn=true as the canonical version
|
|
|
|
res.locals.currentUrl = Url.parse(req.originalUrl).pathname
|
2014-03-24 13:18:58 -04:00
|
|
|
next()
|
|
|
|
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2014-02-12 05:23:40 -05:00
|
|
|
res.locals.getSiteHost = ->
|
|
|
|
Settings.siteUrl.substring(Settings.siteUrl.indexOf("//")+2)
|
|
|
|
next()
|
|
|
|
|
2016-09-22 10:33:50 -04:00
|
|
|
webRouter.use (req, res, next) ->
|
2016-03-21 07:41:05 -04:00
|
|
|
res.locals.getUserEmail = ->
|
2016-09-22 10:33:50 -04:00
|
|
|
user = AuthenticationController.getSessionUser(req)
|
|
|
|
email = user?.email or ""
|
2016-03-21 07:41:05 -04:00
|
|
|
return email
|
|
|
|
next()
|
|
|
|
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2014-04-07 15:46:58 -04:00
|
|
|
res.locals.formatProjectPublicAccessLevel = (privilegeLevel)->
|
|
|
|
formatedPrivileges = private:"Private", readOnly:"Public: Read Only", readAndWrite:"Public: Read and Write"
|
|
|
|
return formatedPrivileges[privilegeLevel] || "Private"
|
2014-02-12 05:23:40 -05:00
|
|
|
next()
|
|
|
|
|
2016-09-05 10:58:31 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2014-02-12 05:23:40 -05:00
|
|
|
res.locals.buildReferalUrl = (referal_medium) ->
|
|
|
|
url = Settings.siteUrl
|
2016-09-22 09:30:34 -04:00
|
|
|
currentUser = AuthenticationController.getSessionUser(req)
|
|
|
|
if currentUser? and currentUser?.referal_id?
|
|
|
|
url+="?r=#{currentUser.referal_id}&rm=#{referal_medium}&rs=b" # Referal source = bonus
|
2014-02-12 05:23:40 -05:00
|
|
|
return url
|
|
|
|
res.locals.getReferalId = ->
|
2016-09-22 09:30:34 -04:00
|
|
|
currentUser = AuthenticationController.getSessionUser(req)
|
|
|
|
if currentUser? and currentUser?.referal_id?
|
|
|
|
return currentUser.referal_id
|
2014-02-12 05:23:40 -05:00
|
|
|
res.locals.getReferalTagLine = ->
|
|
|
|
tagLines = [
|
|
|
|
"Roar!"
|
|
|
|
"Shout about us!"
|
|
|
|
"Please recommend us"
|
|
|
|
"Tell the world!"
|
|
|
|
"Thanks for using ShareLaTeX"
|
|
|
|
]
|
|
|
|
return tagLines[Math.floor(Math.random()*tagLines.length)]
|
|
|
|
res.locals.getRedirAsQueryString = ->
|
|
|
|
if req.query.redir?
|
|
|
|
return "?#{querystring.stringify({redir:req.query.redir})}"
|
|
|
|
return ""
|
2015-11-17 10:54:59 -05:00
|
|
|
|
|
|
|
res.locals.getLoggedInUserId = ->
|
2016-09-05 10:58:31 -04:00
|
|
|
return AuthenticationController.getLoggedInUserId(req)
|
2016-09-06 10:22:13 -04:00
|
|
|
res.locals.isUserLoggedIn = ->
|
|
|
|
return AuthenticationController.isUserLoggedIn(req)
|
|
|
|
res.locals.getSessionUser = ->
|
|
|
|
return AuthenticationController.getSessionUser(req)
|
2016-11-29 09:38:25 -05:00
|
|
|
|
2014-02-12 05:23:40 -05:00
|
|
|
next()
|
|
|
|
|
2015-06-30 09:38:32 -04:00
|
|
|
webRouter.use (req, res, next) ->
|
|
|
|
res.locals.csrfToken = req?.csrfToken()
|
2014-02-12 05:23:40 -05:00
|
|
|
next()
|
|
|
|
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next) ->
|
2015-04-30 06:59:44 -04:00
|
|
|
res.locals.getReqQueryParam = (field)->
|
|
|
|
return req.query?[field]
|
|
|
|
next()
|
|
|
|
|
2016-09-05 10:58:31 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2014-02-12 05:23:40 -05:00
|
|
|
res.locals.formatPrice = SubscriptionFormatters.formatPrice
|
|
|
|
next()
|
|
|
|
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2016-09-22 09:30:34 -04:00
|
|
|
currentUser = AuthenticationController.getSessionUser(req)
|
|
|
|
if currentUser?
|
2014-02-12 05:23:40 -05:00
|
|
|
res.locals.user =
|
2016-09-22 09:30:34 -04:00
|
|
|
email: currentUser.email
|
|
|
|
first_name: currentUser.first_name
|
|
|
|
last_name: currentUser.last_name
|
2014-02-12 05:23:40 -05:00
|
|
|
if req.session.justRegistered
|
|
|
|
res.locals.justRegistered = true
|
|
|
|
delete req.session.justRegistered
|
|
|
|
if req.session.justLoggedIn
|
|
|
|
res.locals.justLoggedIn = true
|
|
|
|
delete req.session.justLoggedIn
|
|
|
|
res.locals.gaToken = Settings.analytics?.ga?.token
|
|
|
|
res.locals.tenderUrl = Settings.tenderUrl
|
2014-12-12 08:58:07 -05:00
|
|
|
res.locals.sentrySrc = Settings.sentry?.src
|
|
|
|
res.locals.sentryPublicDSN = Settings.sentry?.publicDSN
|
2014-02-12 05:23:40 -05:00
|
|
|
next()
|
|
|
|
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next) ->
|
2014-02-12 05:23:40 -05:00
|
|
|
if req.query? and req.query.scribtex_path?
|
|
|
|
res.locals.lookingForScribtex = true
|
|
|
|
res.locals.scribtexPath = req.query.scribtex_path
|
|
|
|
next()
|
|
|
|
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next) ->
|
2015-11-05 11:52:50 -05:00
|
|
|
# Clone the nav settings so they can be modified for each request
|
|
|
|
res.locals.nav = {}
|
|
|
|
for key, value of Settings.nav
|
|
|
|
res.locals.nav[key] = _.clone(Settings.nav[key])
|
2014-08-20 09:47:27 -04:00
|
|
|
res.locals.templates = Settings.templateLinks
|
2017-01-11 05:27:38 -05:00
|
|
|
if res.locals.nav.header
|
|
|
|
console.error {}, "The `nav.header` setting is no longer supported, use `nav.header_extras` instead"
|
2014-06-20 16:35:42 -04:00
|
|
|
next()
|
2016-09-05 10:58:31 -04:00
|
|
|
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next) ->
|
2014-07-24 08:24:08 -04:00
|
|
|
SystemMessageManager.getMessages (error, messages = []) ->
|
|
|
|
res.locals.systemMessages = messages
|
|
|
|
next()
|
2014-02-12 05:23:40 -05:00
|
|
|
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2015-06-01 07:43:42 -04:00
|
|
|
res.locals.query = req.query
|
|
|
|
next()
|
|
|
|
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next)->
|
2014-08-13 07:31:14 -04:00
|
|
|
subdomain = _.find Settings.i18n.subdomainLang, (subdomain)->
|
2014-08-21 12:58:25 -04:00
|
|
|
subdomain.lngCode == req.showUserOtherLng and !subdomain.hide
|
2014-08-13 07:31:14 -04:00
|
|
|
res.locals.recomendSubdomain = subdomain
|
2014-08-13 18:06:15 -04:00
|
|
|
res.locals.currentLngCode = req.lng
|
2014-08-13 07:31:14 -04:00
|
|
|
next()
|
|
|
|
|
2015-06-30 10:36:39 -04:00
|
|
|
webRouter.use (req, res, next) ->
|
2014-10-08 07:39:36 -04:00
|
|
|
if Settings.reloadModuleViewsOnEachRequest
|
2014-09-08 10:40:46 -04:00
|
|
|
Modules.loadViewIncludes()
|
|
|
|
res.locals.moduleIncludes = Modules.moduleIncludes
|
2015-02-05 13:20:25 -05:00
|
|
|
res.locals.moduleIncludesAvailable = Modules.moduleIncludesAvailable
|
2014-09-08 10:40:46 -04:00
|
|
|
next()
|
2017-11-30 10:12:36 -05:00
|
|
|
|
|
|
|
webRouter.use (req, res, next) ->
|
|
|
|
isOl = (Settings.brandPrefix == 'ol-')
|
|
|
|
res.locals.uiConfig =
|
2018-03-29 05:35:17 -04:00
|
|
|
defaultResizerSizeOpen : if isOl then 7 else 24
|
|
|
|
defaultResizerSizeClosed : if isOl then 7 else 24
|
2017-12-13 07:16:44 -05:00
|
|
|
eastResizerCursor : if isOl then "ew-resize" else null
|
|
|
|
westResizerCursor : if isOl then "ew-resize" else null
|
2018-03-29 05:35:17 -04:00
|
|
|
chatResizerSizeOpen : if isOl then 7 else 12
|
2017-12-13 07:16:44 -05:00
|
|
|
chatResizerSizeClosed : 0
|
2017-12-19 05:52:23 -05:00
|
|
|
chatMessageBorderSaturation: if isOl then "85%" else "70%"
|
|
|
|
chatMessageBorderLightness : if isOl then "40%" else "70%"
|
|
|
|
chatMessageBgSaturation : if isOl then "85%" else "60%"
|
|
|
|
chatMessageBgLightness : if isOl then "40%" else "97%"
|
2018-05-04 09:05:20 -04:00
|
|
|
editorFontFamily : if isOl then '\\"Lucida Console\\", monospace' else null
|
|
|
|
editorLineHeight : if isOl then 1.6 else null
|
2017-12-21 08:52:34 -05:00
|
|
|
renderAnnouncements : !isOl
|
2017-11-30 10:12:36 -05:00
|
|
|
next()
|