mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge branch 'master' into pr-email-tokens
This commit is contained in:
commit
0dc4e2c0c1
13 changed files with 104 additions and 50 deletions
|
@ -7,15 +7,19 @@ querystring = require('querystring')
|
||||||
SystemMessageManager = require("../Features/SystemMessages/SystemMessageManager")
|
SystemMessageManager = require("../Features/SystemMessages/SystemMessageManager")
|
||||||
_ = require("underscore")
|
_ = require("underscore")
|
||||||
Modules = require "./Modules"
|
Modules = require "./Modules"
|
||||||
|
url = require "url"
|
||||||
|
|
||||||
fingerprints = {}
|
fingerprints = {}
|
||||||
Path = require 'path'
|
Path = require 'path'
|
||||||
|
|
||||||
|
|
||||||
jsPath =
|
jsPath =
|
||||||
if Settings.useMinifiedJs
|
if Settings.useMinifiedJs
|
||||||
"/minjs/"
|
"/minjs/"
|
||||||
else
|
else
|
||||||
"/js/"
|
"/js/"
|
||||||
|
|
||||||
|
|
||||||
logger.log "Generating file fingerprints..."
|
logger.log "Generating file fingerprints..."
|
||||||
for path in [
|
for path in [
|
||||||
"#{jsPath}libs/require.js",
|
"#{jsPath}libs/require.js",
|
||||||
|
@ -37,7 +41,21 @@ for path in [
|
||||||
fingerprints[path] = hash
|
fingerprints[path] = hash
|
||||||
else
|
else
|
||||||
logger.log filePath:filePath, "file does not exist for fingerprints"
|
logger.log filePath:filePath, "file does not exist for fingerprints"
|
||||||
|
|
||||||
|
getFingerprint = (path) ->
|
||||||
|
if fingerprints[path]?
|
||||||
|
return fingerprints[path]
|
||||||
|
else
|
||||||
|
logger.err "No fingerprint for file: #{path}"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
logger.log "Finished generating file fingerprints"
|
||||||
|
|
||||||
|
|
||||||
|
staticFilesBase = ""
|
||||||
|
if Settings.cdn?.web?.host?
|
||||||
|
staticFilesBase = Settings.cdn?.web?.host
|
||||||
|
|
||||||
|
|
||||||
module.exports = (app, webRouter, apiRouter)->
|
module.exports = (app, webRouter, apiRouter)->
|
||||||
webRouter.use (req, res, next)->
|
webRouter.use (req, res, next)->
|
||||||
|
@ -46,8 +64,42 @@ module.exports = (app, webRouter, apiRouter)->
|
||||||
|
|
||||||
webRouter.use (req, res, next)->
|
webRouter.use (req, res, next)->
|
||||||
res.locals.jsPath = jsPath
|
res.locals.jsPath = jsPath
|
||||||
|
res.locals.fullJsPath = url.resolve(staticFilesBase, jsPath)
|
||||||
|
|
||||||
|
imgPath = "/img/"
|
||||||
|
cssPath = "/stylesheets/"
|
||||||
|
|
||||||
|
res.locals.buildJsPath = (jsFile, opts = {})->
|
||||||
|
p = Path.join(jsPath, jsFile)
|
||||||
|
|
||||||
|
doFingerPrint = opts.fingerprint != false
|
||||||
|
|
||||||
|
if !opts.qs?
|
||||||
|
opts.qs = {}
|
||||||
|
|
||||||
|
if !opts.qs?.fingerprint? and doFingerPrint
|
||||||
|
opts.qs.fingerprint = getFingerprint(p)
|
||||||
|
|
||||||
|
p = url.resolve(staticFilesBase, p)
|
||||||
|
qs = querystring.stringify(opts.qs)
|
||||||
|
|
||||||
|
if qs? and qs.length > 0
|
||||||
|
p = p + "?" + qs
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
res.locals.buildCssPath = (cssFile)->
|
||||||
|
p = Path.join(cssPath, cssFile)
|
||||||
|
return url.resolve(staticFilesBase, p) + "?fingerprint=" + getFingerprint(p)
|
||||||
|
|
||||||
|
res.locals.buildImgPath = (imgFile)->
|
||||||
|
p = Path.join(imgPath, imgFile)
|
||||||
|
return url.resolve(staticFilesBase, p)
|
||||||
|
|
||||||
next()
|
next()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
webRouter.use (req, res, next)->
|
webRouter.use (req, res, next)->
|
||||||
res.locals.settings = Settings
|
res.locals.settings = Settings
|
||||||
next()
|
next()
|
||||||
|
@ -113,12 +165,7 @@ module.exports = (app, webRouter, apiRouter)->
|
||||||
next()
|
next()
|
||||||
|
|
||||||
webRouter.use (req, res, next)->
|
webRouter.use (req, res, next)->
|
||||||
res.locals.fingerprint = (path) ->
|
res.locals.fingerprint = getFingerprint
|
||||||
if fingerprints[path]?
|
|
||||||
return fingerprints[path]
|
|
||||||
else
|
|
||||||
logger.err "No fingerprint for file: #{path}"
|
|
||||||
return ""
|
|
||||||
next()
|
next()
|
||||||
|
|
||||||
webRouter.use (req, res, next)->
|
webRouter.use (req, res, next)->
|
||||||
|
|
|
@ -3,7 +3,7 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||||
head
|
head
|
||||||
title Something went wrong
|
title Something went wrong
|
||||||
link(rel="icon", href="/favicon.ico")
|
link(rel="icon", href="/favicon.ico")
|
||||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
link(rel='stylesheet', href=buildCssPath('/style.css'))
|
||||||
link(href="//netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css",rel="stylesheet")
|
link(href="//netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css",rel="stylesheet")
|
||||||
body
|
body
|
||||||
.content
|
.content
|
||||||
|
@ -12,7 +12,7 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||||
.col-md-8.col-md-offset-2.text-center
|
.col-md-8.col-md-offset-2.text-center
|
||||||
.page-header
|
.page-header
|
||||||
h2 Oh dear, something went wrong.
|
h2 Oh dear, something went wrong.
|
||||||
p: img(src="/img/lion-sad-128.png", alt="Sad Lion")
|
p: img(src=buildImgPath("lion-sad-128.png"), alt="Sad Lion")
|
||||||
p
|
p
|
||||||
| Something went wrong with your request, sorry. Our staff are probably looking into this, but if it continues, please contact us at #{settings.adminEmail}
|
| Something went wrong with your request, sorry. Our staff are probably looking into this, but if it continues, please contact us at #{settings.adminEmail}
|
||||||
p
|
p
|
||||||
|
|
|
@ -15,10 +15,10 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||||
-if (typeof(title) == "undefined")
|
-if (typeof(title) == "undefined")
|
||||||
title= 'ShareLaTeX, '+ translate("online_latex_editor")
|
title= 'ShareLaTeX, '+ translate("online_latex_editor")
|
||||||
-else
|
-else
|
||||||
title= translate(title) + ' - ShareLaTeX, '+translate("online_latex_editor")
|
title= translate(title) + ' - ShareLaTeX, ' + translate("online_latex_editor")
|
||||||
|
|
||||||
link(rel="icon", href="/favicon.ico")
|
link(rel="icon", href="/favicon.ico")
|
||||||
link(rel='stylesheet', href='/stylesheets/style.css?fingerprint='+fingerprint('/stylesheets/style.css'))
|
link(rel='stylesheet', href=buildCssPath('/style.css'))
|
||||||
|
|
||||||
if settings.i18n.subdomainLang
|
if settings.i18n.subdomainLang
|
||||||
each subdomainDetails in settings.i18n.subdomainLang
|
each subdomainDetails in settings.i18n.subdomainLang
|
||||||
|
@ -124,8 +124,8 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||||
window.csrfToken = "#{csrfToken}";
|
window.csrfToken = "#{csrfToken}";
|
||||||
|
|
||||||
block scripts
|
block scripts
|
||||||
script(src="#{jsPath}libs/jquery-1.11.1.min.js")
|
script(src=buildJsPath("libs/jquery-1.11.1.min.js", {fingerprint:false}))
|
||||||
script(src="#{jsPath}libs/angular-1.3.15.min.js")
|
script(src=buildJsPath("libs/angular-1.3.15.min.js", {fingerprint:false}))
|
||||||
script.
|
script.
|
||||||
window.sharelatex = {
|
window.sharelatex = {
|
||||||
siteUrl: '#{settings.siteUrl}',
|
siteUrl: '#{settings.siteUrl}',
|
||||||
|
@ -194,11 +194,12 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
script(
|
script(
|
||||||
data-main=jsPath+'main.js',
|
data-main=buildJsPath('main.js', {fingerprint:false}),
|
||||||
baseurl=jsPath,
|
baseurl=fullJsPath,
|
||||||
src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js')
|
src=buildJsPath('libs/require.js')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
include contact-us-modal
|
include contact-us-modal
|
||||||
include sentry
|
include sentry
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,7 @@ block content
|
||||||
window.wikiEnabled = #{!!(settings.apis.wiki && settings.apis.wiki.url)};
|
window.wikiEnabled = #{!!(settings.apis.wiki && settings.apis.wiki.url)};
|
||||||
window.requirejs = {
|
window.requirejs = {
|
||||||
"paths" : {
|
"paths" : {
|
||||||
"mathjax": "/js/libs/mathjax/MathJax.js?config=TeX-AMS_HTML",
|
"mathjax": "#{buildJsPath('/libs/mathjax/MathJax.js', {qs:{config:'TeX-AMS_HTML', fingerprint:false}})}",
|
||||||
"moment": "libs/moment-2.7.0",
|
"moment": "libs/moment-2.7.0",
|
||||||
"libs/pdf": "libs/pdfjs-1.3.91/pdf"
|
"libs/pdf": "libs/pdfjs-1.3.91/pdf"
|
||||||
},
|
},
|
||||||
|
@ -129,15 +129,18 @@ block content
|
||||||
|
|
||||||
- var pdfPath = 'libs/pdfjs-1.3.91/pdf.worker.js'
|
- var pdfPath = 'libs/pdfjs-1.3.91/pdf.worker.js'
|
||||||
- var fingerprintedPath = fingerprint(jsPath+pdfPath)
|
- var fingerprintedPath = fingerprint(jsPath+pdfPath)
|
||||||
- var pdfJsWorkerPath = jsPath+pdfPath+'?fingerprint='+fingerprintedPath
|
- var pdfJsWorkerPath = buildJsPath(pdfPath, {qs:{fingerprint:fingerprintedPath}})
|
||||||
|
|
||||||
|
|
||||||
script(type='text/javascript').
|
script(type='text/javascript').
|
||||||
window.pdfJsWorkerPath = "#{pdfJsWorkerPath}";
|
window.pdfJsWorkerPath = "#{pdfJsWorkerPath}";
|
||||||
|
|
||||||
script(
|
script(
|
||||||
data-main=jsPath+"ide.js",
|
data-main=buildJsPath("ide.js", {fingerprint:false}),
|
||||||
baseurl=jsPath,
|
baseurl=fullJsPath,
|
||||||
data-ace-base=jsPath+'ace',
|
data-ace-base=buildJsPath('ace', {fingerprint:false}),
|
||||||
src=jsPath+'libs/require.js?fingerprint='+fingerprint(jsPath + 'libs/require.js')
|
src=buildJsPath('libs/require.js')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@
|
||||||
p
|
p
|
||||||
span Get Dropbox Sync
|
span Get Dropbox Sync
|
||||||
p
|
p
|
||||||
img(src="/img/dropbox/simple_logo.png")
|
img(src=buildImgPath("dropbox/simple_logo.png"))
|
||||||
p
|
p
|
||||||
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgraed-rotation").btn.btn-primary #{translate("upgrade")}
|
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgraed-rotation").btn.btn-primary #{translate("upgrade")}
|
||||||
p.small.text-centered
|
p.small.text-centered
|
||||||
|
@ -148,7 +148,7 @@
|
||||||
p
|
p
|
||||||
span Get Github Sync
|
span Get Github Sync
|
||||||
p
|
p
|
||||||
img(src="/img/github/octocat.jpg")
|
img(src=buildImgPath("github/octocat.jpg"))
|
||||||
p
|
p
|
||||||
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgraed-rotation").btn.btn-primary #{translate("upgrade")}
|
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgraed-rotation").btn.btn-primary #{translate("upgrade")}
|
||||||
p.small.text-centered
|
p.small.text-centered
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
- if (sentrySrc.match(/^([a-z]+:)?\/\//i))
|
- if (sentrySrc.match(/^([a-z]+:)?\/\//i))
|
||||||
script(src="#{sentrySrc}")
|
script(src="#{sentrySrc}")
|
||||||
- else
|
- else
|
||||||
script(src="#{jsPath}libs/#{sentrySrc}")
|
script(src=buildJsPath("libs/#{sentrySrc}"))
|
||||||
- if (typeof(sentrySrc) != "undefined")
|
- if (typeof(sentrySrc) != "undefined")
|
||||||
script(type="text/javascript").
|
script(type="text/javascript").
|
||||||
if (typeof(Raven) != "undefined" && Raven.config) {
|
if (typeof(Raven) != "undefined" && Raven.config) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ extends ../layout
|
||||||
block content
|
block content
|
||||||
- locals.supressDefaultJs = true
|
- locals.supressDefaultJs = true
|
||||||
script(data-main=jsPath+'main.js', src=jsPath+'libs/require.js', baseurl=jsPath)
|
script(data-main=jsPath+'main.js', src=jsPath+'libs/require.js', baseurl=jsPath)
|
||||||
script(src=jsPath+'libs/recurly.min.js')
|
script(src=buildJsPath('libs/recurly.min.js'))
|
||||||
|
|
||||||
.content.content-alt
|
.content.content-alt
|
||||||
.container
|
.container
|
||||||
|
|
|
@ -30,10 +30,10 @@ block content
|
||||||
| Henry and James
|
| Henry and James
|
||||||
.portraits
|
.portraits
|
||||||
span.img-circle
|
span.img-circle
|
||||||
img(src="/img/about/henry_oswald.jpg")
|
img(src=buildImgPath("about/henry_oswald.jpg"))
|
||||||
|
|
|
|
||||||
span.img-circle
|
span.img-circle
|
||||||
img(src="/img/about/james_allen.jpg")
|
img(src=buildImgPath("about/james_allen.jpg"))
|
||||||
p
|
p
|
||||||
a.btn.btn-primary(href="/project") < #{translate("back_to_your_projects")}
|
a.btn.btn-primary(href="/project") < #{translate("back_to_your_projects")}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
span(ng-controller="TranslationsPopupController", ng-cloak)
|
span(ng-controller="TranslationsPopupController", ng-cloak)
|
||||||
.translations-message(ng-hide="hidei18nNotification")
|
.translations-message(ng-hide="hidei18nNotification")
|
||||||
a(href=recomendSubdomain.url+currentUrl) !{translate("click_here_to_view_sl_in_lng", {lngName:"<strong>" + translate(recomendSubdomain.lngCode) + "</strong>"})}
|
a(href=recomendSubdomain.url+currentUrl) !{translate("click_here_to_view_sl_in_lng", {lngName:"<strong>" + translate(recomendSubdomain.lngCode) + "</strong>"})}
|
||||||
img(src="/img/flags/24/#{recomendSubdomain.lngCode}.png")
|
img(src=buildImgPath("flags/24/#{recomendSubdomain.lngCode}.png"))
|
||||||
button(ng-click="dismiss()").close.pull-right
|
button(ng-click="dismiss()").close.pull-right
|
||||||
span(aria-hidden="true") ×
|
span(aria-hidden="true") ×
|
||||||
span.sr-only #{translate("close")}
|
span.sr-only #{translate("close")}
|
|
@ -114,6 +114,10 @@ module.exports = settings =
|
||||||
showSocialButtons: false
|
showSocialButtons: false
|
||||||
showComments: false
|
showComments: false
|
||||||
|
|
||||||
|
# cdn:
|
||||||
|
# web:
|
||||||
|
# host:"http://www.sharelatex.dev:3000"
|
||||||
|
|
||||||
# Where your instance of ShareLaTeX can be found publically. Used in emails
|
# Where your instance of ShareLaTeX can be found publically. Used in emails
|
||||||
# that are sent out, generated links, etc.
|
# that are sent out, generated links, etc.
|
||||||
siteUrl : siteUrl = 'http://localhost:3000'
|
siteUrl : siteUrl = 'http://localhost:3000'
|
||||||
|
|
|
@ -102,6 +102,15 @@ define [
|
||||||
for file in response?.outputFiles
|
for file in response?.outputFiles
|
||||||
fileByPath[file.path] = file
|
fileByPath[file.path] = file
|
||||||
|
|
||||||
|
# prepare query string
|
||||||
|
qs = {}
|
||||||
|
# add a query string parameter for the compile group
|
||||||
|
if response.compileGroup?
|
||||||
|
ide.compileGroup = qs.compileGroup = response.compileGroup
|
||||||
|
# add a query string parameter for the clsi server id
|
||||||
|
if response.clsiServerId?
|
||||||
|
ide.clsiServerId = qs.clsiserverid = response.clsiServerId
|
||||||
|
|
||||||
if response.status == "timedout"
|
if response.status == "timedout"
|
||||||
$scope.pdf.view = 'errors'
|
$scope.pdf.view = 'errors'
|
||||||
$scope.pdf.timedout = true
|
$scope.pdf.timedout = true
|
||||||
|
@ -133,8 +142,6 @@ define [
|
||||||
$scope.pdf.view = 'pdf'
|
$scope.pdf.view = 'pdf'
|
||||||
$scope.shouldShowLogs = false
|
$scope.shouldShowLogs = false
|
||||||
|
|
||||||
# prepare query string
|
|
||||||
qs = {}
|
|
||||||
# define the base url. if the pdf file has a build number, pass it to the clsi in the url
|
# define the base url. if the pdf file has a build number, pass it to the clsi in the url
|
||||||
if fileByPath['output.pdf']?.url?
|
if fileByPath['output.pdf']?.url?
|
||||||
$scope.pdf.url = fileByPath['output.pdf'].url
|
$scope.pdf.url = fileByPath['output.pdf'].url
|
||||||
|
@ -146,16 +153,8 @@ define [
|
||||||
# check if we need to bust cache (build id is unique so don't need it in that case)
|
# check if we need to bust cache (build id is unique so don't need it in that case)
|
||||||
if not fileByPath['output.pdf']?.build?
|
if not fileByPath['output.pdf']?.build?
|
||||||
qs.cache_bust = "#{Date.now()}"
|
qs.cache_bust = "#{Date.now()}"
|
||||||
# add a query string parameter for the compile group
|
|
||||||
if response.compileGroup?
|
|
||||||
$scope.pdf.compileGroup = response.compileGroup
|
|
||||||
qs.compileGroup = "#{$scope.pdf.compileGroup}"
|
|
||||||
if response.clsiServerId?
|
|
||||||
qs.clsiserverid = response.clsiServerId
|
|
||||||
ide.clsiServerId = response.clsiServerId
|
|
||||||
# convert the qs hash into a query string and append it
|
# convert the qs hash into a query string and append it
|
||||||
$scope.pdf.qs = createQueryString qs
|
$scope.pdf.url += createQueryString qs
|
||||||
$scope.pdf.url += $scope.pdf.qs
|
|
||||||
# Save all downloads as files
|
# Save all downloads as files
|
||||||
qs.popupDownload = true
|
qs.popupDownload = true
|
||||||
$scope.pdf.downloadUrl = "/project/#{$scope.project_id}/output/output.pdf" + createQueryString(qs)
|
$scope.pdf.downloadUrl = "/project/#{$scope.project_id}/output/output.pdf" + createQueryString(qs)
|
||||||
|
@ -187,6 +186,7 @@ define [
|
||||||
opts =
|
opts =
|
||||||
method:"GET"
|
method:"GET"
|
||||||
params:
|
params:
|
||||||
|
compileGroup:ide.compileGroup
|
||||||
clsiserverid:ide.clsiServerId
|
clsiserverid:ide.clsiServerId
|
||||||
if file?.url? # FIXME clean this up when we have file.urls out consistently
|
if file?.url? # FIXME clean this up when we have file.urls out consistently
|
||||||
opts.url = file.url
|
opts.url = file.url
|
||||||
|
@ -194,6 +194,9 @@ define [
|
||||||
opts.url = "/project/#{$scope.project_id}/build/#{file.build}/output/#{name}"
|
opts.url = "/project/#{$scope.project_id}/build/#{file.build}/output/#{name}"
|
||||||
else
|
else
|
||||||
opts.url = "/project/#{$scope.project_id}/output/#{name}"
|
opts.url = "/project/#{$scope.project_id}/output/#{name}"
|
||||||
|
# check if we need to bust cache (build id is unique so don't need it in that case)
|
||||||
|
if not file?.build?
|
||||||
|
opts.params.cache_bust = "#{Date.now()}"
|
||||||
return $http(opts)
|
return $http(opts)
|
||||||
|
|
||||||
# accumulate the log entries
|
# accumulate the log entries
|
||||||
|
|
|
@ -1,23 +1,14 @@
|
||||||
define [
|
define [
|
||||||
"base"
|
"base"
|
||||||
"ide/pdfng/directives/pdfViewer"
|
"ide/pdfng/directives/pdfViewer"
|
||||||
"text!libs/pdfListView/TextLayer.css"
|
|
||||||
"text!libs/pdfListView/AnnotationsLayer.css"
|
|
||||||
"text!libs/pdfListView/HighlightsLayer.css"
|
|
||||||
], (
|
], (
|
||||||
App
|
App
|
||||||
pdfViewer
|
pdfViewer
|
||||||
textLayerCss
|
|
||||||
annotationsLayerCss
|
|
||||||
highlightsLayerCss
|
|
||||||
) ->
|
) ->
|
||||||
if PDFJS?
|
if PDFJS?
|
||||||
PDFJS.workerSrc = window.pdfJsWorkerPath
|
PDFJS.workerSrc = window.pdfJsWorkerPath
|
||||||
|
|
||||||
style = $("<style/>")
|
|
||||||
style.text(textLayerCss + "\n" + annotationsLayerCss + "\n" + highlightsLayerCss)
|
|
||||||
$("body").append(style)
|
|
||||||
|
|
||||||
App.directive "pdfng", ["$timeout", "localStorage", ($timeout, localStorage) ->
|
App.directive "pdfng", ["$timeout", "localStorage", ($timeout, localStorage) ->
|
||||||
return {
|
return {
|
||||||
scope: {
|
scope: {
|
||||||
|
|
|
@ -76,3 +76,8 @@
|
||||||
@import "app/translations.less";
|
@import "app/translations.less";
|
||||||
@import "app/contact-us.less";
|
@import "app/contact-us.less";
|
||||||
@import "app/sprites.less";
|
@import "app/sprites.less";
|
||||||
|
|
||||||
|
@import "../js/libs/pdfListView/TextLayer.css";
|
||||||
|
@import "../js/libs/pdfListView/AnnotationsLayer.css";
|
||||||
|
@import "../js/libs/pdfListView/HighlightsLayer.css";
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue