Add truth rail and remove duplicate modal content
This commit is contained in:
parent
c4b7f6cb90
commit
3d7a33b06e
|
|
@ -63,6 +63,16 @@ function updateStat(modalWrap, statKey, value) {
|
||||||
if (el) el.textContent = formatCount(value);
|
if (el) el.textContent = formatCount(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateTruth(modalWrap, post) {
|
||||||
|
const rail = modalWrap.querySelector(".sw-truth-rail");
|
||||||
|
if (!rail) return;
|
||||||
|
const score = truthScore(post);
|
||||||
|
const marker = rail.querySelector(".sw-truth-rail-marker");
|
||||||
|
const label = rail.querySelector(".sw-truth-rail-score");
|
||||||
|
if (marker) marker.style.top = `${100 - score}%`;
|
||||||
|
if (label) label.textContent = `${score}`;
|
||||||
|
}
|
||||||
|
|
||||||
function isAuthed() {
|
function isAuthed() {
|
||||||
try {
|
try {
|
||||||
const auth = loadAuth();
|
const auth = loadAuth();
|
||||||
|
|
@ -139,6 +149,7 @@ function applyPostDetails(modalWrap, post) {
|
||||||
updateStat(modalWrap, "likes", likeCount);
|
updateStat(modalWrap, "likes", likeCount);
|
||||||
updateStat(modalWrap, "shares", shareCount);
|
updateStat(modalWrap, "shares", shareCount);
|
||||||
updateStat(modalWrap, "comments", commentCount);
|
updateStat(modalWrap, "comments", commentCount);
|
||||||
|
updateTruth(modalWrap, post);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderComments(container, items) {
|
function renderComments(container, items) {
|
||||||
|
|
@ -162,22 +173,43 @@ function renderComments(container, items) {
|
||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function mountCard(container, spec, data, theme) {
|
function mountCard(container, spec, data, theme, post) {
|
||||||
const root = createRoot(container);
|
const root = createRoot(container);
|
||||||
const tokens = cardTokens?.[theme] || cardTokens.blue;
|
const tokens = cardTokens?.[theme] || cardTokens.blue;
|
||||||
|
const score = truthScore(post || data || {});
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
React.createElement(CardRenderer, {
|
React.createElement(
|
||||||
spec,
|
"div",
|
||||||
data,
|
{
|
||||||
themeTokens: tokens,
|
className: "sw-card-wrap",
|
||||||
onAction: (action, value) => {
|
style: { width: spec?.size?.w, height: spec?.size?.h },
|
||||||
if (action?.type === "open_url" && value) {
|
|
||||||
try { window.open(value, "_blank"); } catch {}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
className: "",
|
React.createElement(CardRenderer, {
|
||||||
})
|
spec,
|
||||||
|
data,
|
||||||
|
themeTokens: tokens,
|
||||||
|
onAction: (action, value) => {
|
||||||
|
if (action?.type === "open_url" && value) {
|
||||||
|
try { window.open(value, "_blank"); } catch {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
className: "",
|
||||||
|
}),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "sw-truth-mini-bar" },
|
||||||
|
React.createElement("div", {
|
||||||
|
className: "sw-truth-mini-marker",
|
||||||
|
style: { top: `${100 - score}%` },
|
||||||
|
})
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "sw-truth-mini-score", style: { background: truthColor(score) } },
|
||||||
|
`${score}`
|
||||||
|
),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|
@ -249,6 +281,29 @@ function formatRelativeTime(createdAt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function truthScore(post) {
|
||||||
|
const raw = Number(post?.truth_score || post?.truthScore);
|
||||||
|
if (Number.isFinite(raw)) {
|
||||||
|
return clamp(Math.round(raw), 0, 100);
|
||||||
|
}
|
||||||
|
const seed = `${post?.id || ""}|${post?.url || ""}|${post?.title || ""}`;
|
||||||
|
let h = 0;
|
||||||
|
for (let i = 0; i < seed.length; i++) {
|
||||||
|
h = (h * 31 + seed.charCodeAt(i)) >>> 0;
|
||||||
|
}
|
||||||
|
return 35 + (h % 61);
|
||||||
|
}
|
||||||
|
|
||||||
|
function truthColor(score) {
|
||||||
|
const s = clamp(score, 0, 100);
|
||||||
|
const hue = Math.round((s / 100) * 120);
|
||||||
|
return `hsl(${hue} 80% 42%)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(v, min, max) {
|
||||||
|
return Math.min(Math.max(v, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeImageUrl(raw) {
|
function normalizeImageUrl(raw) {
|
||||||
const value = (raw || "").toString().trim();
|
const value = (raw || "").toString().trim();
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
|
|
@ -300,6 +355,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
const metaLeft = author ? `by ${author}` : "";
|
const metaLeft = author ? `by ${author}` : "";
|
||||||
const metaRight = sub ? sub : "";
|
const metaRight = sub ? sub : "";
|
||||||
const img = heroImageForPost(postData);
|
const img = heroImageForPost(postData);
|
||||||
|
const truth = truthScore(postData);
|
||||||
const url = data?.url || "";
|
const url = data?.url || "";
|
||||||
const postID = postData?.id || postData?.ID || 0;
|
const postID = postData?.id || postData?.ID || 0;
|
||||||
const viewCount = readCount(postData, ["view_count", "views", "viewCount"]);
|
const viewCount = readCount(postData, ["view_count", "views", "viewCount"]);
|
||||||
|
|
@ -347,12 +403,22 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
<button class="sw-modal-x" type="button" aria-label="Close">✕</button>
|
<button class="sw-modal-x" type="button" aria-label="Close">✕</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sw-modal-hero">
|
<div class="sw-modal-hero-row">
|
||||||
${
|
<div class="sw-truth-rail" data-score="${truth}">
|
||||||
img
|
<div class="sw-truth-rail-top">TRUE</div>
|
||||||
? `<img src="${escapeHtml(img)}" alt="" loading="lazy" />`
|
<div class="sw-truth-rail-bar">
|
||||||
: `<div class="sw-modal-hero-fallback"><div class="sw-modal-hero-fallback-inner"><i class="${categoryIconClass(postData)}"></i><span>${escapeHtml(normCatLabel(postData))}</span></div></div>`
|
<div class="sw-truth-rail-marker" style="top:${100 - truth}%"></div>
|
||||||
}
|
</div>
|
||||||
|
<div class="sw-truth-rail-score">${truth}</div>
|
||||||
|
<div class="sw-truth-rail-bottom">FALSE</div>
|
||||||
|
</div>
|
||||||
|
<div class="sw-modal-hero">
|
||||||
|
${
|
||||||
|
img
|
||||||
|
? `<img src="${escapeHtml(img)}" alt="" loading="lazy" />`
|
||||||
|
: `<div class="sw-modal-hero-fallback"><div class="sw-modal-hero-fallback-inner"><i class="${categoryIconClass(postData)}"></i><span>${escapeHtml(normCatLabel(postData))}</span></div></div>`
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sw-modal-title">${escapeHtml(data?.headline || postData?.title || "Untitled")}</div>
|
<div class="sw-modal-title">${escapeHtml(data?.headline || postData?.title || "Untitled")}</div>
|
||||||
|
|
@ -393,8 +459,6 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- (Optional) keep template renderer mounted but hidden for future -->
|
|
||||||
<div class="sw-template-full-wrap" style="display:none;"></div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -595,13 +659,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
};
|
};
|
||||||
window.addEventListener("sociowire:auth-changed", onAuth);
|
window.addEventListener("sociowire:auth-changed", onAuth);
|
||||||
|
|
||||||
// still mount template full (hidden) so you can switch back later instantly
|
|
||||||
let unmountFull = null;
|
let unmountFull = null;
|
||||||
const fullWrap = modalWrap.querySelector(".sw-template-full-wrap");
|
|
||||||
if (fullWrap) {
|
|
||||||
const spec = getTemplateSpecForPost(post, "full");
|
|
||||||
unmountFull = mountCard(fullWrap, spec, data, theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
map.__swCenteredOverlay = {
|
map.__swCenteredOverlay = {
|
||||||
postId: postData?.id,
|
postId: postData?.id,
|
||||||
|
|
@ -735,7 +793,7 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
|
||||||
if (wrap) {
|
if (wrap) {
|
||||||
const spec = getTemplateSpecForPost(post, "mini");
|
const spec = getTemplateSpecForPost(post, "mini");
|
||||||
const data = adaptPostToTemplateData(post);
|
const data = adaptPostToTemplateData(post);
|
||||||
root.__swUnmount = mountCard(wrap, spec, data, theme);
|
root.__swUnmount = mountCard(wrap, spec, data, theme, post);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearOcclusion(markersRef);
|
clearOcclusion(markersRef);
|
||||||
|
|
|
||||||
|
|
@ -531,6 +531,107 @@ body[data-theme="light"] .sw-chat-bubble::before{
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sw-modal-hero-row{
|
||||||
|
display:flex;
|
||||||
|
gap:10px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-rail{
|
||||||
|
width: 16px;
|
||||||
|
min-width: 16px;
|
||||||
|
height: 100%;
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:space-between;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: .08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color:#e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-rail-bar{
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(180deg, #22c55e 0%, #facc15 50%, #ef4444 100%);
|
||||||
|
border: 2px solid rgba(0,0,0,0.45);
|
||||||
|
position: relative;
|
||||||
|
box-shadow: inset 0 0 10px rgba(0,0,0,0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-rail-marker{
|
||||||
|
position:absolute;
|
||||||
|
left:50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 20px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background:#0b1220;
|
||||||
|
border:2px solid #fff;
|
||||||
|
box-shadow: 0 4px 10px rgba(0,0,0,0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-rail-score{
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 900;
|
||||||
|
color:#fff;
|
||||||
|
background:#0b1220;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
box-shadow: 0 6px 12px rgba(0,0,0,0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-modal-hero-row{
|
||||||
|
display:flex;
|
||||||
|
gap:12px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-card-wrap{
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-mini-bar{
|
||||||
|
position:absolute;
|
||||||
|
left:0;
|
||||||
|
top:0;
|
||||||
|
bottom:0;
|
||||||
|
width: 8px;
|
||||||
|
background: linear-gradient(180deg, #22c55e 0%, #facc15 50%, #ef4444 100%);
|
||||||
|
border-top-left-radius: 18px;
|
||||||
|
border-bottom-left-radius: 18px;
|
||||||
|
box-shadow: inset 0 0 6px rgba(0,0,0,0.35);
|
||||||
|
pointer-events:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-mini-marker{
|
||||||
|
position:absolute;
|
||||||
|
left:50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 14px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background:#0b1220;
|
||||||
|
border:1px solid #fff;
|
||||||
|
box-shadow: 0 3px 6px rgba(0,0,0,0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-mini-score{
|
||||||
|
position:absolute;
|
||||||
|
right:6px;
|
||||||
|
bottom:6px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 900;
|
||||||
|
color:#fff;
|
||||||
|
letter-spacing: .03em;
|
||||||
|
pointer-events:none;
|
||||||
|
}
|
||||||
|
|
||||||
.sw-modal-hero img{
|
.sw-modal-hero img{
|
||||||
width:100%;
|
width:100%;
|
||||||
height:100%;
|
height:100%;
|
||||||
|
|
@ -639,6 +740,13 @@ body[data-theme="light"] .sw-modal-hero-fallback{
|
||||||
width: min(96vw, 420px);
|
width: min(96vw, 420px);
|
||||||
height: min(92vh, 700px);
|
height: min(92vh, 700px);
|
||||||
}
|
}
|
||||||
|
.sw-modal-hero-row{
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.sw-truth-rail{
|
||||||
|
width: 12px;
|
||||||
|
min-width: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SW_CENTERED_MODAL_ANIM */
|
/* SW_CENTERED_MODAL_ANIM */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue