Compare commits
1 commit
master
...
title-acro
Author | SHA1 | Date | |
---|---|---|---|
|
76d96e76fe |
15 changed files with 1967 additions and 368 deletions
|
@ -78,6 +78,7 @@ TEMPLATES = [
|
||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
'django.template.context_processors.media',
|
'django.template.context_processors.media',
|
||||||
|
'content.context_processors.random_title',
|
||||||
],
|
],
|
||||||
'loaders': [
|
'loaders': [
|
||||||
'django.template.loaders.filesystem.Loader',
|
'django.template.loaders.filesystem.Loader',
|
||||||
|
|
170
camp/static/css/jquery.bxslider.css
Normal file
170
camp/static/css/jquery.bxslider.css
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
/** VARIABLES
|
||||||
|
===================================*/
|
||||||
|
/** RESET AND LAYOUT
|
||||||
|
===================================*/
|
||||||
|
.bx-wrapper {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
padding: 0;
|
||||||
|
*zoom: 1;
|
||||||
|
-ms-touch-action: pan-y;
|
||||||
|
touch-action: pan-y;
|
||||||
|
}
|
||||||
|
.bx-wrapper img {
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.bxslider {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
ul.bxslider {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.bx-viewport {
|
||||||
|
/*fix other elements on the page moving (on Chrome)*/
|
||||||
|
-webkit-transform: translatez(0);
|
||||||
|
}
|
||||||
|
/** THEME
|
||||||
|
===================================*/
|
||||||
|
|
||||||
|
.bx-wrapper .bx-pager,
|
||||||
|
.bx-wrapper .bx-controls-auto {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -30px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
/* LOADER */
|
||||||
|
.bx-wrapper .bx-loading {
|
||||||
|
min-height: 50px;
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
/* PAGER */
|
||||||
|
.bx-wrapper .bx-pager {
|
||||||
|
text-align: center;
|
||||||
|
font-size: .85em;
|
||||||
|
font-family: Arial;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #666;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-pager.bx-default-pager a {
|
||||||
|
background: #666;
|
||||||
|
text-indent: -9999px;
|
||||||
|
display: block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
margin: 0 5px;
|
||||||
|
outline: 0;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-pager.bx-default-pager a:hover,
|
||||||
|
.bx-wrapper .bx-pager.bx-default-pager a.active,
|
||||||
|
.bx-wrapper .bx-pager.bx-default-pager a:focus {
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-pager-item,
|
||||||
|
.bx-wrapper .bx-controls-auto .bx-controls-auto-item {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: bottom;
|
||||||
|
*zoom: 1;
|
||||||
|
*display: inline;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-pager-item {
|
||||||
|
font-size: 0;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
/* DIRECTION CONTROLS (NEXT / PREV) */
|
||||||
|
.bx-wrapper .bx-prev {
|
||||||
|
left: 10px;
|
||||||
|
background: url('controls.png') no-repeat 0 -32px;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-prev:hover,
|
||||||
|
.bx-wrapper .bx-prev:focus {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-next {
|
||||||
|
right: 30px;
|
||||||
|
background: url('controls.png') no-repeat -43px -32px;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-next:hover,
|
||||||
|
.bx-wrapper .bx-next:focus {
|
||||||
|
background-position: -43px 0;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-controls-direction a {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -16px;
|
||||||
|
outline: 0;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
text-indent: -9999px;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-controls-direction a.disabled {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
/* AUTO CONTROLS (START / STOP) */
|
||||||
|
.bx-wrapper .bx-controls-auto {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-controls-auto .bx-start {
|
||||||
|
display: block;
|
||||||
|
text-indent: -9999px;
|
||||||
|
width: 10px;
|
||||||
|
height: 11px;
|
||||||
|
outline: 0;
|
||||||
|
background: url('images/controls.png') -86px -11px no-repeat;
|
||||||
|
margin: 0 3px;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-controls-auto .bx-start:hover,
|
||||||
|
.bx-wrapper .bx-controls-auto .bx-start.active,
|
||||||
|
.bx-wrapper .bx-controls-auto .bx-start:focus {
|
||||||
|
background-position: -86px 0;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-controls-auto .bx-stop {
|
||||||
|
display: block;
|
||||||
|
text-indent: -9999px;
|
||||||
|
width: 9px;
|
||||||
|
height: 11px;
|
||||||
|
outline: 0;
|
||||||
|
background: url('images/controls.png') -86px -44px no-repeat;
|
||||||
|
margin: 0 3px;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-controls-auto .bx-stop:hover,
|
||||||
|
.bx-wrapper .bx-controls-auto .bx-stop.active,
|
||||||
|
.bx-wrapper .bx-controls-auto .bx-stop:focus {
|
||||||
|
background-position: -86px -33px;
|
||||||
|
}
|
||||||
|
/* PAGER WITH AUTO-CONTROLS HYBRID LAYOUT */
|
||||||
|
.bx-wrapper .bx-controls.bx-has-controls-auto.bx-has-pager .bx-pager {
|
||||||
|
text-align: left;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-controls.bx-has-controls-auto.bx-has-pager .bx-controls-auto {
|
||||||
|
right: 0;
|
||||||
|
width: 35px;
|
||||||
|
}
|
||||||
|
/* IMAGE CAPTIONS */
|
||||||
|
.bx-wrapper .bx-caption {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background: #666;
|
||||||
|
background: rgba(80, 80, 80, 0.75);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.bx-wrapper .bx-caption span {
|
||||||
|
color: #fff;
|
||||||
|
font-family: Arial;
|
||||||
|
display: block;
|
||||||
|
font-size: .85em;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
|
@ -58,11 +58,6 @@ a:focus, a:hover {
|
||||||
padding-left: 0em !important;
|
padding-left: 0em !important;
|
||||||
padding-top: 10px; /* moved below search bar */
|
padding-top: 10px; /* moved below search bar */
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 39.9375em) {
|
|
||||||
.special-column {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.index-text {
|
.index-text {
|
||||||
padding-left: 3%;
|
padding-left: 3%;
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
.gallery-dialog {
|
.gallery-dialog {
|
||||||
background: #282828;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid black;
|
|
||||||
}
|
|
||||||
.gallery-dialog, .gallery-main-stage {
|
|
||||||
&:open {
|
&:open {
|
||||||
background: #282828;
|
background: #282828;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -27,13 +22,11 @@
|
||||||
height: calc(100vh - 180px);
|
height: calc(100vh - 180px);
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 39.9375em) {
|
@media screen and (max-width: 39.9375em) {
|
||||||
&:open {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
.photo-container .image {
|
.photo-container .image {
|
||||||
height: auto;
|
height: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.main-image {
|
.main-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -42,20 +35,15 @@
|
||||||
}
|
}
|
||||||
.caption {
|
.caption {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
font-size: 14px;
|
font-size: 18px;
|
||||||
color: lightgray;
|
color: lightgray;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.thumbnails {
|
.thumbnails {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 10px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 39.9375em) {
|
|
||||||
.thumbnails {
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
.thumbnail {
|
.thumbnail {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
|
@ -64,6 +52,9 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
}
|
}
|
||||||
|
.thumbnail.active {
|
||||||
|
border-color: blue;
|
||||||
|
}
|
||||||
.nav-btn {
|
.nav-btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
@ -93,8 +84,7 @@
|
||||||
.close:hover {
|
.close:hover {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
.download.disabled,
|
.download.disabled {
|
||||||
.edit.disabled {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.download {
|
.download {
|
||||||
|
@ -105,15 +95,7 @@
|
||||||
right: 24px
|
right: 24px
|
||||||
|
|
||||||
}
|
}
|
||||||
.edit {
|
.download:hover {
|
||||||
color: lightgray !important;
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
left: 4px
|
|
||||||
}
|
|
||||||
.download:hover,
|
|
||||||
.edit:hover {
|
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 39.9375em) {
|
@media screen and (max-width: 39.9375em) {
|
||||||
|
@ -140,60 +122,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-main-stage {
|
|
||||||
.photo-container .image {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
.download {
|
|
||||||
right: 4px
|
|
||||||
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 39.9375em) {
|
|
||||||
.download {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.gallery-dialog, .inline-gallery {
|
|
||||||
.thumbnail {
|
|
||||||
cursor: pointer;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
}
|
|
||||||
.thumbnail.active {
|
|
||||||
cursor: initial;
|
|
||||||
border-color: #1779ba;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-photo {
|
|
||||||
display: inline-block;
|
|
||||||
.caption {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.photo {
|
|
||||||
scroll-margin-top: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.inline {
|
|
||||||
margin-left: -12px;
|
|
||||||
width: 100vw;
|
|
||||||
li {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.inline-photo {
|
|
||||||
display: block;
|
|
||||||
.caption {
|
|
||||||
display: block;
|
|
||||||
color: #ffffff;
|
|
||||||
padding-left: 2px;
|
|
||||||
padding-right: 2px;
|
|
||||||
p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 39.9375em) {
|
|
||||||
.gallery {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,34 +1,9 @@
|
||||||
function isNumber(x) {
|
|
||||||
return parseFloat(x) == x
|
|
||||||
};
|
|
||||||
|
|
||||||
function isMobile() {
|
|
||||||
return getComputedStyle(document.querySelector('.special-column')).paddingRight == '0px'
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInView(el) {
|
|
||||||
var rect = el.getBoundingClientRect();
|
|
||||||
var elemTop = rect.top;
|
|
||||||
var elemBottom = rect.bottom;
|
|
||||||
|
|
||||||
// Only completely visible elements return true:
|
|
||||||
var isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight);
|
|
||||||
// Partially visible elements return true:
|
|
||||||
//isVisible = elemTop < window.innerHeight && elemBottom >= 0;
|
|
||||||
return isVisible;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseGalleryHash() {
|
function parseGalleryHash() {
|
||||||
let parts = document.location.hash.slice(1).split('/')
|
let parts = document.location.hash.slice(1).split('/')
|
||||||
if (parts.lengh == 1 && isNumber(parts[0])) {
|
if (parts[0] == 'g') {
|
||||||
return {
|
return {
|
||||||
'gallery': 'inline',
|
"gallery": parseInt(parts[1], 10),
|
||||||
"image": parseInt(parts[0], 10) - 1,
|
"image": parseInt(parts[2], 10),
|
||||||
}
|
|
||||||
} else if (parts.length == 2 && isNumber(parts[0]) && isNumber(parts[1])) {
|
|
||||||
return {
|
|
||||||
"gallery": parseInt(parts[0], 10) - 1,
|
|
||||||
"image": parseInt(parts[1], 10) - 1,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
|
@ -36,38 +11,9 @@ function parseGalleryHash() {
|
||||||
|
|
||||||
function loadGalleries() {
|
function loadGalleries() {
|
||||||
let current = parseGalleryHash()
|
let current = parseGalleryHash()
|
||||||
let galleryIdx = 0
|
let galleryIdx = 1
|
||||||
document.querySelectorAll('.gallery').forEach(gallery => {
|
document.querySelectorAll('.gallery').forEach(gallery => {
|
||||||
var photos = [], dialog
|
var photos = [], dialog
|
||||||
gallery.querySelectorAll(".photo").forEach(img => {
|
|
||||||
const src = img.src.replace('_thumbnail', '_display')
|
|
||||||
let photo = {
|
|
||||||
src: src,
|
|
||||||
caption: img.dataset.caption,
|
|
||||||
orig: img.dataset.orig,
|
|
||||||
edit: img.dataset.edit
|
|
||||||
}
|
|
||||||
photos.push(photo)
|
|
||||||
img.parentElement.href = `#${galleryIdx+1}/${photos.length}`
|
|
||||||
img.parentElement.addEventListener("click", event => {
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
dialog.open(src, -1, event)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if (isMobile()) {
|
|
||||||
dialog = loadInlineGallery(photos, galleryIdx, null, gallery)
|
|
||||||
} else {
|
|
||||||
dialog = loadGallery(photos, galleryIdx, null, gallery)
|
|
||||||
}
|
|
||||||
dialog.element && gallery.appendChild(dialog.element)
|
|
||||||
if (current.gallery == galleryIdx) {
|
|
||||||
dialog.open(undefined, current.image)
|
|
||||||
}
|
|
||||||
galleryIdx += 1
|
|
||||||
})
|
|
||||||
document.querySelectorAll('.inline-gallery').forEach(gallery => {
|
|
||||||
var photos = [], stage
|
|
||||||
gallery.querySelectorAll(".photo").forEach(img => {
|
gallery.querySelectorAll(".photo").forEach(img => {
|
||||||
const src = img.src.replace('_thumbnail', '_display')
|
const src = img.src.replace('_thumbnail', '_display')
|
||||||
let photo = {
|
let photo = {
|
||||||
|
@ -79,86 +25,20 @@ function loadGalleries() {
|
||||||
img.parentElement.addEventListener("click", event => {
|
img.parentElement.addEventListener("click", event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
dialog.open(src, -1, event)
|
dialog.open(src)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const main = document.querySelector('.gallery-main-stage')
|
dialog = loadGallery(photos, galleryIdx)
|
||||||
stage = loadGallery(photos, 'inline', main, gallery)
|
gallery.appendChild(dialog.element)
|
||||||
|
if (current.gallery == galleryIdx) {
|
||||||
|
dialog.open(undefined, current.image)
|
||||||
|
}
|
||||||
|
galleryIdx += 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadInlineGallery(images, idx, main, thumbnails) {
|
function loadGallery(images, idx) {
|
||||||
thumbnails.querySelectorAll('.photo').forEach(img => {
|
var gallery = document.createElement("dialog")
|
||||||
const container = document.createElement('div')
|
|
||||||
container.classList.add("inline-photo")
|
|
||||||
container.innerHTML = `
|
|
||||||
<img class="photo" src="${img.src}">
|
|
||||||
<div class="caption">${img.dataset.caption||''}</div>
|
|
||||||
`
|
|
||||||
img.replaceWith(container)
|
|
||||||
})
|
|
||||||
|
|
||||||
function scroll(event) {
|
|
||||||
let found = false
|
|
||||||
let index = 0
|
|
||||||
thumbnails.querySelectorAll('.photo').forEach(img => {
|
|
||||||
if (!found) {
|
|
||||||
if (isInView(img)) {
|
|
||||||
found = true
|
|
||||||
const hash = `#${idx + 1}/${index + 1}`
|
|
||||||
if (document.location.hash != hash) {
|
|
||||||
document.location.hash = hash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
index += 1
|
|
||||||
})
|
|
||||||
/*
|
|
||||||
// fixme this jumps don't do it
|
|
||||||
if (!found && document.location.hash.slice(1).length) {
|
|
||||||
document.location.hash = ''
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
element: null,
|
|
||||||
open: function(src, newIdx=-1, event=null) {
|
|
||||||
let img
|
|
||||||
if (newIdx != -1) {
|
|
||||||
img = thumbnails.querySelectorAll('.photo')[newIdx - 1]
|
|
||||||
}
|
|
||||||
if (thumbnails.classList.contains("inline")) {
|
|
||||||
thumbnails.classList.remove("inline");
|
|
||||||
thumbnails.querySelectorAll('.photo').forEach(img => {
|
|
||||||
img.src = img.src.replace('_display', '_thumbnail')
|
|
||||||
})
|
|
||||||
thumbnails.previousElementSibling.scrollIntoView()
|
|
||||||
window.removeEventListener("scroll", scroll)
|
|
||||||
} else {
|
|
||||||
thumbnails.classList.add("inline");
|
|
||||||
thumbnails.querySelectorAll('.photo').forEach(img => {
|
|
||||||
img.src = img.src.replace('_thumbnail', '_display')
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
if (img) {
|
|
||||||
img.scrollIntoView()
|
|
||||||
} else if (event && event.target) {
|
|
||||||
event.target.scrollIntoView()
|
|
||||||
}
|
|
||||||
}, 20)
|
|
||||||
window.addEventListener("scroll", scroll)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function loadGallery(images, idx, main, thumbnails) {
|
|
||||||
let gallery;
|
|
||||||
|
|
||||||
if (idx == 'inline') {
|
|
||||||
gallery = main
|
|
||||||
} else {
|
|
||||||
gallery = document.createElement("dialog")
|
|
||||||
gallery.classList.add("gallery-dialog")
|
gallery.classList.add("gallery-dialog")
|
||||||
gallery.innerHTML = `
|
gallery.innerHTML = `
|
||||||
<div class="photo-container">
|
<div class="photo-container">
|
||||||
|
@ -169,7 +49,6 @@ function loadGallery(images, idx, main, thumbnails) {
|
||||||
<button class="next nav-btn">Next ❯</button>
|
<button class="next nav-btn">Next ❯</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="close">✖</button>
|
<button class="close">✖</button>
|
||||||
<a class="edit disabled" target="_blank" title="edit">✎</a>
|
|
||||||
<a class="download disabled" target="_blank" download title="download original">
|
<a class="download disabled" target="_blank" download title="download original">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 304 384"><path fill="currentColor" d="M299 128L149 277L0 128h85V0h128v128h86zM0 320h299v43H0v-43z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 304 384"><path fill="currentColor" d="M299 128L149 277L0 128h85V0h128v128h86zM0 320h299v43H0v-43z"/></svg>
|
||||||
</a>
|
</a>
|
||||||
|
@ -191,19 +70,14 @@ function loadGallery(images, idx, main, thumbnails) {
|
||||||
gallery.close();
|
gallery.close();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
let currentIndex = 0;
|
let currentIndex = 0;
|
||||||
|
|
||||||
const mainImage = gallery.querySelector(".main-image");
|
const mainImage = gallery.querySelector(".main-image");
|
||||||
const caption = gallery.querySelector(".caption");
|
const caption = gallery.querySelector(".caption");
|
||||||
const prevBtn = gallery.querySelector(".prev.nav-btn");
|
const prevBtn = gallery.querySelector(".prev.nav-btn");
|
||||||
const nextBtn = gallery.querySelector(".next.nav-btn");
|
const nextBtn = gallery.querySelector(".next.nav-btn");
|
||||||
|
const thumbnailsContainer = gallery.querySelector(".thumbnails");
|
||||||
const download = gallery.querySelector(".download");
|
const download = gallery.querySelector(".download");
|
||||||
const edit = gallery.querySelector(".edit");
|
|
||||||
let thumbnailsContainer
|
|
||||||
if (idx != 'inline') {
|
|
||||||
thumbnailsContainer = gallery.querySelector(".thumbnails");
|
|
||||||
}
|
|
||||||
|
|
||||||
gallery.addEventListener("keydown", event => {
|
gallery.addEventListener("keydown", event => {
|
||||||
if (event.keyCode == 39) {
|
if (event.keyCode == 39) {
|
||||||
|
@ -223,18 +97,9 @@ function loadGallery(images, idx, main, thumbnails) {
|
||||||
mainImage.src = images[index].src;
|
mainImage.src = images[index].src;
|
||||||
}, 0)
|
}, 0)
|
||||||
caption.innerHTML = images[index].caption;
|
caption.innerHTML = images[index].caption;
|
||||||
if (idx == 'inline') {
|
|
||||||
thumbnails.querySelectorAll(".thumbnail").forEach((thumb, i) => {
|
|
||||||
thumb.classList.toggle("active", i === index);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
gallery.querySelectorAll(".thumbnail").forEach((thumb, i) => {
|
gallery.querySelectorAll(".thumbnail").forEach((thumb, i) => {
|
||||||
thumb.classList.toggle("active", i === index);
|
thumb.classList.toggle("active", i === index);
|
||||||
});
|
});
|
||||||
if (thumbnailsContainer && !isMobile()) {
|
|
||||||
gallery.querySelector('.active').scrollIntoView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (images[index].orig) {
|
if (images[index].orig) {
|
||||||
download.href = images[index].orig
|
download.href = images[index].orig
|
||||||
download.classList.remove('disabled')
|
download.classList.remove('disabled')
|
||||||
|
@ -242,22 +107,9 @@ function loadGallery(images, idx, main, thumbnails) {
|
||||||
download.href = ""
|
download.href = ""
|
||||||
download.classList.add('disabled')
|
download.classList.add('disabled')
|
||||||
}
|
}
|
||||||
if (edit) {
|
|
||||||
if (images[index].edit) {
|
|
||||||
edit.href = images[index].edit
|
|
||||||
edit.classList.remove('disabled')
|
|
||||||
} else {
|
|
||||||
edit.href = ""
|
|
||||||
edit.classList.add('disabled')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevBtn.disabled = index === 0;
|
prevBtn.disabled = index === 0;
|
||||||
nextBtn.disabled = index === images.length - 1;
|
nextBtn.disabled = index === images.length - 1;
|
||||||
if (idx == 'inline') {
|
document.location.hash = `#g/${idx}/${index}`
|
||||||
document.location.hash = `#${index + 1}`
|
|
||||||
} else {
|
|
||||||
document.location.hash = `#${idx + 1}/${index + 1}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createThumbnails() {
|
function createThumbnails() {
|
||||||
|
@ -265,9 +117,7 @@ function loadGallery(images, idx, main, thumbnails) {
|
||||||
const thumb = document.createElement("img");
|
const thumb = document.createElement("img");
|
||||||
thumb.src = img.src.replace('_display', '_thumbnail');
|
thumb.src = img.src.replace('_display', '_thumbnail');
|
||||||
thumb.classList.add("thumbnail");
|
thumb.classList.add("thumbnail");
|
||||||
thumb.onclick = (event) => {
|
thumb.onclick = () => {
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
currentIndex = index;
|
currentIndex = index;
|
||||||
updateGallery(index);
|
updateGallery(index);
|
||||||
};
|
};
|
||||||
|
@ -281,8 +131,6 @@ function loadGallery(images, idx, main, thumbnails) {
|
||||||
}
|
}
|
||||||
|
|
||||||
prevBtn.onclick = () => {
|
prevBtn.onclick = () => {
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
if (currentIndex > 0) {
|
if (currentIndex > 0) {
|
||||||
currentIndex--;
|
currentIndex--;
|
||||||
updateGallery(currentIndex);
|
updateGallery(currentIndex);
|
||||||
|
@ -290,31 +138,29 @@ function loadGallery(images, idx, main, thumbnails) {
|
||||||
};
|
};
|
||||||
|
|
||||||
nextBtn.onclick = () => {
|
nextBtn.onclick = () => {
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
if (currentIndex < images.length - 1) {
|
if (currentIndex < images.length - 1) {
|
||||||
currentIndex++;
|
currentIndex++;
|
||||||
updateGallery(currentIndex);
|
updateGallery(currentIndex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
idx != 'inline' && createThumbnails();
|
createThumbnails();
|
||||||
updateGallery(currentIndex);
|
updateGallery(currentIndex);
|
||||||
|
|
||||||
const imagesIndex = images.map(img => img.src);
|
const imagesIndex = images.map(img => img.src);
|
||||||
return {
|
return {
|
||||||
element: gallery,
|
element: gallery,
|
||||||
open: function(src, newIdx=-1) {
|
open: function(src, idx=-1) {
|
||||||
if (newIdx != -1) {
|
if (idx != -1) {
|
||||||
currentIndex = newIdx
|
currentIndex = idx
|
||||||
} else {
|
} else {
|
||||||
currentIndex = imagesIndex.indexOf(src)
|
currentIndex = imagesIndex.indexOf(src)
|
||||||
}
|
}
|
||||||
updateGallery(currentIndex)
|
updateGallery(currentIndex)
|
||||||
if (idx != 'inline') {
|
|
||||||
showGallery()
|
showGallery()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
window.addEventListener("load", loadGalleries)
|
window.addEventListener("load", loadGalleries)
|
||||||
|
|
||||||
|
|
||||||
|
|
1611
camp/static/js/jquery.bxslider.js
Normal file
1611
camp/static/js/jquery.bxslider.js
Normal file
File diff suppressed because it is too large
Load diff
50
camp/static/js/slideshow.js
Normal file
50
camp/static/js/slideshow.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
var startSlide = 0;
|
||||||
|
if (document.querySelectorAll('.select-image').length && document.location.hash.length) {
|
||||||
|
startSlide = parseInt(document.location.hash.slice(1)) - 1
|
||||||
|
}
|
||||||
|
function loadSlideshow() {
|
||||||
|
$('a.original-link').bind('touchstart MSPointerDown pointerdown', function(event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
});
|
||||||
|
$('a.original-link').on({
|
||||||
|
mousedown: function(event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
slider = $('.slider1').show().bxSlider({
|
||||||
|
startSlide: startSlide,
|
||||||
|
preloadImages: 'all',
|
||||||
|
adaptiveHeight : true,
|
||||||
|
pager : false,
|
||||||
|
});
|
||||||
|
|
||||||
|
var images = $('.slider1 img'), count = images.length;
|
||||||
|
images.on({load: function(event) {
|
||||||
|
setTimeout(function() {
|
||||||
|
slider.resize()
|
||||||
|
slider.redrawSlider()
|
||||||
|
}, 100)
|
||||||
|
}})
|
||||||
|
|
||||||
|
document.querySelectorAll('.select-image').forEach(function(a) {
|
||||||
|
a.onclick = function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
slider.goToSlide(parseInt(this.dataset.id) - 1)
|
||||||
|
document.location.hash = '#' + this.dataset.id;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).keydown(function(e){
|
||||||
|
if (e.keyCode == 39) // Right arrow
|
||||||
|
{
|
||||||
|
slider && slider.goToNextSlide && slider.goToNextSlide();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (e.keyCode == 37) // left arrow
|
||||||
|
{
|
||||||
|
slider && slider.goToPrevSlide && slider.goToPrevSlide();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$(document).ready(loadSlideshow);
|
|
@ -8,24 +8,16 @@
|
||||||
</p>
|
</p>
|
||||||
<ul class="sortedm2m-items">
|
<ul class="sortedm2m-items">
|
||||||
{% for row in selected %}
|
{% for row in selected %}
|
||||||
<li class="sortedm2m-item"><label{{ row.label_for|safe }}>{{ row.rendered_cb }} {{ row | admin_thumbnail }} <div>{{ row.option_label }}</div></label></li>
|
<li class="sortedm2m-item"><label{{ row.label_for|safe }}>{{ row.rendered_cb }} {{ row | admin_thumbnail }} {{ row.option_label }}</label></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% for row in unselected %}
|
{% for row in unselected %}
|
||||||
<li class="sortedm2m-item"><label{{ row.label_for|safe }}>{{ row.rendered_cb }} {{ row | admin_thumbnail }}<div>{{ row.option_label }}</div></label></li>
|
<li class="sortedm2m-item"><label{{ row.label_for|safe }}>{{ row.rendered_cb }} {{ row | admin_thumbnail }}{{ row.option_label }}</label></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p class="help">
|
<p class="help">
|
||||||
{% trans "Choose items and order by drag & drop." %}
|
{% trans "Choose items and order by drag & drop." %}
|
||||||
</p>
|
</p>
|
||||||
<style>
|
|
||||||
.sortedm2m-container ul label {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
.sortedm2m-item > label > div {
|
|
||||||
text-wrap-mode: wrap;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
16
content/context_processors.py
Normal file
16
content/context_processors.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from .models import Acronym
|
||||||
|
import random
|
||||||
|
|
||||||
|
def random_title(request):
|
||||||
|
count = Acronym.objects.count()
|
||||||
|
random_c = random.randint(1, count)
|
||||||
|
random_a = random.randint(1, count)
|
||||||
|
random_m = random.randint(1, count)
|
||||||
|
random_p = random.randint(1, count)
|
||||||
|
c = Acronym.objects.all()[random_c].c
|
||||||
|
a = Acronym.objects.all()[random_a].a
|
||||||
|
m = Acronym.objects.all()[random_m].m
|
||||||
|
p = Acronym.objects.all()[random_p].p
|
||||||
|
return {
|
||||||
|
'RANDOM_TITLE': f"{c} {a} {m} {p}"
|
||||||
|
}
|
|
@ -200,8 +200,7 @@ class Content(models.Model):
|
||||||
if self.photo and self.photo.image.url.endswith('.gif'):
|
if self.photo and self.photo.image.url.endswith('.gif'):
|
||||||
video_path = self.photo.image.path.replace('.gif', '.mp4')
|
video_path = self.photo.image.path.replace('.gif', '.mp4')
|
||||||
image_path = self.photo.image.path.replace('.gif', '.jpg')
|
image_path = self.photo.image.path.replace('.gif', '.jpg')
|
||||||
if os.path.exists(self.photo.image.path):
|
if os.path.exists(self.photo.image.path) and not os.path.exists(video_path):
|
||||||
if not os.path.exists(video_path):
|
|
||||||
height = self.photo.image.height
|
height = self.photo.image.height
|
||||||
if height % 2:
|
if height % 2:
|
||||||
height += 1
|
height += 1
|
||||||
|
|
|
@ -6,10 +6,11 @@
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<title>CAMP</title>
|
<title>CAMP: {{ RANDOM_TITLE }}</title>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans"/>
|
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans"/>
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "css/foundation.css" %}?20250201">
|
<link rel="stylesheet" type="text/css" href="{% static "css/foundation.css" %}?20250201">
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "css/jquery.bxslider.css" %}">
|
||||||
{% compress css file site %}
|
{% compress css file site %}
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "css/main.css" %}">
|
<link rel="stylesheet" type="text/css" href="{% static "css/main.css" %}">
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "css/app.css" %}">
|
<link rel="stylesheet" type="text/css" href="{% static "css/app.css" %}">
|
||||||
|
@ -46,12 +47,16 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{}
|
||||||
<script src="{% static "js/jquery.js" %}"></script>
|
<script src="{% static "js/jquery.js" %}"></script>
|
||||||
<script src="{% static "js/foundation.js" %}"></script>
|
<script src="{% static "js/foundation.js" %}"></script>
|
||||||
{% compress js file base %}
|
{% compress js file base %}
|
||||||
<script src="{% static "js/app.js" %}"></script>
|
<script src="{% static "js/app.js" %}"></script>
|
||||||
<script src="{% static "js/what-input.js" %}"></script>
|
<script src="{% static "js/what-input.js" %}"></script>
|
||||||
|
<script src="{% static "js/jquery.bxslider.js" %}?2"></script>
|
||||||
<script src="{% static "js/gallery.js" %}"></script>
|
<script src="{% static "js/gallery.js" %}"></script>
|
||||||
|
<script src="{% static "js/slideshow.js" %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% block end %}
|
{% block end %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,12 +2,7 @@
|
||||||
<h6><strong>{{gallery.title}}</strong></h6>
|
<h6><strong>{{gallery.title}}</strong></h6>
|
||||||
<ul class="clearing-thumbs gallery" data-clearing>
|
<ul class="clearing-thumbs gallery" data-clearing>
|
||||||
{% for photo in gallery.public %}
|
{% for photo in gallery.public %}
|
||||||
<li><a href="{{ gallery.get_absolute_url }}#{{forloop.counter}}"><img src="{{ photo.get_thumbnail_url }}"
|
<li><a href="{{ gallery.get_absolute_url }}#{{forloop.counter}}"><img src="{{ photo.get_thumbnail_url }}" class="photo" data-caption="{{ photo.caption_html }}"{% if request.user.is_staff %} data-orig="{{ photo.image.url }}"{% endif %}></a></li>
|
||||||
class="photo"
|
|
||||||
data-caption="{{ photo.caption_html }}"
|
|
||||||
{% if request.user.is_staff %}data-orig="{{ photo.image.url }}"{% endif %}
|
|
||||||
{% if request.user.is_staff %}data-edit="{{ photo.edit_url }}"{% endif %}
|
|
||||||
></a></li>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -10,34 +10,33 @@
|
||||||
<h6 class="page-header"><a href="{{ gallery.content.all.0.get_absolute_url }}">Back to {{ gallery.content.all.0.title }}</a></h6>
|
<h6 class="page-header"><a href="{{ gallery.content.all.0.get_absolute_url }}">Back to {{ gallery.content.all.0.title }}</a></h6>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h5>{{ gallery.title }}</h5>
|
<h5>{{ gallery.title }}</h5>
|
||||||
{% with gallery.public.0 as photo %}
|
<ul class="slider1">
|
||||||
<div class="gallery-main-stage">
|
{% for photo in gallery.public %}
|
||||||
<div class="photo-container">
|
<li id="slide-{{photo.slug}}">
|
||||||
<div class="image">
|
<img src="{{ photo.get_display_url }}" alt="{{ photo.title }}" loading="lazy">
|
||||||
<img src="{{ photo.get_display_url }}" alt="{{ photo.title }}" class="main-image" alt="{{ photo.title }}">
|
<p>
|
||||||
</div>
|
{{ photo.caption|safe }}
|
||||||
<button class="prev nav-btn">❮ Prev</button>
|
{% if photo.caption %}<br>{%endif%}
|
||||||
<button class="next nav-btn">Next ❯</button>
|
{% if request.user.is_staff %}<a href="{{ photo.image.url }}" class="original-link" target="_blank" >Link to original file</a>{% endif %}
|
||||||
<a class="download disabled" target="_blank" download title="download original">
|
</p>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 304 384"><path fill="currentColor" d="M299 128L149 277L0 128h85V0h128v128h86zM0 320h299v43H0v-43z"/></svg>
|
|
||||||
</a>
|
</li>
|
||||||
</div>
|
{% endfor %}
|
||||||
<div class="caption">{{ photo.caption_html }}</div>
|
</ul>
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="medium-3 columns">
|
<div class="medium-3 columns">
|
||||||
<br>
|
<br>
|
||||||
<p>{% trans "Other photos" %}:</p>
|
<p>{% trans "Other photos" %}:</p>
|
||||||
<div class="inline-gallery">
|
<div>
|
||||||
{% for photo in gallery.public %}
|
{% for photo in gallery.public %}
|
||||||
<div style="float: left; padding: 4px">
|
<div style="float: left; padding: 4px">
|
||||||
<a href="{{ gallery.get_absolute_url }}#{{forloop.counter}}">
|
<a href="{{ photo.get_absolute_url }}" class="select-image" data-id="{{forloop.counter}}">
|
||||||
<img src="{{ photo.get_thumbnail_url }}" class="thumbnail photo" alt="{{ photo.title }}" data-caption="{{ photo.caption_html }}"{% if request.user.is_staff %} data-orig="{{ photo.image.url }}"{% endif %}>
|
<img src="{{ photo.get_thumbnail_url }}" class="thumbnail" alt="{{ photo.title }}">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<div class="medium-9 columns">
|
<div class="medium-9 columns">
|
||||||
<img src="{{ object.get_display_url }}" alt="{{ object.title }}">
|
<img src="{{ object.get_display_url }}" alt="{{ object.title }}">
|
||||||
<p>
|
<p>
|
||||||
{{ object.caption_html|safe }}
|
{{ object.caption|safe }}
|
||||||
{% if object.caption %}<br>{%endif%}
|
{% if object.caption %}<br>{%endif%}
|
||||||
{% if request.user.is_staff %}<a href="{{ photo.image.url }}" class="original-link" target="_blank" >Link to original file</a>{% endif %}
|
{% if request.user.is_staff %}<a href="{{ photo.image.url }}" class="original-link" target="_blank" >Link to original file</a>{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -281,9 +281,7 @@ class ImageModel(models.Model):
|
||||||
if func is None:
|
if func is None:
|
||||||
return _('An "admin_thumbnail" photo size has not been defined.')
|
return _('An "admin_thumbnail" photo size has not been defined.')
|
||||||
else:
|
else:
|
||||||
if not self.id:
|
if hasattr(self, 'get_absolute_url'):
|
||||||
return ""
|
|
||||||
elif hasattr(self, 'get_absolute_url'):
|
|
||||||
return mark_safe(f'<a href="{self.get_absolute_url()}"><img src="{func()}"></a>')
|
return mark_safe(f'<a href="{self.get_absolute_url()}"><img src="{func()}"></a>')
|
||||||
else:
|
else:
|
||||||
return mark_safe(f'<a href="{self.image.url}"><img src="{func()}"></a>')
|
return mark_safe(f'<a href="{self.image.url}"><img src="{func()}"></a>')
|
||||||
|
@ -541,7 +539,7 @@ class Photo(ImageModel):
|
||||||
verbose_name_plural = _("photos")
|
verbose_name_plural = _("photos")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s\n%s" % (self.title, self.caption)
|
return self.title
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
# If crop_from or effect property has been changed on existing image,
|
# If crop_from or effect property has been changed on existing image,
|
||||||
|
@ -596,12 +594,9 @@ class Photo(ImageModel):
|
||||||
def caption_html(self):
|
def caption_html(self):
|
||||||
caption = self.caption
|
caption = self.caption
|
||||||
if caption:
|
if caption:
|
||||||
return markdownify(caption)
|
return mark_safe(markdownify(caption))
|
||||||
return caption
|
return caption
|
||||||
|
|
||||||
def edit_url(self):
|
|
||||||
return '/admin/photologue/photo/%s/change/' % self.id
|
|
||||||
|
|
||||||
class BaseEffect(models.Model):
|
class BaseEffect(models.Model):
|
||||||
name = models.CharField(_('name'),
|
name = models.CharField(_('name'),
|
||||||
max_length=30,
|
max_length=30,
|
||||||
|
|
Loading…
Add table
Reference in a new issue