Compare commits
No commits in common. "3c7cb11f02f2e146de151926c5f3f154165be567" and "1ee9c788d87f36abf23f76a0f29f0f6a4a1ba260" have entirely different histories.
3c7cb11f02
...
1ee9c788d8
@ -4,11 +4,9 @@
|
||||
.gallery-nav:active,
|
||||
#gallery-modal-content,
|
||||
#gallery-close,
|
||||
#gallery-download,
|
||||
#gallery-info,
|
||||
#gallery-play {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
outline: none;
|
||||
#gallery-download {
|
||||
-webkit-tap-highlight-color: transparent; /* Disable blue highlight on Android/iOS/Chrome/Safari */
|
||||
outline: none; /* Disable outline on click/focus */
|
||||
}
|
||||
|
||||
@property --play-progress {
|
||||
@ -17,196 +15,160 @@
|
||||
initial-value: 0deg;
|
||||
}
|
||||
|
||||
/* Gallery Modal Styles */
|
||||
#gallery-modal {
|
||||
display: none;
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed;
|
||||
z-index: 3000;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.82);
|
||||
background-image:
|
||||
radial-gradient(circle at 50% 15%, rgba(42, 42, 42, 0.52), rgba(8, 8, 8, 0.95) 56%, #000 100%);
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: rgba(0,0,0,1);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#gallery-stage {
|
||||
position: absolute;
|
||||
inset: 82px 22px 96px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#gallery-modal-content{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: min(96vw, calc(100vw - 80px));
|
||||
max-height: calc(100vh - 190px);
|
||||
max-width: 95%;
|
||||
max-height: 95vh;
|
||||
object-fit: contain;
|
||||
margin: auto;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 26px 60px rgba(0, 0, 0, 0.58);
|
||||
}
|
||||
|
||||
.gallery-top-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 18px;
|
||||
z-index: 130;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gallery-actions-left,
|
||||
.gallery-actions-right {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.gallery-control {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
min-width: 48px;
|
||||
min-height: 48px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255, 255, 255, 0.27);
|
||||
background: rgba(0, 0, 0, 0.56);
|
||||
color: #fff;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 132;
|
||||
font-size: 1.45rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.gallery-control:hover,
|
||||
.gallery-control:focus-visible {
|
||||
background: rgba(38, 38, 38, 0.88);
|
||||
border-color: rgba(255, 255, 255, 0.65);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#gallery-info.loading {
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
#gallery-info {
|
||||
font-size: 1.7rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#gallery-download,
|
||||
/* Close Button */
|
||||
#gallery-close {
|
||||
font-size: 1.6rem;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
z-index: 120;
|
||||
font-size: 1.1rem;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#gallery-close:hover {
|
||||
background: rgba(40, 40, 40, 0.85);
|
||||
}
|
||||
|
||||
/* Loader for gallery */
|
||||
#loader-container {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 3500;
|
||||
pointer-events: none;
|
||||
z-index: 9999; /* Ensure it's on top */
|
||||
pointer-events: none; /* Allow interacting with close button while loader shows */
|
||||
}
|
||||
|
||||
#gallery-loader {
|
||||
border: 4px solid rgba(255, 255, 255, 0.22);
|
||||
border-top: 4px solid #fff;
|
||||
border: 8px solid #f3f3f3;
|
||||
border-top: 8px solid #525252;
|
||||
border-radius: 50%;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
animation: gallery-spin 0.9s linear infinite;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
animation: spin 3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes gallery-spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
/* Keyframes for spinner animation */
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.gallery-nav {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
min-width: 56px;
|
||||
min-height: 56px;
|
||||
font-size: 3rem;
|
||||
color: white;
|
||||
background: rgba(0,0,0,0.1);
|
||||
border: none;
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
|
||||
.gallery-prev { left: 20px; }
|
||||
.gallery-next { right: 20px; }
|
||||
|
||||
.gallery-nav.nav-hidden { opacity: 0; }
|
||||
|
||||
#gallery-info {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.54);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
z-index: 120;
|
||||
font-size: 2.2rem;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#gallery-download {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 64px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
z-index: 120;
|
||||
font-size: 1.25rem;
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
transition: opacity 0.25s ease, transform 0.25s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.gallery-nav:hover,
|
||||
.gallery-nav:focus-visible {
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
#gallery-download:hover {
|
||||
background: rgba(40, 40, 40, 0.85);
|
||||
}
|
||||
|
||||
.gallery-prev {
|
||||
left: 18px;
|
||||
}
|
||||
|
||||
.gallery-next {
|
||||
right: 18px;
|
||||
}
|
||||
|
||||
.gallery-next.is-close {
|
||||
background: #fff;
|
||||
border-color: #fff;
|
||||
color: #101010;
|
||||
}
|
||||
|
||||
.gallery-nav.nav-hidden {
|
||||
opacity: 0;
|
||||
transform: translateY(-50%) scale(0.97);
|
||||
pointer-events: none;
|
||||
#gallery-info.loading {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
#gallery-exif {
|
||||
position: absolute;
|
||||
top: 68px;
|
||||
left: 18px;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: 20px;
|
||||
background: rgba(0, 0, 0, 0.82);
|
||||
color: #fff;
|
||||
padding: 0.85rem 0.92rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
box-shadow: 0 14px 36px rgba(0, 0, 0, 0.5);
|
||||
width: min(360px, calc(100vw - 36px));
|
||||
max-height: calc(100vh - 228px);
|
||||
padding: 0.75rem 0.85rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
max-width: 320px;
|
||||
max-height: 50vh;
|
||||
overflow: auto;
|
||||
display: none;
|
||||
z-index: 140;
|
||||
z-index: 120;
|
||||
}
|
||||
|
||||
#gallery-exif.open {
|
||||
@ -217,9 +179,9 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
font-size: 0.82rem;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.28;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.exif-row:last-child {
|
||||
@ -228,127 +190,76 @@
|
||||
|
||||
.exif-key {
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
.exif-value {
|
||||
text-align: right;
|
||||
color: rgba(255, 255, 255, 0.98);
|
||||
word-break: break-word;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.exif-placeholder {
|
||||
font-style: italic;
|
||||
color: rgba(255, 255, 255, 0.76);
|
||||
}
|
||||
|
||||
.gallery-meta-bar {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
left: 18px;
|
||||
right: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
z-index: 120;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#gallery-filename,
|
||||
#gallery-position {
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
border-radius: 8px;
|
||||
padding: 0.45rem 0.72rem;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
color: #d0d0d0;
|
||||
}
|
||||
|
||||
#gallery-filename {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
--filename-progress: 0%;
|
||||
}
|
||||
|
||||
#gallery-filename::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0.95) 0%,
|
||||
rgba(255, 255, 255, 0.95) var(--filename-progress),
|
||||
rgba(0, 0, 0, 0.62) var(--filename-progress),
|
||||
rgba(0, 0, 0, 0.62) 100%
|
||||
);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#gallery-filename .filename-text {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#gallery-filename::after {
|
||||
content: attr(data-filename);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0.45rem 0.72rem;
|
||||
box-sizing: border-box;
|
||||
z-index: 2;
|
||||
overflow: hidden;
|
||||
clip-path: inset(0 calc(100% - var(--filename-progress)) 0 0);
|
||||
text-overflow: ellipsis;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
padding: 0.4rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
max-width: 90%;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
color: #0a0a0a;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
z-index: 20;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#gallery-position {
|
||||
min-width: 72px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#gallery-play {
|
||||
position: relative;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
z-index: 120;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
padding: 0 0 0 2px; /* optical compensation for ▶ glyph whitespace */
|
||||
--autoplay-duration: 5s;
|
||||
--play-progress: 0deg;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#gallery-play.playing::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
border-radius: 999px;
|
||||
inset: -8px;
|
||||
border-radius: 50%;
|
||||
background:
|
||||
conic-gradient(
|
||||
rgba(255, 255, 255, 0.96) 0deg,
|
||||
rgba(255, 255, 255, 0.96) var(--play-progress, 0deg),
|
||||
rgba(255, 255, 255, 0.9) 0deg,
|
||||
rgba(255, 255, 255, 0.9) var(--play-progress, 0deg),
|
||||
rgba(255, 255, 255, 0.2) var(--play-progress, 0deg),
|
||||
rgba(255, 255, 255, 0.2) 360deg
|
||||
);
|
||||
mask: radial-gradient(ellipse at center, transparent 67%, #000 74%);
|
||||
mask: radial-gradient(circle at center, transparent 60%, #000 70%);
|
||||
animation: none;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
#gallery-play.playing.playing-anim::after {
|
||||
@ -356,186 +267,6 @@
|
||||
}
|
||||
|
||||
@keyframes gallery-play-fill {
|
||||
from {
|
||||
--play-progress: 0deg;
|
||||
}
|
||||
to {
|
||||
--play-progress: 360deg;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
#gallery-stage {
|
||||
inset: 74px 10px 110px;
|
||||
}
|
||||
|
||||
#gallery-modal-content {
|
||||
max-width: calc(100vw - 20px);
|
||||
max-height: calc(100vh - 210px);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.gallery-top-bar {
|
||||
padding: 10px;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.gallery-actions-left,
|
||||
.gallery-actions-right {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gallery-control {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
min-width: 42px;
|
||||
min-height: 42px;
|
||||
font-size: 1.3rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.gallery-nav {
|
||||
top: auto;
|
||||
bottom: 62px;
|
||||
transform: none;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
min-width: 48px;
|
||||
min-height: 48px;
|
||||
font-size: 1.9rem;
|
||||
}
|
||||
|
||||
.gallery-prev {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.gallery-next {
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.gallery-nav.nav-hidden {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
|
||||
#gallery-exif {
|
||||
top: 58px;
|
||||
left: 10px;
|
||||
width: calc(100vw - 20px);
|
||||
max-height: calc(100vh - 224px);
|
||||
}
|
||||
|
||||
.gallery-meta-bar {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
#gallery-filename,
|
||||
#gallery-position {
|
||||
font-size: 0.76rem;
|
||||
padding: 0.42rem 0.6rem;
|
||||
}
|
||||
|
||||
#gallery-play {
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
#gallery-info {
|
||||
font-size: 1.45rem;
|
||||
}
|
||||
|
||||
#gallery-download,
|
||||
#gallery-close {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 520px) and (orientation: landscape) {
|
||||
#gallery-stage {
|
||||
inset: 44px 10px 60px;
|
||||
}
|
||||
|
||||
#gallery-modal-content {
|
||||
max-width: calc(100vw - 20px);
|
||||
max-height: calc(100vh - 112px);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.gallery-top-bar {
|
||||
padding: 8px 10px;
|
||||
gap: 6px;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.gallery-actions-left,
|
||||
.gallery-actions-right {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.gallery-control {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
min-width: 38px;
|
||||
min-height: 38px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
#gallery-play {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
#gallery-info {
|
||||
font-size: 1.22rem;
|
||||
}
|
||||
|
||||
#gallery-download,
|
||||
#gallery-close {
|
||||
font-size: 1.18rem;
|
||||
}
|
||||
|
||||
.gallery-nav {
|
||||
top: 50%;
|
||||
bottom: auto;
|
||||
transform: translateY(-50%);
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
|
||||
.gallery-prev {
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
.gallery-next {
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.gallery-nav.nav-hidden {
|
||||
opacity: 0;
|
||||
transform: translateY(-50%) scale(0.96);
|
||||
}
|
||||
|
||||
.gallery-meta-bar {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 8px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#gallery-filename,
|
||||
#gallery-position {
|
||||
padding: 0.32rem 0.52rem;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
#gallery-exif {
|
||||
top: 50px;
|
||||
left: 10px;
|
||||
width: min(360px, calc(100vw - 20px));
|
||||
max-height: calc(100vh - 120px);
|
||||
}
|
||||
from { --play-progress: 0deg; }
|
||||
to { --play-progress: 360deg; }
|
||||
}
|
||||
|
||||
@ -3,17 +3,6 @@ let pinchZoomOccurred = false;
|
||||
let initialPinchDistance = null;
|
||||
let initialScale = 1;
|
||||
let currentScale = 1;
|
||||
let currentTranslateX = 0;
|
||||
let currentTranslateY = 0;
|
||||
let isPanning = false;
|
||||
let panStartX = 0;
|
||||
let panStartY = 0;
|
||||
let panOriginX = 0;
|
||||
let panOriginY = 0;
|
||||
let pinchStartMidX = 0;
|
||||
let pinchStartMidY = 0;
|
||||
let pinchStartTranslateX = 0;
|
||||
let pinchStartTranslateY = 0;
|
||||
let exifAbortController = null;
|
||||
let isGalleryLoading = false;
|
||||
let autoPlayTimer = null;
|
||||
@ -190,55 +179,16 @@ function updateFilenameDisplay(relUrl) {
|
||||
if (!filenameEl) {
|
||||
return;
|
||||
}
|
||||
const filenameTextEl = filenameEl.querySelector('.filename-text');
|
||||
|
||||
if (!relUrl) {
|
||||
if (filenameTextEl) {
|
||||
filenameTextEl.textContent = '';
|
||||
} else {
|
||||
filenameEl.textContent = '';
|
||||
}
|
||||
filenameEl.dataset.filename = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = relUrl.split('/');
|
||||
const filename = parts[parts.length - 1] || relUrl;
|
||||
if (filenameTextEl) {
|
||||
filenameTextEl.textContent = filename;
|
||||
} else {
|
||||
filenameEl.textContent = filename;
|
||||
}
|
||||
filenameEl.dataset.filename = filename;
|
||||
}
|
||||
|
||||
function updateFilenameProgress() {
|
||||
const filenameEl = document.getElementById('gallery-filename');
|
||||
if (!filenameEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const total = currentGalleryImages.length;
|
||||
const hasValidIndex = currentGalleryIndex >= 0 && currentGalleryIndex < total;
|
||||
const progress = total > 0 && hasValidIndex
|
||||
? ((currentGalleryIndex + 1) / total) * 100
|
||||
: 0;
|
||||
filenameEl.style.setProperty('--filename-progress', `${progress}%`);
|
||||
}
|
||||
|
||||
function updateGalleryPosition() {
|
||||
const positionEl = document.getElementById('gallery-position');
|
||||
if (!positionEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const total = currentGalleryImages.length;
|
||||
if (total <= 0 || currentGalleryIndex < 0 || currentGalleryIndex >= total) {
|
||||
positionEl.textContent = '';
|
||||
return;
|
||||
}
|
||||
positionEl.textContent = `${currentGalleryIndex + 1} / ${total}`;
|
||||
}
|
||||
|
||||
function updateDownloadLink(relUrl) {
|
||||
const downloadBtn = document.getElementById('gallery-download');
|
||||
@ -246,11 +196,15 @@ function updateDownloadLink(relUrl) {
|
||||
return;
|
||||
}
|
||||
if (!relUrl) {
|
||||
downloadBtn.removeAttribute('href');
|
||||
downloadBtn.removeAttribute('download');
|
||||
downloadBtn.removeAttribute('data-relurl');
|
||||
return;
|
||||
}
|
||||
// Store relUrl for the click handler (which fetches a short-lived token)
|
||||
downloadBtn.dataset.relurl = relUrl;
|
||||
downloadBtn.removeAttribute('href');
|
||||
downloadBtn.removeAttribute('download');
|
||||
}
|
||||
|
||||
function updateNextButtonState() {
|
||||
@ -260,87 +214,23 @@ function updateNextButtonState() {
|
||||
}
|
||||
|
||||
const atLastImage = currentGalleryIndex >= currentGalleryImages.length - 1;
|
||||
nextButton.textContent = atLastImage ? '\u00D7' : '\u203A';
|
||||
nextButton.textContent = atLastImage ? 'x' : '›';
|
||||
nextButton.setAttribute('aria-label', atLastImage ? 'Close gallery' : 'Next image');
|
||||
nextButton.classList.toggle('is-close', atLastImage);
|
||||
}
|
||||
|
||||
function showGalleryNav() {
|
||||
clearTimeout(navHideTimer);
|
||||
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.remove('nav-hidden'));
|
||||
if (currentScale > 1.01) {
|
||||
return;
|
||||
}
|
||||
navHideTimer = setTimeout(() => {
|
||||
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.add('nav-hidden'));
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
function getPanBounds(image, scale) {
|
||||
const stage = document.getElementById('gallery-stage');
|
||||
if (!stage || !image) {
|
||||
return { maxX: 0, maxY: 0 };
|
||||
}
|
||||
const baseWidth = image.clientWidth || 0;
|
||||
const baseHeight = image.clientHeight || 0;
|
||||
const scaledWidth = baseWidth * scale;
|
||||
const scaledHeight = baseHeight * scale;
|
||||
|
||||
return {
|
||||
maxX: Math.max(0, (scaledWidth - stage.clientWidth) / 2),
|
||||
maxY: Math.max(0, (scaledHeight - stage.clientHeight) / 2)
|
||||
};
|
||||
}
|
||||
|
||||
function applyImageTransform(image) {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
const { maxX, maxY } = getPanBounds(image, currentScale);
|
||||
currentTranslateX = clamp(currentTranslateX, -maxX, maxX);
|
||||
currentTranslateY = clamp(currentTranslateY, -maxY, maxY);
|
||||
image.style.transform = `translate(-50%, -50%) translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${currentScale})`;
|
||||
}
|
||||
|
||||
function resetImageTransformState(image = null) {
|
||||
isPinching = false;
|
||||
isPanning = false;
|
||||
pinchZoomOccurred = false;
|
||||
initialPinchDistance = null;
|
||||
initialScale = 1;
|
||||
currentScale = 1;
|
||||
currentTranslateX = 0;
|
||||
currentTranslateY = 0;
|
||||
panStartX = 0;
|
||||
panStartY = 0;
|
||||
panOriginX = 0;
|
||||
panOriginY = 0;
|
||||
pinchStartMidX = 0;
|
||||
pinchStartMidY = 0;
|
||||
pinchStartTranslateX = 0;
|
||||
pinchStartTranslateY = 0;
|
||||
if (image) {
|
||||
applyImageTransform(image);
|
||||
}
|
||||
}
|
||||
|
||||
function openGalleryModal(relUrl) {
|
||||
const modal = document.getElementById('gallery-modal');
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
document.body.style.overflow = 'hidden'; // Disable background scrolling.
|
||||
currentGalleryIndex = currentGalleryImages.indexOf(relUrl);
|
||||
if (currentGalleryIndex < 0) {
|
||||
currentGalleryIndex = 0;
|
||||
}
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
showGalleryImage(relUrl);
|
||||
modal.style.display = 'flex';
|
||||
document.getElementById('gallery-modal').style.display = 'flex';
|
||||
showGalleryNav();
|
||||
}
|
||||
|
||||
@ -351,16 +241,12 @@ function showGalleryImage(relUrl) {
|
||||
isGalleryLoading = true;
|
||||
clearAutoPlayTimer();
|
||||
updateNextButtonState();
|
||||
updateGalleryPosition();
|
||||
updateFilenameProgress();
|
||||
loadExifData(relUrl);
|
||||
const fullUrl = '/media/' + relUrl;
|
||||
const stage = document.getElementById('gallery-stage');
|
||||
const modal = document.getElementById('gallery-modal');
|
||||
const currentImage = document.getElementById('gallery-modal-content');
|
||||
const loader = document.getElementById('loader-container');
|
||||
if (!stage || !loader) {
|
||||
isGalleryLoading = false;
|
||||
return;
|
||||
}
|
||||
const closeBtn = document.getElementById('gallery-close');
|
||||
updateDownloadLink(relUrl);
|
||||
// Set a timeout for showing the loader after 500ms.
|
||||
let loaderTimeout = setTimeout(() => {
|
||||
@ -376,8 +262,8 @@ function showGalleryImage(relUrl) {
|
||||
loader.style.display = 'none';
|
||||
updateFilenameDisplay(relUrl);
|
||||
|
||||
// Select all current images in the gallery stage
|
||||
const existingImages = stage.querySelectorAll('img');
|
||||
// Select all current images in the gallery modal
|
||||
const existingImages = document.querySelectorAll('#gallery-modal img');
|
||||
|
||||
// Create a new image element for the transition
|
||||
const newImage = document.createElement('img');
|
||||
@ -386,23 +272,27 @@ function showGalleryImage(relUrl) {
|
||||
newImage.style.top = '50%'; // Center vertically
|
||||
newImage.style.left = '50%'; // Center horizontally
|
||||
newImage.style.transform = 'translate(-50%, -50%)'; // Ensure centering
|
||||
newImage.style.maxWidth = '95vw'; // Prevent overflow on large screens
|
||||
newImage.style.maxHeight = '95vh'; // Prevent overflow on tall screens
|
||||
newImage.style.transition = 'opacity 0.5s ease-in-out';
|
||||
newImage.style.opacity = 0; // Start fully transparent
|
||||
newImage.id = 'gallery-modal-content';
|
||||
newImage.alt = '';
|
||||
|
||||
// Add pinch-to-zoom event listeners
|
||||
newImage.addEventListener('touchstart', handlePinchStart, { passive: false });
|
||||
newImage.addEventListener('touchmove', handlePinchMove, { passive: false });
|
||||
newImage.addEventListener('touchend', handlePinchEnd, { passive: false });
|
||||
newImage.addEventListener('touchcancel', handlePinchEnd, { passive: false });
|
||||
newImage.addEventListener('click', handleGalleryImageClick);
|
||||
|
||||
|
||||
// Reset pinch variables for the new image
|
||||
resetImageTransformState(newImage);
|
||||
initialPinchDistance = null;
|
||||
initialScale = 1;
|
||||
currentScale = 1;
|
||||
pinchZoomOccurred = false;
|
||||
|
||||
// Append new image on top of existing ones
|
||||
stage.appendChild(newImage);
|
||||
modal.appendChild(newImage);
|
||||
|
||||
// Fade out all old images and fade in the new one
|
||||
setTimeout(() => {
|
||||
@ -457,88 +347,50 @@ function preloadNextImage() {
|
||||
function closeGalleryModal() {
|
||||
clearTimeout(navHideTimer);
|
||||
document.querySelectorAll('.gallery-nav').forEach(btn => btn.classList.remove('nav-hidden'));
|
||||
const modal = document.getElementById('gallery-modal');
|
||||
const stage = document.getElementById('gallery-stage');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
document.getElementById('gallery-modal').style.display = 'none';
|
||||
isGalleryLoading = false;
|
||||
setAutoPlay(false);
|
||||
updateDownloadLink(null);
|
||||
// remove all images
|
||||
if (stage) {
|
||||
const existingImages = stage.querySelectorAll('img');
|
||||
const existingImages = document.querySelectorAll('#gallery-modal img');
|
||||
existingImages.forEach(img => {
|
||||
if (img.parentNode) {
|
||||
img.parentNode.removeChild(img);
|
||||
}
|
||||
});
|
||||
}
|
||||
const filenameEl = document.getElementById('gallery-filename');
|
||||
if (filenameEl) {
|
||||
const filenameTextEl = filenameEl.querySelector('.filename-text');
|
||||
if (filenameTextEl) {
|
||||
filenameTextEl.textContent = '';
|
||||
} else {
|
||||
filenameEl.textContent = '';
|
||||
}
|
||||
filenameEl.dataset.filename = '';
|
||||
filenameEl.style.setProperty('--filename-progress', '0%');
|
||||
}
|
||||
const positionEl = document.getElementById('gallery-position');
|
||||
if (positionEl) {
|
||||
positionEl.textContent = '';
|
||||
}
|
||||
resetExifPanel(true);
|
||||
resetImageTransformState();
|
||||
// Restore scrolling.
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function navigateByScreenHalf(clientX) {
|
||||
if (isGalleryLoading) {
|
||||
function handleGalleryImageClick(e) {
|
||||
// If a pinch zoom occurred, don't change the image.
|
||||
if (pinchZoomOccurred) {
|
||||
pinchZoomOccurred = false; // Reset the flag for the next gesture.
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
const midpoint = window.innerWidth / 2;
|
||||
if (clientX < midpoint) {
|
||||
if (currentGalleryIndex > 0) {
|
||||
if (e.target.classList.contains('gallery-next') || e.target.classList.contains('gallery-prev')) {
|
||||
return; // Prevent conflict with navigation buttons.
|
||||
}
|
||||
|
||||
const imgRect = this.getBoundingClientRect();
|
||||
if (e.clientX < imgRect.left + imgRect.width / 2) {
|
||||
if (!isGalleryLoading && currentGalleryIndex > 0) {
|
||||
currentGalleryIndex--;
|
||||
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentGalleryIndex < currentGalleryImages.length - 1) {
|
||||
} else {
|
||||
if (!isGalleryLoading && currentGalleryIndex < currentGalleryImages.length - 1) {
|
||||
currentGalleryIndex++;
|
||||
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
|
||||
} else {
|
||||
closeGalleryModal();
|
||||
}
|
||||
}
|
||||
|
||||
function handleGalleryScreenClick(e) {
|
||||
// If a pinch zoom occurred, don't change the image.
|
||||
if (pinchZoomOccurred) {
|
||||
pinchZoomOccurred = false;
|
||||
return;
|
||||
}
|
||||
if (currentScale > 1.01 || isPinching || isPanning) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = e.target;
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not interpret clicks on controls/panels as navigation.
|
||||
if (target.closest('.gallery-control') || target.closest('.gallery-nav') || target.closest('#gallery-exif')) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigateByScreenHalf(e.clientX);
|
||||
}
|
||||
|
||||
|
||||
@ -548,24 +400,21 @@ function initGallerySwipe() {
|
||||
let touchStartX = 0;
|
||||
let touchEndX = 0;
|
||||
const galleryModal = document.getElementById('gallery-modal');
|
||||
if (!galleryModal) {
|
||||
return;
|
||||
}
|
||||
|
||||
galleryModal.addEventListener('mousemove', showGalleryNav, false);
|
||||
|
||||
galleryModal.addEventListener('touchstart', function(e) {
|
||||
showGalleryNav();
|
||||
// If more than one finger is on screen, ignore swipe tracking.
|
||||
if (e.touches.length > 1) {
|
||||
return;
|
||||
}
|
||||
if (currentScale > 1.01 || isPinching || isPanning) {
|
||||
return;
|
||||
}
|
||||
touchStartX = e.changedTouches[0].screenX;
|
||||
}, false);
|
||||
|
||||
galleryModal.addEventListener('touchend', function(e) {
|
||||
// If the event was part of a multi-touch (pinch) gesture or a pinch was detected, skip swipe.
|
||||
if (e.changedTouches.length > 1 || isPinching || isPanning || pinchZoomOccurred || currentScale > 1.01) {
|
||||
if (e.changedTouches.length > 1 || isPinching || pinchZoomOccurred) {
|
||||
return;
|
||||
}
|
||||
touchEndX = e.changedTouches[0].screenX;
|
||||
@ -592,7 +441,7 @@ function initGallerySwipe() {
|
||||
|
||||
function clearAutoPlayTimer() {
|
||||
if (autoPlayTimer) {
|
||||
clearTimeout(autoPlayTimer);
|
||||
clearInterval(autoPlayTimer);
|
||||
autoPlayTimer = null;
|
||||
}
|
||||
}
|
||||
@ -601,9 +450,8 @@ function setAutoPlay(active) {
|
||||
const playButton = document.getElementById('gallery-play');
|
||||
isAutoPlay = active;
|
||||
if (playButton) {
|
||||
playButton.textContent = active ? '\u2225' : '\u25B6';
|
||||
playButton.textContent = active ? '\u23F8' : '\u25B6';
|
||||
playButton.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
playButton.setAttribute('aria-label', active ? 'Pause slideshow' : 'Start slideshow');
|
||||
playButton.classList.toggle('playing', active);
|
||||
}
|
||||
pendingAutoAdvance = false;
|
||||
@ -693,28 +541,10 @@ function handlePinchStart(e) {
|
||||
if (e.touches.length === 2) {
|
||||
e.preventDefault();
|
||||
isPinching = true;
|
||||
isPanning = false;
|
||||
pinchZoomOccurred = false; // Reset at the start of a pinch gesture.
|
||||
const dx = e.touches[0].pageX - e.touches[1].pageX;
|
||||
const dy = e.touches[0].pageY - e.touches[1].pageY;
|
||||
initialPinchDistance = Math.sqrt(dx * dx + dy * dy);
|
||||
initialScale = currentScale;
|
||||
pinchStartMidX = (e.touches[0].pageX + e.touches[1].pageX) / 2;
|
||||
pinchStartMidY = (e.touches[0].pageY + e.touches[1].pageY) / 2;
|
||||
pinchStartTranslateX = currentTranslateX;
|
||||
pinchStartTranslateY = currentTranslateY;
|
||||
showGalleryNav();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.touches.length === 1 && currentScale > 1.01) {
|
||||
e.preventDefault();
|
||||
isPanning = true;
|
||||
panStartX = e.touches[0].pageX;
|
||||
panStartY = e.touches[0].pageY;
|
||||
panOriginX = currentTranslateX;
|
||||
panOriginY = currentTranslateY;
|
||||
showGalleryNav();
|
||||
}
|
||||
}
|
||||
|
||||
@ -725,97 +555,21 @@ function handlePinchMove(e) {
|
||||
const dy = e.touches[0].pageY - e.touches[1].pageY;
|
||||
const currentDistance = Math.sqrt(dx * dx + dy * dy);
|
||||
let scale = currentDistance / initialPinchDistance;
|
||||
scale = Math.max(1, Math.min(scale * initialScale, 4));
|
||||
scale = Math.max(0.5, Math.min(scale * initialScale, 3));
|
||||
// If the scale has changed enough, mark that a pinch zoom occurred.
|
||||
if (Math.abs(scale - currentScale) > 0.01) {
|
||||
if (Math.abs(scale - initialScale) > 0.05) {
|
||||
pinchZoomOccurred = true;
|
||||
}
|
||||
currentScale = scale;
|
||||
const midX = (e.touches[0].pageX + e.touches[1].pageX) / 2;
|
||||
const midY = (e.touches[0].pageY + e.touches[1].pageY) / 2;
|
||||
currentTranslateX = pinchStartTranslateX + (midX - pinchStartMidX);
|
||||
currentTranslateY = pinchStartTranslateY + (midY - pinchStartMidY);
|
||||
applyImageTransform(e.currentTarget);
|
||||
showGalleryNav();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.touches.length === 1 && isPanning && currentScale > 1.01) {
|
||||
e.preventDefault();
|
||||
const deltaX = e.touches[0].pageX - panStartX;
|
||||
const deltaY = e.touches[0].pageY - panStartY;
|
||||
currentTranslateX = panOriginX + deltaX;
|
||||
currentTranslateY = panOriginY + deltaY;
|
||||
pinchZoomOccurred = true;
|
||||
applyImageTransform(e.currentTarget);
|
||||
showGalleryNav();
|
||||
e.currentTarget.style.transform = `translate(-50%, -50%) scale(${scale})`;
|
||||
}
|
||||
}
|
||||
|
||||
function handlePinchEnd(e) {
|
||||
if (e.touches.length === 1 && currentScale > 1.01) {
|
||||
// Transition from pinch to one-finger pan.
|
||||
isPinching = false;
|
||||
isPanning = true;
|
||||
initialPinchDistance = null;
|
||||
panStartX = e.touches[0].pageX;
|
||||
panStartY = e.touches[0].pageY;
|
||||
panOriginX = currentTranslateX;
|
||||
panOriginY = currentTranslateY;
|
||||
showGalleryNav();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.touches.length < 1) {
|
||||
isPinching = false;
|
||||
isPanning = false;
|
||||
initialPinchDistance = null;
|
||||
if (e.touches.length < 2) {
|
||||
initialScale = currentScale;
|
||||
const image = e.currentTarget;
|
||||
if (currentScale <= 1.01) {
|
||||
currentScale = 1;
|
||||
currentTranslateX = 0;
|
||||
currentTranslateY = 0;
|
||||
}
|
||||
applyImageTransform(image);
|
||||
showGalleryNav();
|
||||
}
|
||||
}
|
||||
|
||||
function handleGalleryKeydown(e) {
|
||||
const modal = document.getElementById('gallery-modal');
|
||||
if (!modal || modal.style.display !== 'flex') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
closeGalleryModal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowLeft') {
|
||||
e.preventDefault();
|
||||
if (!isGalleryLoading && currentGalleryIndex > 0) {
|
||||
currentGalleryIndex--;
|
||||
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowRight') {
|
||||
e.preventDefault();
|
||||
if (!isGalleryLoading && currentGalleryIndex < currentGalleryImages.length - 1) {
|
||||
currentGalleryIndex++;
|
||||
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
|
||||
} else if (!isGalleryLoading) {
|
||||
closeGalleryModal();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === ' ') {
|
||||
e.preventDefault();
|
||||
setAutoPlay(!isAutoPlay);
|
||||
initialPinchDistance = null;
|
||||
isPinching = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -920,8 +674,12 @@ function initGallery() {
|
||||
initGallerySwipe();
|
||||
initGalleryControls();
|
||||
|
||||
galleryModal.addEventListener('click', handleGalleryScreenClick);
|
||||
document.addEventListener('keydown', handleGalleryKeydown);
|
||||
// Close modal when clicking outside the image.
|
||||
galleryModal.addEventListener('click', function(e) {
|
||||
if (e.target === galleryModal) {
|
||||
closeGalleryModal();
|
||||
}
|
||||
});
|
||||
galleryModal.dataset.galleryBound = '1';
|
||||
}
|
||||
|
||||
|
||||
@ -203,30 +203,16 @@
|
||||
</div>
|
||||
|
||||
<!-- Gallery Modal for Images -->
|
||||
<div id="gallery-modal" style="display: none;" role="dialog" aria-modal="true" aria-hidden="true">
|
||||
<div id="gallery-stage">
|
||||
<img id="gallery-modal-content" src="" alt="">
|
||||
</div>
|
||||
|
||||
<div class="gallery-top-bar">
|
||||
<div class="gallery-actions-left">
|
||||
<button id="gallery-info" class="gallery-control" type="button" aria-expanded="false" aria-controls="gallery-exif">i</button>
|
||||
<button id="gallery-download" class="gallery-control" type="button" aria-label="Download original image" title="Download original image">↓</button>
|
||||
</div>
|
||||
<div class="gallery-actions-right">
|
||||
<button id="gallery-play" class="gallery-control" type="button" aria-pressed="false" aria-label="Start slideshow">▶</button>
|
||||
<button id="gallery-close" class="gallery-control" type="button" aria-label="Close gallery">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="gallery-nav gallery-prev" type="button" aria-label="Previous image">‹</button>
|
||||
<button class="gallery-nav gallery-next" type="button" aria-label="Next image">›</button>
|
||||
<div id="gallery-modal" style="display: none;">
|
||||
<button id="gallery-close">✕</button>
|
||||
<button id="gallery-info" aria-expanded="false" aria-controls="gallery-exif">i</button>
|
||||
<a id="gallery-download" aria-label="Download original image" title="Download original">⇩</a>
|
||||
<img id="gallery-modal-content" src="">
|
||||
<button class="gallery-nav gallery-prev">‹</button>
|
||||
<button class="gallery-nav gallery-next">›</button>
|
||||
<button id="gallery-play" aria-pressed="false" aria-label="Start slideshow">▶</button>
|
||||
<div id="gallery-exif" aria-live="polite"></div>
|
||||
|
||||
<div class="gallery-meta-bar">
|
||||
<div id="gallery-filename" aria-live="polite"><span class="filename-text"></span></div>
|
||||
<div id="gallery-position" aria-live="polite"></div>
|
||||
</div>
|
||||
<div id="gallery-filename" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
<div id="loader-container" style="display: none;">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user