v0.2.11: 커스텀 공유 모달과 레이아웃 수정 반영
Made-with: Cursor
This commit is contained in:
@@ -660,11 +660,7 @@ body.left-sidebar-collapsed .sidebar--left .sidebar__inner {
|
||||
}
|
||||
|
||||
.topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
height: var(--topbar-height);
|
||||
width: 100%;
|
||||
background: color-mix(in srgb, var(--bg) 94%, transparent);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
@@ -686,7 +682,6 @@ body.left-sidebar-collapsed .sidebar--left .sidebar__inner {
|
||||
}
|
||||
|
||||
.topbar__brand {
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -810,7 +805,6 @@ body:not(.left-sidebar-collapsed) .topbar__sidebar-toggle:hover .topbar__sidebar
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -1242,6 +1236,7 @@ body:not(.left-sidebar-collapsed) .topbar__sidebar-toggle:hover .topbar__sidebar
|
||||
color: var(--text-soft);
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
line-clamp: 2;
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 2;
|
||||
@@ -1912,6 +1907,7 @@ body:not(.left-sidebar-collapsed) .topbar__sidebar-toggle:hover .topbar__sidebar
|
||||
}
|
||||
|
||||
.search-result__excerpt {
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
@@ -1926,13 +1922,170 @@ body:not(.left-sidebar-collapsed) .topbar__sidebar-toggle:hover .topbar__sidebar
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.topbar {
|
||||
padding: 0 14px;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
body.share-modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.share-modal[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.share-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 90;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.share-modal__backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(10, 10, 10, 0.42);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.share-modal__dialog {
|
||||
position: relative;
|
||||
width: min(calc(100% - 2rem), 480px);
|
||||
margin: 56px auto 0;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--shadow);
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
transform: translateY(24px) scale(0.95);
|
||||
opacity: 0;
|
||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.share-modal.is-open .share-modal__dialog {
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.share-modal__close {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
opacity: 0.35;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s ease;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.share-modal__close:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.share-modal__eyebrow {
|
||||
display: block;
|
||||
align-self: flex-start;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--text-soft);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.share-modal__preview {
|
||||
width: 100%;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.share-modal__image-wrap {
|
||||
width: 100%;
|
||||
aspect-ratio: 2 / 1;
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.share-modal__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.share-modal__meta {
|
||||
padding: 16px 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.share-modal__title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.share-modal__description {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
color: var(--text-soft);
|
||||
line-clamp: 2;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.share-modal__actions {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.share-modal__action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 40px;
|
||||
padding: 9px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
background: var(--surface-muted);
|
||||
color: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.share-modal__action:hover {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.share-modal__action--x {
|
||||
min-width: 44px;
|
||||
padding-inline: 10px;
|
||||
}
|
||||
|
||||
.share-modal__action--copy {
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.share-modal__action--copy.is-success {
|
||||
background: #111111;
|
||||
border-color: #111111;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.topbar__inner {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
@@ -2067,11 +2220,6 @@ body:not(.left-sidebar-collapsed) .topbar__sidebar-toggle:hover .topbar__sidebar
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.topbar {
|
||||
padding-inline: 16px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.button--accent {
|
||||
padding-inline: 12px;
|
||||
min-height: 38px;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -699,6 +699,217 @@
|
||||
});
|
||||
}
|
||||
|
||||
function copyTextToClipboard(text) {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
return navigator.clipboard.writeText(text);
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
textArea.setAttribute("readonly", "readonly");
|
||||
textArea.style.position = "fixed";
|
||||
textArea.style.opacity = "0";
|
||||
textArea.style.pointerEvents = "none";
|
||||
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
var copied = document.execCommand("copy");
|
||||
document.body.removeChild(textArea);
|
||||
|
||||
if (!copied) {
|
||||
reject(new Error("Copy command was rejected"));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
document.body.removeChild(textArea);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setShareButtonFeedback(button, label) {
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
var originalLabel = button.dataset.shareOriginalLabel || button.getAttribute("aria-label") || "Share this post";
|
||||
button.dataset.shareOriginalLabel = originalLabel;
|
||||
button.setAttribute("aria-label", label);
|
||||
|
||||
if (button.dataset.shareFeedbackTimer) {
|
||||
window.clearTimeout(Number(button.dataset.shareFeedbackTimer));
|
||||
}
|
||||
|
||||
button.dataset.shareFeedbackTimer = String(window.setTimeout(function () {
|
||||
button.setAttribute("aria-label", originalLabel);
|
||||
delete button.dataset.shareFeedbackTimer;
|
||||
}, 1600));
|
||||
}
|
||||
|
||||
var shareModal = document.querySelector("[data-share-modal]");
|
||||
var shareModalDialog = shareModal ? shareModal.querySelector(".share-modal__dialog") : null;
|
||||
var shareModalTitle = shareModal ? shareModal.querySelector("[data-share-modal-title]") : null;
|
||||
var shareModalDescription = shareModal ? shareModal.querySelector("[data-share-modal-description]") : null;
|
||||
var shareModalImage = shareModal ? shareModal.querySelector("[data-share-modal-image]") : null;
|
||||
var shareModalCopyButton = shareModal ? shareModal.querySelector("[data-share-copy-button]") : null;
|
||||
var shareModalCopyLabel = shareModal ? shareModal.querySelector("[data-share-copy-label]") : null;
|
||||
var activeShareMetadata = null;
|
||||
|
||||
function buildShareHrefMap(metadata) {
|
||||
var encodedUrl = encodeURIComponent(metadata.url || "");
|
||||
var encodedTitle = encodeURIComponent(metadata.title || "");
|
||||
|
||||
return {
|
||||
x: "https://twitter.com/intent/tweet?text=" + encodedTitle + "&url=" + encodedUrl
|
||||
};
|
||||
}
|
||||
|
||||
function renderShareModal(metadata) {
|
||||
if (!shareModal) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeShareMetadata = metadata;
|
||||
var hrefMap = buildShareHrefMap(metadata);
|
||||
|
||||
if (shareModalTitle) {
|
||||
shareModalTitle.textContent = metadata.title || document.title || "Post";
|
||||
}
|
||||
|
||||
if (shareModalDescription) {
|
||||
shareModalDescription.textContent = metadata.description || "이 글을 공유해보세요.";
|
||||
}
|
||||
|
||||
if (shareModalImage) {
|
||||
if (metadata.image) {
|
||||
shareModalImage.src = metadata.image;
|
||||
shareModalImage.alt = metadata.title || "Post image";
|
||||
shareModalImage.classList.remove("hidden");
|
||||
} else {
|
||||
shareModalImage.removeAttribute("src");
|
||||
shareModalImage.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(hrefMap).forEach(function (key) {
|
||||
var link = shareModal.querySelector("[data-share-link='" + key + "']");
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
|
||||
link.href = hrefMap[key];
|
||||
});
|
||||
|
||||
if (shareModalCopyLabel) {
|
||||
shareModalCopyLabel.textContent = "주소 복사";
|
||||
}
|
||||
}
|
||||
|
||||
function openShareModal(metadata) {
|
||||
if (!shareModal || !metadata || !metadata.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderShareModal(metadata);
|
||||
shareModal.hidden = false;
|
||||
body.classList.add("share-modal-open");
|
||||
|
||||
window.requestAnimationFrame(function () {
|
||||
shareModal.classList.add("is-open");
|
||||
});
|
||||
}
|
||||
|
||||
function closeShareModal() {
|
||||
if (!shareModal || shareModal.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
shareModal.classList.remove("is-open");
|
||||
body.classList.remove("share-modal-open");
|
||||
|
||||
window.setTimeout(function () {
|
||||
if (!shareModal.classList.contains("is-open")) {
|
||||
shareModal.hidden = true;
|
||||
}
|
||||
}, 180);
|
||||
}
|
||||
|
||||
function getShareMetadata(button) {
|
||||
return {
|
||||
url: (button.dataset.shareUrl || window.location.href || "").trim(),
|
||||
title: (button.dataset.shareTitle || document.title || "").trim(),
|
||||
description: (button.dataset.shareDescription || "").trim(),
|
||||
image: (button.dataset.shareImage || "").trim()
|
||||
};
|
||||
}
|
||||
|
||||
function initializeShareButtons() {
|
||||
document.querySelectorAll("[data-post-share-toggle]").forEach(function (button) {
|
||||
if (button.dataset.shareBound === "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
button.dataset.shareBound = "true";
|
||||
|
||||
button.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
openShareModal(getShareMetadata(button));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (shareModal) {
|
||||
shareModal.querySelectorAll("[data-share-modal-close]").forEach(function (button) {
|
||||
button.addEventListener("click", closeShareModal);
|
||||
});
|
||||
|
||||
shareModal.addEventListener("click", function (event) {
|
||||
if (shareModalDialog && !shareModalDialog.contains(event.target)) {
|
||||
closeShareModal();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key === "Escape" && !shareModal.hidden) {
|
||||
closeShareModal();
|
||||
}
|
||||
});
|
||||
|
||||
if (shareModalCopyButton) {
|
||||
shareModalCopyButton.addEventListener("click", function () {
|
||||
if (!activeShareMetadata || !activeShareMetadata.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
copyTextToClipboard(activeShareMetadata.url)
|
||||
.then(function () {
|
||||
setShareButtonFeedback(shareModalCopyButton, "복사 완료");
|
||||
shareModalCopyButton.classList.add("is-success");
|
||||
window.setTimeout(function () {
|
||||
shareModalCopyButton.classList.remove("is-success");
|
||||
}, 1400);
|
||||
if (shareModalCopyLabel) {
|
||||
shareModalCopyLabel.textContent = "복사 완료";
|
||||
window.setTimeout(function () {
|
||||
if (shareModalCopyLabel) {
|
||||
shareModalCopyLabel.textContent = "주소 복사";
|
||||
}
|
||||
}, 1400);
|
||||
}
|
||||
})
|
||||
.catch(function () {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
initializeShareButtons();
|
||||
|
||||
function updateLoadMoreState(pagination, nextUrl, loading) {
|
||||
var trigger = pagination.querySelector("[data-load-more-trigger]");
|
||||
if (!trigger) {
|
||||
@@ -767,6 +978,7 @@
|
||||
list.appendChild(item.cloneNode(true));
|
||||
});
|
||||
|
||||
initializeShareButtons();
|
||||
updateLoadMoreState(pagination, nextPagination ? nextPagination.dataset.nextUrl : "", false);
|
||||
})
|
||||
.catch(function () {
|
||||
|
||||
Reference in New Issue
Block a user