Fixed tons of XSS issues and escaping problems
This commit is contained in:
parent
9b833f89d1
commit
2e96f089a7
BIN
dist/main.css
vendored
BIN
dist/main.css
vendored
Binary file not shown.
BIN
dist/main.js
vendored
BIN
dist/main.js
vendored
Binary file not shown.
BIN
dist/view.js
vendored
BIN
dist/view.js
vendored
Binary file not shown.
@ -483,9 +483,6 @@ class Album extends Module {
|
||||
# Call plugins
|
||||
$this->plugins(__METHOD__, 0, func_get_args());
|
||||
|
||||
# Parse
|
||||
if (strlen($title)>100) $title = substr($title, 0, 100);
|
||||
|
||||
# Execute query
|
||||
$query = Database::prepare($this->database, "UPDATE ? SET title = '?' WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $title, $this->albumIDs));
|
||||
$result = $this->database->query($query);
|
||||
@ -509,10 +506,6 @@ class Album extends Module {
|
||||
# Call plugins
|
||||
$this->plugins(__METHOD__, 0, func_get_args());
|
||||
|
||||
# Parse
|
||||
$description = htmlentities($description, ENT_COMPAT | ENT_HTML401, 'UTF-8');
|
||||
if (strlen($description)>1000) $description = substr($description, 0, 1000);
|
||||
|
||||
# Execute query
|
||||
$query = Database::prepare($this->database, "UPDATE ? SET description = '?' WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $description, $this->albumIDs));
|
||||
$result = $this->database->query($query);
|
||||
|
@ -896,9 +896,6 @@ class Photo extends Module {
|
||||
# Call plugins
|
||||
$this->plugins(__METHOD__, 0, func_get_args());
|
||||
|
||||
# Parse
|
||||
if (strlen($title)>100) $title = substr($title, 0, 100);
|
||||
|
||||
# Set title
|
||||
$query = Database::prepare($this->database, "UPDATE ? SET title = '?' WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $title, $this->photoIDs));
|
||||
$result = $this->database->query($query);
|
||||
@ -929,10 +926,6 @@ class Photo extends Module {
|
||||
# Call plugins
|
||||
$this->plugins(__METHOD__, 0, func_get_args());
|
||||
|
||||
# Parse
|
||||
$description = htmlentities($description, ENT_COMPAT | ENT_HTML401, 'UTF-8');
|
||||
if (strlen($description)>1000) $description = substr($description, 0, 1000);
|
||||
|
||||
# Set description
|
||||
$query = Database::prepare($this->database, "UPDATE ? SET description = '?' WHERE id IN ('?')", array(LYCHEE_TABLE_PHOTOS, $description, $this->photoIDs));
|
||||
$result = $this->database->query($query);
|
||||
@ -1122,10 +1115,6 @@ class Photo extends Module {
|
||||
# Parse tags
|
||||
$tags = preg_replace('/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/', ',', $tags);
|
||||
$tags = preg_replace('/,$|^,|(\ ){0,}$/', '', $tags);
|
||||
if (strlen($tags)>1000) {
|
||||
Log::notice($this->database, __METHOD__, __LINE__, 'Length of tags higher than 1000');
|
||||
return false;
|
||||
}
|
||||
|
||||
# Set tags
|
||||
$query = Database::prepare($this->database, "UPDATE ? SET tags = '?' WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $tags, $this->photoIDs));
|
||||
|
@ -40,9 +40,9 @@ gulp.task('view--js', function() {
|
||||
|
||||
var stream =
|
||||
gulp.src(paths.view.js)
|
||||
.pipe(plugins.babel())
|
||||
.on('error', catchError)
|
||||
.pipe(plugins.concat('_view--javascript.js', {newLine: "\n"}))
|
||||
.pipe(plugins.babel({ compact: true }))
|
||||
.on('error', catchError)
|
||||
.pipe(gulp.dest('../dist/'));
|
||||
|
||||
return stream;
|
||||
@ -109,9 +109,9 @@ gulp.task('main--js', function() {
|
||||
|
||||
var stream =
|
||||
gulp.src(paths.main.js)
|
||||
.pipe(plugins.babel())
|
||||
.on('error', catchError)
|
||||
.pipe(plugins.concat('_main--javascript.js', {newLine: "\n"}))
|
||||
.pipe(plugins.babel({ compact: true }))
|
||||
.on('error', catchError)
|
||||
.pipe(gulp.dest('../dist/'));
|
||||
|
||||
return stream;
|
||||
|
@ -204,14 +204,14 @@ album.delete = function(albumIDs) {
|
||||
if (album.json) albumTitle = album.json.title
|
||||
else if (albums.json) albumTitle = albums.getByID(albumIDs).title
|
||||
|
||||
msg = `<p>Are you sure you want to delete the album ${ albumTitle } and all of the photos it contains? This action can't be undone!</p>`
|
||||
msg = lychee.html`<p>Are you sure you want to delete the album '$${ albumTitle }' and all of the photos it contains? This action can't be undone!</p>`
|
||||
|
||||
} else {
|
||||
|
||||
action.title = 'Delete Albums and Photos'
|
||||
cancel.title = 'Keep Albums'
|
||||
|
||||
msg = `<p>Are you sure you want to delete all ${ albumIDs.length } selected albums and all of the photos they contain? This action can't be undone!</p>`
|
||||
msg = lychee.html`<p>Are you sure you want to delete all $${ albumIDs.length } selected albums and all of the photos they contain? This action can't be undone!</p>`
|
||||
|
||||
}
|
||||
|
||||
@ -292,10 +292,10 @@ album.setTitle = function(albumIDs) {
|
||||
|
||||
}
|
||||
|
||||
let input = `<input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='${ lychee.escapeHTML(oldTitle) }'>`
|
||||
let input = lychee.html`<input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='$${ oldTitle }'>`
|
||||
|
||||
if (albumIDs.length===1) msg = `<p>Enter a new title for this album: ${ input }</p>`
|
||||
else msg = `<p>Enter a title for all ${ albumIDs.length } selected albums: ${ input }</p>`
|
||||
if (albumIDs.length===1) msg = lychee.html`<p>Enter a new title for this album: ${ input }</p>`
|
||||
else msg = lychee.html`<p>Enter a title for all $${ albumIDs.length } selected albums: ${ input }</p>`
|
||||
|
||||
basicModal.show({
|
||||
body: msg,
|
||||
@ -342,7 +342,7 @@ album.setDescription = function(albumID) {
|
||||
}
|
||||
|
||||
basicModal.show({
|
||||
body: `<p>Please enter a description for this album: <input class='text' name='description' type='text' maxlength='800' placeholder='Description' value='${ lychee.escapeHTML(oldDescription) }'></p>`,
|
||||
body: lychee.html`<p>Please enter a description for this album: <input class='text' name='description' type='text' maxlength='800' placeholder='Description' value='$${ oldDescription }'></p>`,
|
||||
buttons: {
|
||||
action: {
|
||||
title: 'Set Description',
|
||||
@ -574,11 +574,11 @@ album.merge = function(albumIDs) {
|
||||
if (!sTitle) sTitle = ''
|
||||
sTitle = sTitle.replace(/'/g, ''')
|
||||
|
||||
msg = `<p>Are you sure you want to merge the album '${ sTitle }' into the album '${ title }'?</p>`
|
||||
msg = lychee.html`<p>Are you sure you want to merge the album '$${ sTitle }' into the album '$${ title }'?</p>`
|
||||
|
||||
} else {
|
||||
|
||||
msg = `<p>Are you sure you want to merge all selected albums into the album '${ title }'?</p>`
|
||||
msg = lychee.html`<p>Are you sure you want to merge all selected albums into the album '$${ title }'?</p>`
|
||||
|
||||
}
|
||||
|
||||
|
@ -7,54 +7,66 @@ build = {}
|
||||
|
||||
build.iconic = function(icon, classes = '') {
|
||||
|
||||
return `<svg class='iconic ${ classes }'><use xlink:href='#${ icon }' /></svg>`
|
||||
let html = ''
|
||||
|
||||
html += lychee.html`<svg class='iconic $${ classes }'><use xlink:href='#$${ icon }' /></svg>`
|
||||
|
||||
return html
|
||||
|
||||
}
|
||||
|
||||
build.divider = function(title) {
|
||||
|
||||
return `<div class='divider'><h1>${ title }</h1></div>`
|
||||
let html = ''
|
||||
|
||||
html += lychee.html`<div class='divider'><h1>$${ title }</h1></div>`
|
||||
|
||||
return html
|
||||
|
||||
}
|
||||
|
||||
build.editIcon = function(id) {
|
||||
|
||||
return `<div id='${ id }' class='edit'>${ build.iconic('pencil') }</div>`
|
||||
let html = ''
|
||||
|
||||
html += lychee.html`<div id='$${ id }' class='edit'>${ build.iconic('pencil') }</div>`
|
||||
|
||||
return html
|
||||
|
||||
}
|
||||
|
||||
build.multiselect = function(top, left) {
|
||||
|
||||
return `<div id='multiselect' style='top: ${ top }px; left: ${ left }px;'></div>`
|
||||
return lychee.html`<div id='multiselect' style='top: $${ top }px; left: $${ left }px;'></div>`
|
||||
|
||||
}
|
||||
|
||||
build.album = function(data) {
|
||||
|
||||
if (data==null) return ''
|
||||
let html = ''
|
||||
|
||||
let { path: thumbPath, hasRetina: thumbRetina } = lychee.retinize(data.thumbs[0])
|
||||
|
||||
let html = `
|
||||
<div class='album' data-id='${ data.id }'>
|
||||
<img src='${ data.thumbs[2] }' width='200' height='200' alt='thumb' data-overlay='false'>
|
||||
<img src='${ data.thumbs[1] }' width='200' height='200' alt='thumb' data-overlay='false'>
|
||||
<img src='${ thumbPath }' width='200' height='200' alt='thumb' data-overlay='${ thumbRetina }'>
|
||||
html += lychee.html`
|
||||
<div class='album' data-id='$${ data.id }'>
|
||||
<img src='$${ data.thumbs[2] }' width='200' height='200' alt='thumb' data-overlay='false'>
|
||||
<img src='$${ data.thumbs[1] }' width='200' height='200' alt='thumb' data-overlay='false'>
|
||||
<img src='$${ thumbPath }' width='200' height='200' alt='thumb' data-overlay='$${ thumbRetina }'>
|
||||
<div class='overlay'>
|
||||
<h1 title='${ data.title }'>${ data.title }</h1>
|
||||
<a>${ data.sysdate }</a>
|
||||
<h1 title='$${ data.title }'>$${ data.title }</h1>
|
||||
<a>$${ data.sysdate }</a>
|
||||
</div>
|
||||
`
|
||||
|
||||
if (lychee.publicMode===false) {
|
||||
|
||||
html += `
|
||||
html += lychee.html`
|
||||
<div class='badges'>
|
||||
<a class='badge ${ (data.star==='1' ? 'badge--visible' : '') } icn-star'>${ build.iconic('star') }</a>
|
||||
<a class='badge ${ (data.public==='1' ? 'badge--visible' : '') } icn-share'>${ build.iconic('eye') }</a>
|
||||
<a class='badge ${ (data.unsorted==='1' ? 'badge--visible' : '') }'>${ build.iconic('list') }</a>
|
||||
<a class='badge ${ (data.recent==='1' ? 'badge--visible' : '') }'>${ build.iconic('clock') }</a>
|
||||
<a class='badge ${ (data.password==='1' ? 'badge--visible' : '') }'>${ build.iconic('lock-locked') }</a>
|
||||
<a class='badge $${ (data.star==='1' ? 'badge--visible' : '') } icn-star'>${ build.iconic('star') }</a>
|
||||
<a class='badge $${ (data.public==='1' ? 'badge--visible' : '') } icn-share'>${ build.iconic('eye') }</a>
|
||||
<a class='badge $${ (data.unsorted==='1' ? 'badge--visible' : '') }'>${ build.iconic('list') }</a>
|
||||
<a class='badge $${ (data.recent==='1' ? 'badge--visible' : '') }'>${ build.iconic('clock') }</a>
|
||||
<a class='badge $${ (data.password==='1' ? 'badge--visible' : '') }'>${ build.iconic('lock-locked') }</a>
|
||||
</div>
|
||||
`
|
||||
|
||||
@ -68,34 +80,34 @@ build.album = function(data) {
|
||||
|
||||
build.photo = function(data) {
|
||||
|
||||
if (data==null) return ''
|
||||
let html = ''
|
||||
|
||||
let { path: thumbPath, hasRetina: thumbRetina } = lychee.retinize(data.thumbUrl)
|
||||
|
||||
let html = `
|
||||
<div class='photo' data-album-id='${ data.album }' data-id='${ data.id }'>
|
||||
<img src='${ thumbPath }' width='200' height='200' alt='thumb'>
|
||||
html += lychee.html`
|
||||
<div class='photo' data-album-id='$${ data.album }' data-id='$${ data.id }'>
|
||||
<img src='$${ thumbPath }' width='200' height='200' alt='thumb'>
|
||||
<div class='overlay'>
|
||||
<h1 title='${ data.title }'>${ data.title }</h1>
|
||||
<h1 title='$${ data.title }'>$${ data.title }</h1>
|
||||
`
|
||||
|
||||
if (data.cameraDate==='1') html += `<a><span title='Camera Date'>${ build.iconic('camera-slr') }</span>${ data.sysdate }</a>`
|
||||
else html += `<a>${ data.sysdate }</a>`
|
||||
if (data.cameraDate==='1') html += lychee.html`<a><span title='Camera Date'>${ build.iconic('camera-slr') }</span>$${ data.sysdate }</a>`
|
||||
else html += lychee.html`<a>$${ data.sysdate }</a>`
|
||||
|
||||
html += '</div>'
|
||||
html += `</div>`
|
||||
|
||||
if (lychee.publicMode===false) {
|
||||
|
||||
html += `
|
||||
html += lychee.html`
|
||||
<div class='badges'>
|
||||
<a class='badge ${ (data.star==='1' ? 'badge--visible' : '') } icn-star'>${ build.iconic('star') }</a>
|
||||
<a class='badge ${ ((data.public==='1' && album.json.public!=='1') ? 'badge--visible' : '') } icn-share'>${ build.iconic('eye') }</a>
|
||||
<a class='badge $${ (data.star==='1' ? 'badge--visible' : '') } icn-star'>${ build.iconic('star') }</a>
|
||||
<a class='badge $${ ((data.public==='1' && album.json.public!=='1') ? 'badge--visible' : '') } icn-share'>${ build.iconic('eye') }</a>
|
||||
</div>
|
||||
`
|
||||
|
||||
}
|
||||
|
||||
html += '</div>'
|
||||
html += `</div>`
|
||||
|
||||
return html
|
||||
|
||||
@ -103,24 +115,22 @@ build.photo = function(data) {
|
||||
|
||||
build.imageview = function(data, size, visibleControls) {
|
||||
|
||||
if (data==null) return ''
|
||||
|
||||
let html = ''
|
||||
|
||||
if (size==='big') {
|
||||
|
||||
if (visibleControls===true) html += `<div id='image' style='background-image: url(${ data.url })'></div>`
|
||||
else html += `<div id='image' style='background-image: url(${ data.url });' class='full'></div>`
|
||||
if (visibleControls===true) html += lychee.html`<div id='image' style='background-image: url($${ data.url })'></div>`
|
||||
else html += lychee.html`<div id='image' style='background-image: url($${ data.url });' class='full'></div>`
|
||||
|
||||
} else if (size==='medium') {
|
||||
|
||||
if (visibleControls===true) html += `<div id='image' style='background-image: url(${ data.medium })'></div>`
|
||||
else html += `<div id='image' style='background-image: url(${ data.medium });' class='full'></div>`
|
||||
if (visibleControls===true) html += lychee.html`<div id='image' style='background-image: url($${ data.medium })'></div>`
|
||||
else html += lychee.html`<div id='image' style='background-image: url($${ data.medium });' class='full'></div>`
|
||||
|
||||
} else if (size==='small') {
|
||||
|
||||
if (visibleControls===true) html += `<div id='image' class='small' style='background-image: url(${ data.url }); width: ${ data.width }px; height: ${ data.height }px; margin-top: -${ parseInt(data.height/2-20) }px; margin-left: -${ data.width/2 }px;'></div>`
|
||||
else html += `<div id='image' class='small' style='background-image: url(${ data.url }); width: ${ data.width }px; height: ${ data.height }px; margin-top: -${ parseInt(data.height/2) }px; margin-left: -${ data.width/2 }px;'></div>`
|
||||
if (visibleControls===true) html += lychee.html`<div id='image' class='small' style='background-image: url($${ data.url }); width: $${ data.width }px; height: $${ data.height }px; margin-top: -$${ parseInt(data.height/2-20) }px; margin-left: -$${ data.width/2 }px;'></div>`
|
||||
else html += lychee.html`<div id='image' class='small' style='background-image: url($${ data.url }); width: $${ data.width }px; height: $${ data.height }px; margin-top: -$${ parseInt(data.height/2) }px; margin-left: -$${ data.width/2 }px;'></div>`
|
||||
|
||||
}
|
||||
|
||||
@ -135,27 +145,29 @@ build.imageview = function(data, size, visibleControls) {
|
||||
|
||||
build.no_content = function(typ) {
|
||||
|
||||
let html = `
|
||||
let html = ''
|
||||
|
||||
html += `
|
||||
<div class='no_content fadeIn'>
|
||||
${ build.iconic(typ) }
|
||||
`
|
||||
|
||||
switch (typ) {
|
||||
case 'magnifying-glass':
|
||||
html += '<p>No results</p>'
|
||||
html += `<p>No results</p>`
|
||||
break
|
||||
case 'eye':
|
||||
html += '<p>No public albums</p>'
|
||||
html += `<p>No public albums</p>`
|
||||
break
|
||||
case 'cog':
|
||||
html += '<p>No configuration</p>'
|
||||
html += `<p>No configuration</p>`
|
||||
break
|
||||
case 'question-mark':
|
||||
html += '<p>Photo not found</p>'
|
||||
html += `<p>Photo not found</p>`
|
||||
break
|
||||
}
|
||||
|
||||
html += '</div>'
|
||||
html += `</div>`
|
||||
|
||||
return html
|
||||
|
||||
@ -163,8 +175,10 @@ build.no_content = function(typ) {
|
||||
|
||||
build.uploadModal = function(title, files) {
|
||||
|
||||
let html = `
|
||||
<h1>${ title }</h1>
|
||||
let html = ''
|
||||
|
||||
html += lychee.html`
|
||||
<h1>$${ title }</h1>
|
||||
<div class='rows'>
|
||||
`
|
||||
|
||||
@ -176,9 +190,9 @@ build.uploadModal = function(title, files) {
|
||||
|
||||
if (file.name.length>40) file.name = file.name.substr(0, 17) + '...' + file.name.substr(file.name.length-20, 20)
|
||||
|
||||
html += `
|
||||
html += lychee.html`
|
||||
<div class='row'>
|
||||
<a class='name'>${ lychee.escapeHTML(file.name) }</a>
|
||||
<a class='name'>$${ file.name }</a>
|
||||
`
|
||||
|
||||
if (file.supported===true) html += `<a class='status'></a>`
|
||||
@ -193,7 +207,7 @@ build.uploadModal = function(title, files) {
|
||||
|
||||
}
|
||||
|
||||
html += '</div>'
|
||||
html += `</div>`
|
||||
|
||||
return html
|
||||
|
||||
@ -208,7 +222,7 @@ build.tags = function(tags) {
|
||||
tags = tags.split(',')
|
||||
|
||||
tags.forEach(function(tag, index, array) {
|
||||
html += `<a class='tag'>${ tag }<span data-index='${ index }'>${ build.iconic('x') }</span></a>`
|
||||
html += lychee.html`<a class='tag'>$${ tag }<span data-index='$${ index }'>${ build.iconic('x') }</span></a>`
|
||||
})
|
||||
|
||||
} else {
|
||||
|
@ -101,7 +101,7 @@ contextMenu.albumTitle = function(albumID, e) {
|
||||
|
||||
if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg'
|
||||
|
||||
let title = `<img class='cover' width='16' height='16' src='${ this.thumbs[0] }'><div class='title'>${ this.title }</div>`
|
||||
let title = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
|
||||
|
||||
if (this.id!=albumID) items.push({ type: 'item', title, fn: () => lychee.goto(this.id) })
|
||||
|
||||
@ -131,7 +131,7 @@ contextMenu.mergeAlbum = function(albumID, e) {
|
||||
|
||||
if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg'
|
||||
|
||||
let title = `<img class='cover' width='16' height='16' src='${ this.thumbs[0] }'><div class='title'>${ this.title }</div>`
|
||||
let title = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
|
||||
|
||||
if (this.id!=albumID) items.push({ type: 'item', title, fn: () => album.merge([albumID, this.id]) })
|
||||
|
||||
@ -206,7 +206,7 @@ contextMenu.photoTitle = function(albumID, photoID, e) {
|
||||
// Generate list of albums
|
||||
$.each(data.content, function(index) {
|
||||
|
||||
let title = `<img class='cover' width='16' height='16' src='${ this.thumbUrl }'><div class='title'>${ this.title }</div>`
|
||||
let title = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbUrl }'><div class='title'>$${ this.title }</div>`
|
||||
|
||||
if (this.id!=photoID) items.push({ type: 'item', title, fn: () => lychee.goto(albumID + '/' + this.id) })
|
||||
|
||||
@ -254,7 +254,7 @@ contextMenu.move = function(photoIDs, e) {
|
||||
|
||||
if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg'
|
||||
|
||||
let title = `<img class='cover' width='16' height='16' src='${ this.thumbs[0] }'><div class='title'>${ this.title }</div>`
|
||||
let title = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
|
||||
|
||||
if (this.id!=album.getID()) items.push({ type: 'item', title, fn: () => photo.setAlbum(photoIDs, this.id) })
|
||||
|
||||
|
@ -107,9 +107,10 @@ header.hide = function(e, delay = 500) {
|
||||
|
||||
header.setTitle = function(title = 'Untitled') {
|
||||
|
||||
let $title = header.dom('#title')
|
||||
let $title = header.dom('#title'),
|
||||
html = lychee.html`$${ title }${ build.iconic('caret-bottom') }`
|
||||
|
||||
$title.html(title + build.iconic('caret-bottom'))
|
||||
$title.html(html)
|
||||
|
||||
return true
|
||||
|
||||
|
@ -119,12 +119,12 @@ lychee.login = function(data) {
|
||||
|
||||
lychee.loginDialog = function() {
|
||||
|
||||
let msg = `
|
||||
let msg = lychee.html`
|
||||
<p class='signIn'>
|
||||
<input class='text' name='username' autocomplete='username' type='text' value='' placeholder='username' autocapitalize='off' autocorrect='off'>
|
||||
<input class='text' name='password' autocomplete='current-password' type='password' value='' placeholder='password'>
|
||||
</p>
|
||||
<p class='version'>Lychee ${ lychee.version }<span> – <a target='_blank' href='${ lychee.updateURL }'>Update available!</a><span></p>
|
||||
<p class='version'>Lychee $${ lychee.version }<span> – <a target='_blank' href='$${ lychee.updateURL }'>Update available!</a><span></p>
|
||||
`
|
||||
|
||||
basicModal.show({
|
||||
@ -387,11 +387,46 @@ lychee.escapeHTML = function(html = '') {
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/`/g, '`')
|
||||
|
||||
return html
|
||||
|
||||
}
|
||||
|
||||
lychee.html = function(literalSections, ...substs) {
|
||||
|
||||
// Use raw literal sections: we don’t want
|
||||
// backslashes (\n etc.) to be interpreted
|
||||
let raw = literalSections.raw,
|
||||
result = ''
|
||||
|
||||
substs.forEach((subst, i) => {
|
||||
|
||||
// Retrieve the literal section preceding
|
||||
// the current substitution
|
||||
let lit = raw[i]
|
||||
|
||||
// If the substitution is preceded by a dollar sign,
|
||||
// we escape special characters in it
|
||||
if (lit.slice(-1)==='$') {
|
||||
subst = lychee.escapeHTML(subst)
|
||||
lit = lit.slice(0, -1)
|
||||
}
|
||||
|
||||
result += lit
|
||||
result += subst
|
||||
|
||||
})
|
||||
|
||||
// Take care of last literal section
|
||||
// (Never fails, because an empty template string
|
||||
// produces one literal section, an empty string)
|
||||
result += raw[raw.length-1]
|
||||
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
lychee.error = function(errorThrown, params, data) {
|
||||
|
||||
console.error({
|
||||
|
@ -55,8 +55,10 @@ password.getDialog = function(albumID, callback) {
|
||||
const action = (data) => password.get(albumID, callback, data.password)
|
||||
|
||||
const cancel = () => {
|
||||
|
||||
basicModal.close()
|
||||
if (visible.albums()===false) lychee.goto()
|
||||
|
||||
}
|
||||
|
||||
let msg = `
|
||||
|
@ -244,14 +244,14 @@ photo.delete = function(photoIDs) {
|
||||
action.title = 'Delete Photo'
|
||||
cancel.title = 'Keep Photo'
|
||||
|
||||
msg = `<p>Are you sure you want to delete the photo '${ photoTitle }'? This action can't be undone!</p>`
|
||||
msg = lychee.html`<p>Are you sure you want to delete the photo '$${ photoTitle }'? This action can't be undone!</p>`
|
||||
|
||||
} else {
|
||||
|
||||
action.title = 'Delete Photo'
|
||||
cancel.title = 'Keep Photo'
|
||||
|
||||
msg = `<p>Are you sure you want to delete all ${ photoIDs.length } selected photo? This action can't be undone!</p>`
|
||||
msg = lychee.html`<p>Are you sure you want to delete all $${ photoIDs.length } selected photo? This action can't be undone!</p>`
|
||||
|
||||
}
|
||||
|
||||
@ -317,10 +317,10 @@ photo.setTitle = function(photoIDs) {
|
||||
|
||||
}
|
||||
|
||||
let input = `<input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='${ lychee.escapeHTML(oldTitle) }'>`
|
||||
let input = lychee.html`<input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='$${ oldTitle }'>`
|
||||
|
||||
if (photoIDs.length===1) msg = `<p>Enter a new title for this photo: ${ input }</p>`
|
||||
else msg = `<p>Enter a title for all ${ photoIDs.length } selected photos: ${ input }</p>`
|
||||
if (photoIDs.length===1) msg = lychee.html`<p>Enter a new title for this photo: ${ input }</p>`
|
||||
else msg = lychee.html`<p>Enter a title for all $${ photoIDs.length } selected photos: ${ input }</p>`
|
||||
|
||||
basicModal.show({
|
||||
body: msg,
|
||||
@ -488,7 +488,7 @@ photo.setDescription = function(photoID) {
|
||||
}
|
||||
|
||||
basicModal.show({
|
||||
body: `<p>Enter a description for this photo: <input class='text' name='description' type='text' maxlength='800' placeholder='Description' value='${ lychee.escapeHTML(oldDescription) }'></p>`,
|
||||
body: lychee.html`<p>Enter a description for this photo: <input class='text' name='description' type='text' maxlength='800' placeholder='Description' value='$${ oldDescription }'></p>`,
|
||||
buttons: {
|
||||
action: {
|
||||
title: 'Set Description',
|
||||
@ -534,10 +534,10 @@ photo.editTags = function(photoIDs) {
|
||||
|
||||
}
|
||||
|
||||
let input = `<input class='text' name='tags' type='text' maxlength='800' placeholder='Tags' value='${ oldTags }'>`
|
||||
let input = lychee.html`<input class='text' name='tags' type='text' maxlength='800' placeholder='Tags' value='$${ oldTags }'>`
|
||||
|
||||
if (photoIDs.length===1) msg = `<p>Enter your tags for this photo. You can add multiple tags by separating them with a comma: ${ input }</p>`
|
||||
else msg = `<p>Enter your tags for all ${ photoIDs.length } selected photos. Existing tags will be overwritten. You can add multiple tags by separating them with a comma: ${ input }</p>`
|
||||
if (photoIDs.length===1) msg = lychee.html`<p>Enter your tags for this photo. You can add multiple tags by separating them with a comma: ${ input }</p>`
|
||||
else msg = lychee.html`<p>Enter your tags for all $${ photoIDs.length } selected photos. Existing tags will be overwritten. You can add multiple tags by separating them with a comma: ${ input }</p>`
|
||||
|
||||
basicModal.show({
|
||||
body: msg,
|
||||
|
@ -404,10 +404,10 @@ settings.setDropboxKey = function(callback) {
|
||||
|
||||
}
|
||||
|
||||
let msg = `
|
||||
let msg = lychee.html`
|
||||
<p>
|
||||
In order to import photos from your Dropbox, you need a valid drop-ins app key from <a href='https://www.dropbox.com/developers/apps/create'>their website</a>. Generate yourself a personal key and enter it below:
|
||||
<input class='text' name='key' type='text' placeholder='Dropbox API Key' value='${ lychee.dropboxKey }'>
|
||||
<input class='text' name='key' type='text' placeholder='Dropbox API Key' value='$${ lychee.dropboxKey }'>
|
||||
</p>
|
||||
`
|
||||
|
||||
|
@ -93,13 +93,17 @@ sidebar.setSelectable = function(selectable = true) {
|
||||
|
||||
}
|
||||
|
||||
sidebar.changeAttr = function(attr, value = '-') {
|
||||
sidebar.changeAttr = function(attr, value = '-', dangerouslySetInnerHTML = false) {
|
||||
|
||||
if (attr==null || attr==='') return false
|
||||
|
||||
// Set a default for the value
|
||||
if (value==null || value==='') value = '-'
|
||||
|
||||
// Escape value
|
||||
if (dangerouslySetInnerHTML===false) value = lychee.escapeHTML(value)
|
||||
|
||||
// Set new value
|
||||
sidebar.dom('.attr_' + attr).html(value)
|
||||
|
||||
return true
|
||||
@ -339,14 +343,14 @@ sidebar.render = function(structure) {
|
||||
if (value==='' || value==null) value = '-'
|
||||
|
||||
// Wrap span-element around value for easier selecting on change
|
||||
value = `<span class='attr_${ row.title.toLowerCase() }'>${ value }</span>`
|
||||
value = lychee.html`<span class='attr_$${ row.title.toLowerCase() }'>$${ value }</span>`
|
||||
|
||||
// Add edit-icon to the value when editable
|
||||
if (row.editable===true) value += ' ' + build.editIcon('edit_' + row.title.toLowerCase())
|
||||
|
||||
_html += `
|
||||
_html += lychee.html`
|
||||
<tr>
|
||||
<td>${ row.title }</td>
|
||||
<td>$${ row.title }</td>
|
||||
<td>${ value }</td>
|
||||
</tr>
|
||||
`
|
||||
@ -363,20 +367,19 @@ sidebar.render = function(structure) {
|
||||
|
||||
let renderTags = function(section) {
|
||||
|
||||
let _html = ''
|
||||
|
||||
_html += `
|
||||
<div class='divider'>
|
||||
<h1>${ section.title }</h1>
|
||||
</div>
|
||||
<div id='tags'>
|
||||
<div class='attr_${ section.title.toLowerCase() }'>${ section.value }</div>
|
||||
`
|
||||
let _html = '',
|
||||
editable = ''
|
||||
|
||||
// Add edit-icon to the value when editable
|
||||
if (section.editable===true) _html += build.editIcon('edit_tags')
|
||||
if (section.editable===true) editable = build.editIcon('edit_tags')
|
||||
|
||||
_html += `
|
||||
_html += lychee.html`
|
||||
<div class='divider'>
|
||||
<h1>$${ section.title }</h1>
|
||||
</div>
|
||||
<div id='tags'>
|
||||
<div class='attr_$${ section.title.toLowerCase() }'>${ section.value }</div>
|
||||
${ editable }
|
||||
</div>
|
||||
`
|
||||
|
||||
|
@ -338,7 +338,7 @@ upload.start = {
|
||||
}
|
||||
|
||||
basicModal.show({
|
||||
body: `<p>Please enter the direct link to a photo to import it: <input class='text' name='link' type='text' placeholder='http://' value='${ url }'></p>`,
|
||||
body: lychee.html`<p>Please enter the direct link to a photo to import it: <input class='text' name='link' type='text' placeholder='http://' value='$${ url }'></p>`,
|
||||
buttons: {
|
||||
action: {
|
||||
title: 'Import',
|
||||
@ -444,7 +444,7 @@ upload.start = {
|
||||
}
|
||||
|
||||
basicModal.show({
|
||||
body: `<p>This action will import all photos, folders and sub-folders which are located in the following directory. The <b>original files will be deleted</b> after the import when possible. <input class='text' name='path' type='text' maxlength='100' placeholder='Absolute path to directory' value='${ lychee.location }uploads/import/'></p>`,
|
||||
body: lychee.html`<p>This action will import all photos, folders and sub-folders which are located in the following directory. The <b>original files will be deleted</b> after the import when possible. <input class='text' name='path' type='text' maxlength='100' placeholder='Absolute path to directory' value='$${ lychee.location }uploads/import/'></p>`,
|
||||
buttons: {
|
||||
action: {
|
||||
title: 'Import',
|
||||
|
@ -72,6 +72,8 @@ view.albums = {
|
||||
|
||||
let title = albums.getByID(albumID).title
|
||||
|
||||
title = lychee.escapeHTML(title)
|
||||
|
||||
$('.album[data-id="' + albumID + '"] .overlay h1')
|
||||
.html(title)
|
||||
.attr('title', title)
|
||||
@ -164,6 +166,8 @@ view.album = {
|
||||
|
||||
let title = album.json.content[photoID].title
|
||||
|
||||
title = lychee.escapeHTML(title)
|
||||
|
||||
$('.photo[data-id="' + photoID + '"] .overlay h1')
|
||||
.html(title)
|
||||
.attr('title', title)
|
||||
@ -199,7 +203,6 @@ view.album = {
|
||||
if (!visible.albums()) {
|
||||
album.json.num--
|
||||
view.album.num()
|
||||
view.album.title()
|
||||
}
|
||||
})
|
||||
|
||||
@ -396,7 +399,7 @@ view.photo = {
|
||||
|
||||
tags: function() {
|
||||
|
||||
sidebar.changeAttr('tags', build.tags(photo.json.tags))
|
||||
sidebar.changeAttr('tags', build.tags(photo.json.tags), true)
|
||||
sidebar.bind()
|
||||
|
||||
},
|
||||
@ -416,7 +419,7 @@ view.photo = {
|
||||
let nextPhotoID = album.json.content[photo.getID()].nextPhoto,
|
||||
nextPhoto = album.json.content[nextPhotoID]
|
||||
|
||||
$nextArrow.css('background-image', `linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url("${ nextPhoto.thumbUrl }")`)
|
||||
$nextArrow.css('background-image', lychee.html`linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url("$${ nextPhoto.thumbUrl }")`)
|
||||
|
||||
}
|
||||
|
||||
@ -426,7 +429,7 @@ view.photo = {
|
||||
let previousPhotoID = album.json.content[photo.getID()].previousPhoto,
|
||||
previousPhoto = album.json.content[previousPhotoID]
|
||||
|
||||
$previousArrow.css('background-image', `linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url("${ previousPhoto.thumbUrl }")`)
|
||||
$previousArrow.css('background-image', lychee.html`linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url("$${ previousPhoto.thumbUrl }")`)
|
||||
|
||||
}
|
||||
|
||||
|
@ -3,18 +3,74 @@
|
||||
* @copyright 2015 by Tobias Reich
|
||||
*/
|
||||
|
||||
let lychee = {
|
||||
content: $('#content'),
|
||||
getEventName() {
|
||||
// Sub-implementation of Lychee -------------------------------------------------------------- //
|
||||
|
||||
let lychee = {}
|
||||
|
||||
lychee.content = $('#content')
|
||||
|
||||
lychee.getEventName = function() {
|
||||
|
||||
let touchendSupport = (/Android|iPhone|iPad|iPod/i).test(navigator.userAgent || navigator.vendor || window.opera) && ('ontouchend' in document.documentElement),
|
||||
eventName = (touchendSupport===true ? 'touchend' : 'click')
|
||||
|
||||
return eventName
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
lychee.escapeHTML = function(html = '') {
|
||||
|
||||
// Ensure that html is a string
|
||||
html += ''
|
||||
|
||||
// Escape all critical characters
|
||||
html = html.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/`/g, '`')
|
||||
|
||||
return html
|
||||
|
||||
}
|
||||
|
||||
lychee.html = function(literalSections, ...substs) {
|
||||
|
||||
// Use raw literal sections: we don’t want
|
||||
// backslashes (\n etc.) to be interpreted
|
||||
let raw = literalSections.raw,
|
||||
result = ''
|
||||
|
||||
substs.forEach((subst, i) => {
|
||||
|
||||
// Retrieve the literal section preceding
|
||||
// the current substitution
|
||||
let lit = raw[i]
|
||||
|
||||
// If the substitution is preceded by a dollar sign,
|
||||
// we escape special characters in it
|
||||
if (lit.slice(-1)==='$') {
|
||||
subst = lychee.escapeHTML(subst)
|
||||
lit = lit.slice(0, -1)
|
||||
}
|
||||
|
||||
result += lit
|
||||
result += subst
|
||||
|
||||
})
|
||||
|
||||
// Take care of last literal section
|
||||
// (Never fails, because an empty template string
|
||||
// produces one literal section, an empty string)
|
||||
result += raw[raw.length-1]
|
||||
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
// Main -------------------------------------------------------------- //
|
||||
|
||||
let loadingBar = { show() {}, hide() {} },
|
||||
imageview = $('#imageview')
|
||||
|
||||
@ -99,7 +155,7 @@ const loadPhotoInfo = function(photoID) {
|
||||
// Set title
|
||||
if (!data.title) data.title = 'Untitled'
|
||||
document.title = 'Lychee - ' + data.title
|
||||
header.dom('#title').html(data.title)
|
||||
header.dom('#title').html(lychee.escapeHTML(data.title))
|
||||
|
||||
let size = getPhotoSize(data)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user