Add support page, admin features, and auth hardening.
Rename public affiliate route to /support with localized nav labels, and include affiliate/social/wallpaper admin, 2FA login, and related site updates. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
202dc16a52
commit
fc45c35dbe
@@ -1,12 +1,11 @@
|
||||
@using Resources
|
||||
@model IEnumerable<atakanozbancom.Models.classes.MyProjectVM>
|
||||
@model IEnumerable<atakanozbancom.Models.classes.MyProjectsVM>
|
||||
|
||||
<p class="text-center">
|
||||
@(Resource.MyProjectsP1)
|
||||
<a href="https://link.atakanozban.com/github" style="text-decoration:none;color:darkgrey;" target="_blank">
|
||||
@Resource.GitHub
|
||||
@(atakanozbancom.App_GlobalResources.Resource.MyProjectsP1)
|
||||
<a href="https://git.atakanozban.com/gitea" style="text-decoration:none;color:darkgrey;" target="_blank">
|
||||
@atakanozbancom.App_GlobalResources.Resource.Gitea
|
||||
</a>
|
||||
@Resource.MyProjectsP1_1
|
||||
@atakanozbancom.App_GlobalResources.Resource.MyProjectsP1_1
|
||||
</p>
|
||||
|
||||
<style>
|
||||
@@ -38,6 +37,7 @@
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.media-arrow {
|
||||
@@ -58,22 +58,100 @@
|
||||
.carousel-control-next {
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.carousel-indicators {
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
#projectLightboxModal .modal-dialog {
|
||||
width: auto;
|
||||
max-width: calc(100vw - 2rem);
|
||||
margin: 0.5rem auto;
|
||||
}
|
||||
|
||||
#projectLightboxModal .modal-content {
|
||||
background: transparent;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#projectLightboxModal .modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#projectLightboxModal .lightbox-stage {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
max-width: calc(100vw - 2rem);
|
||||
line-height: 0;
|
||||
background: #000;
|
||||
border-radius: 0.35rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#projectLightboxModal .lightbox-stage img {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: calc(100vw - 2rem);
|
||||
max-height: 85vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
#projectLightboxModal .lightbox-arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
bottom: auto;
|
||||
transform: translateY(-50%);
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
opacity: 0.9;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#projectLightboxModal .lightbox-arrow.carousel-control-prev {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#projectLightboxModal .lightbox-arrow.carousel-control-next {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#projectLightboxModal .lightbox-indicators {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0.75rem;
|
||||
margin: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#projectLightboxModal .lightbox-indicators button {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, .5);
|
||||
border: 0;
|
||||
margin: 0 4px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#projectLightboxModal .lightbox-indicators button.active {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#projectLightboxModal .lightbox-close {
|
||||
position: absolute;
|
||||
top: 0.6rem;
|
||||
right: 0.6rem;
|
||||
z-index: 3;
|
||||
}
|
||||
</style>
|
||||
|
||||
@foreach (var p in Model)
|
||||
{
|
||||
var media = new List<(string Type, string Url)>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(p.iframe))
|
||||
{
|
||||
media.Add(("iframe", p.iframe));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(p.image))
|
||||
{
|
||||
media.Add(("image", p.image));
|
||||
}
|
||||
|
||||
var media = p.medias ?? new List<atakanozbancom.Models.classes.ProjectMediaVM>();
|
||||
var carouselId = $"projCarousel_{p.id}";
|
||||
var hasMultiple = media.Count > 1;
|
||||
|
||||
@@ -82,6 +160,22 @@
|
||||
@if (media.Count > 0)
|
||||
{
|
||||
<div id="@carouselId" class="carousel slide" data-bs-ride="carousel">
|
||||
|
||||
@if (hasMultiple)
|
||||
{
|
||||
<div class="carousel-indicators">
|
||||
@for (int i = 0; i < media.Count; i++)
|
||||
{
|
||||
<button type="button"
|
||||
data-bs-target="#@carouselId"
|
||||
data-bs-slide-to="@i"
|
||||
class="@(i == 0 ? "active" : "")"
|
||||
aria-current="@(i == 0 ? "true" : null)"
|
||||
aria-label="Slide @(i + 1)"></button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="carousel-inner">
|
||||
|
||||
@for (int i = 0; i < media.Count; i++)
|
||||
@@ -89,16 +183,17 @@
|
||||
var m = media[i];
|
||||
|
||||
<div class="carousel-item @(i == 0 ? "active" : "")"
|
||||
data-bs-interval="@(m.Type == "iframe" ? "false" : "5000")">
|
||||
data-bs-interval="false">
|
||||
|
||||
@if (m.Type == "iframe")
|
||||
@if (m.type == "iframe")
|
||||
{
|
||||
<div class="carousel-media-iframe">
|
||||
<iframe src="@m.Url"
|
||||
<iframe src="@m.url"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer-when-downgrade"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen></iframe>
|
||||
allowfullscreen
|
||||
referrerpolicy="no-referrer"
|
||||
sandbox="allow-scripts allow-same-origin allow-popups allow-forms"></iframe>
|
||||
|
||||
|
||||
@if (hasMultiple)
|
||||
{
|
||||
@@ -120,10 +215,14 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
else if (m.type == "image")
|
||||
{
|
||||
<div class="carousel-media-image">
|
||||
<img src="@m.Url" alt="@p.title" />
|
||||
<img src="@m.url"
|
||||
alt="@p.title"
|
||||
class="project-lightbox-trigger"
|
||||
data-fullsrc="@m.url"
|
||||
data-gallery="@carouselId" />
|
||||
|
||||
@if (hasMultiple)
|
||||
{
|
||||
@@ -131,20 +230,33 @@
|
||||
type="button"
|
||||
data-bs-target="#@carouselId"
|
||||
data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
<span class="carousel-control-prev-icon"></span>
|
||||
</button>
|
||||
|
||||
<button class="carousel-control-next media-arrow"
|
||||
type="button"
|
||||
data-bs-target="#@carouselId"
|
||||
data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
<span class="carousel-control-next-icon"></span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@*else if (m.type == "video")
|
||||
{
|
||||
<video class="d-block w-100" controls preload="metadata">
|
||||
<source src="@m.url" type="video/mp4" />
|
||||
</video>
|
||||
}*@
|
||||
@*else if (m.type == "document")
|
||||
{
|
||||
<div class="ratio ratio-4x3">
|
||||
<iframe src="@m.url"></iframe>
|
||||
</div>
|
||||
<div class="text-center mt-2">
|
||||
<a href="@m.url" target="_blank">Open document</a>
|
||||
</div>
|
||||
}*@
|
||||
|
||||
</div>
|
||||
}
|
||||
@@ -154,7 +266,187 @@
|
||||
}
|
||||
|
||||
<h3 class="mt-3 text-center">@p.title</h3>
|
||||
<p class="lead mb-5 text-center">@Html.Raw(p.description)</p>
|
||||
<p class="lead mb-5 text-center">@Html.Raw(atakanozbancom.Models.classes.DescriptionFormatter.ToSafeHtml(p.description))</p>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="modal fade" id="projectLightboxModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-none">
|
||||
<div class="modal-body">
|
||||
<div class="lightbox-stage">
|
||||
<button type="button" class="btn-close btn-close-white lightbox-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<img id="projectLightboxImg" src="" alt="" />
|
||||
<button type="button" id="lightboxPrev" class="carousel-control-prev lightbox-arrow" aria-label="Previous">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" id="lightboxNext" class="carousel-control-next lightbox-arrow" aria-label="Next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div id="lightboxIndicators" class="carousel-indicators lightbox-indicators d-none"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var items = [];
|
||||
var index = 0;
|
||||
var modalEl = null;
|
||||
var imgEl = null;
|
||||
var stageEl = null;
|
||||
var dialogEl = null;
|
||||
var indicatorsEl = null;
|
||||
var prevBtn = null;
|
||||
var nextBtn = null;
|
||||
|
||||
function getEls() {
|
||||
modalEl = document.getElementById('projectLightboxModal');
|
||||
imgEl = document.getElementById('projectLightboxImg');
|
||||
indicatorsEl = document.getElementById('lightboxIndicators');
|
||||
prevBtn = document.getElementById('lightboxPrev');
|
||||
nextBtn = document.getElementById('lightboxNext');
|
||||
stageEl = modalEl ? modalEl.querySelector('.lightbox-stage') : null;
|
||||
dialogEl = modalEl ? modalEl.querySelector('.modal-dialog') : null;
|
||||
return modalEl && imgEl && indicatorsEl && prevBtn && nextBtn && stageEl && dialogEl;
|
||||
}
|
||||
|
||||
function fitToImage() {
|
||||
var maxW = Math.max(window.innerWidth - 32, 200);
|
||||
var maxH = Math.max(window.innerHeight * 0.85, 200);
|
||||
var nw = imgEl.naturalWidth || 0;
|
||||
var nh = imgEl.naturalHeight || 0;
|
||||
if (!nw || !nh) return;
|
||||
|
||||
var scale = Math.min(maxW / nw, maxH / nh, 1);
|
||||
var w = Math.round(nw * scale);
|
||||
var h = Math.round(nh * scale);
|
||||
|
||||
imgEl.style.width = w + 'px';
|
||||
imgEl.style.height = h + 'px';
|
||||
stageEl.style.width = w + 'px';
|
||||
stageEl.style.height = h + 'px';
|
||||
dialogEl.style.width = w + 'px';
|
||||
dialogEl.style.maxWidth = w + 'px';
|
||||
}
|
||||
|
||||
function showSlide(i) {
|
||||
if (!items.length) return;
|
||||
index = (i + items.length) % items.length;
|
||||
var item = items[index];
|
||||
|
||||
imgEl.onload = fitToImage;
|
||||
imgEl.src = item.src;
|
||||
imgEl.alt = item.alt;
|
||||
if (imgEl.complete && imgEl.naturalWidth) fitToImage();
|
||||
|
||||
var dots = indicatorsEl.querySelectorAll('button');
|
||||
for (var d = 0; d < dots.length; d++) {
|
||||
dots[d].classList.toggle('active', d === index);
|
||||
if (d === index) dots[d].setAttribute('aria-current', 'true');
|
||||
else dots[d].removeAttribute('aria-current');
|
||||
}
|
||||
}
|
||||
|
||||
function buildIndicators() {
|
||||
indicatorsEl.innerHTML = '';
|
||||
if (items.length < 2) {
|
||||
indicatorsEl.classList.add('d-none');
|
||||
prevBtn.classList.add('d-none');
|
||||
nextBtn.classList.add('d-none');
|
||||
return;
|
||||
}
|
||||
|
||||
indicatorsEl.classList.remove('d-none');
|
||||
prevBtn.classList.remove('d-none');
|
||||
nextBtn.classList.remove('d-none');
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
(function (slideIndex) {
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.setAttribute('aria-label', 'Slide ' + (slideIndex + 1));
|
||||
if (slideIndex === index) {
|
||||
btn.classList.add('active');
|
||||
btn.setAttribute('aria-current', 'true');
|
||||
}
|
||||
btn.addEventListener('click', function () { showSlide(slideIndex); });
|
||||
indicatorsEl.appendChild(btn);
|
||||
})(i);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var trigger = e.target.closest('.project-lightbox-trigger');
|
||||
if (!trigger) return;
|
||||
if (typeof bootstrap === 'undefined') return;
|
||||
if (!getEls()) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
var gallery = trigger.getAttribute('data-gallery');
|
||||
var galleryImgs = gallery
|
||||
? document.querySelectorAll('.project-lightbox-trigger[data-gallery="' + gallery + '"]')
|
||||
: [trigger];
|
||||
|
||||
items = [];
|
||||
index = 0;
|
||||
for (var i = 0; i < galleryImgs.length; i++) {
|
||||
var img = galleryImgs[i];
|
||||
items.push({
|
||||
src: img.getAttribute('data-fullsrc') || img.src,
|
||||
alt: img.alt || ''
|
||||
});
|
||||
if (img === trigger) index = i;
|
||||
}
|
||||
|
||||
buildIndicators();
|
||||
showSlide(index);
|
||||
bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (e.target.closest('#lightboxPrev')) {
|
||||
e.preventDefault();
|
||||
showSlide(index - 1);
|
||||
} else if (e.target.closest('#lightboxNext')) {
|
||||
e.preventDefault();
|
||||
showSlide(index + 1);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (!modalEl || !modalEl.classList.contains('show')) return;
|
||||
if (e.key === 'ArrowLeft') showSlide(index - 1);
|
||||
if (e.key === 'ArrowRight') showSlide(index + 1);
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
if (modalEl && modalEl.classList.contains('show')) fitToImage();
|
||||
});
|
||||
|
||||
document.addEventListener('hidden.bs.modal', function (e) {
|
||||
if (e.target.id !== 'projectLightboxModal') return;
|
||||
if (!imgEl) getEls();
|
||||
if (imgEl) {
|
||||
imgEl.onload = null;
|
||||
imgEl.removeAttribute('src');
|
||||
imgEl.style.width = '';
|
||||
imgEl.style.height = '';
|
||||
}
|
||||
if (stageEl) {
|
||||
stageEl.style.width = '';
|
||||
stageEl.style.height = '';
|
||||
}
|
||||
if (dialogEl) {
|
||||
dialogEl.style.width = '';
|
||||
dialogEl.style.maxWidth = '';
|
||||
}
|
||||
items = [];
|
||||
index = 0;
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user