feat: add impact-colored tags to post modals
- Update renderTagPills to accept topicImpacts and apply color classes - Tags colored by impact: negative=red, neutral=blue, positive=green - Add CSS for tag color classes (sw-tag-negative, sw-tag-neutral, sw-tag-positive) - Tags display below title in post modals Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
91e024e156
commit
9e6f3f8029
|
|
@ -524,7 +524,7 @@ function getCameraStreamUrl(postData) {
|
||||||
return embedUrl;
|
return embedUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTagPills(tags) {
|
function renderTagPills(tags, topicImpacts = {}) {
|
||||||
if (!Array.isArray(tags) || tags.length === 0) return "";
|
if (!Array.isArray(tags) || tags.length === 0) return "";
|
||||||
const uniq = [];
|
const uniq = [];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
|
|
@ -536,16 +536,25 @@ function renderTagPills(tags) {
|
||||||
if (uniq.length >= 6) break;
|
if (uniq.length >= 6) break;
|
||||||
}
|
}
|
||||||
if (uniq.length === 0) return "";
|
if (uniq.length === 0) return "";
|
||||||
|
|
||||||
|
// Get impact color class: negative=red, neutral=blue, positive=green
|
||||||
|
const getImpactClass = (tag) => {
|
||||||
|
const impact = topicImpacts?.[tag] ?? topicImpacts?.[tag.toLowerCase()] ?? 0;
|
||||||
|
if (impact < 0) return "sw-tag-negative";
|
||||||
|
if (impact > 0) return "sw-tag-positive";
|
||||||
|
return "sw-tag-neutral";
|
||||||
|
};
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="sw-modal-tags">
|
<div class="sw-modal-tags">
|
||||||
${uniq.map((t) => `<span class="sw-modal-tag">#${escapeHtml(t)}</span>`).join("")}
|
${uniq.map((t) => `<span class="sw-modal-tag ${getImpactClass(t)}">#${escapeHtml(t)}</span>`).join("")}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setModalTags(modalWrap, post) {
|
function setModalTags(modalWrap, post) {
|
||||||
if (!modalWrap) return;
|
if (!modalWrap) return;
|
||||||
const tagsHtml = renderTagPills(post?.event_tags || post?.tags);
|
const tagsHtml = renderTagPills(post?.event_tags || post?.tags, post?.topic_impacts);
|
||||||
const existing = modalWrap.querySelector(".sw-modal-tags");
|
const existing = modalWrap.querySelector(".sw-modal-tags");
|
||||||
if (tagsHtml) {
|
if (tagsHtml) {
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
|
@ -1281,7 +1290,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
const isCluster = isClusterMainPost(postData);
|
const isCluster = isClusterMainPost(postData);
|
||||||
const clusterItems = Array.isArray(postData?.cluster_items) ? postData.cluster_items : [];
|
const clusterItems = Array.isArray(postData?.cluster_items) ? postData.cluster_items : [];
|
||||||
const clusterCount = Array.isArray(postData?.cluster_members) ? postData.cluster_members.length : 0;
|
const clusterCount = Array.isArray(postData?.cluster_members) ? postData.cluster_members.length : 0;
|
||||||
const tagPills = renderTagPills(postData?.event_tags || postData?.tags);
|
const tagPills = renderTagPills(postData?.event_tags || postData?.tags, postData?.topic_impacts);
|
||||||
const heatmapPill = HEATMAP_ENABLED
|
const heatmapPill = HEATMAP_ENABLED
|
||||||
? `<div class="sw-stat-pill sw-heatmap-pill" data-action="cluster-heatmap">🔥 Heat map</div>`
|
? `<div class="sw-stat-pill sw-heatmap-pill" data-action="cluster-heatmap">🔥 Heat map</div>`
|
||||||
: "";
|
: "";
|
||||||
|
|
@ -1336,7 +1345,9 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sw-cluster-title">${escapeHtml(postData?.cluster_title || postData?.title || "Untitled")}</div>
|
<div class="sw-cluster-title">${escapeHtml(postData?.cluster_title || postData?.title || "Untitled")}</div>
|
||||||
|
<div class="sw-modal-title-meta" style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;">
|
||||||
${tagPills}
|
${tagPills}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="sw-cluster-body">
|
<div class="sw-cluster-body">
|
||||||
<div class="sw-cluster-left">
|
<div class="sw-cluster-left">
|
||||||
|
|
@ -1575,7 +1586,9 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
</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>
|
||||||
|
<div class="sw-modal-title-meta" style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;">
|
||||||
${tagPills}
|
${tagPills}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="sw-modal-summary">
|
<div class="sw-modal-summary">
|
||||||
${escapeHtml(data?.summary || postData?.snippet || "")}
|
${escapeHtml(data?.summary || postData?.snippet || "")}
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ export function getCameraStreamUrl(postData) {
|
||||||
return embedUrl;
|
return embedUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderTagPills(tags) {
|
export function renderTagPills(tags, topicImpacts = {}) {
|
||||||
if (!Array.isArray(tags) || tags.length === 0) return "";
|
if (!Array.isArray(tags) || tags.length === 0) return "";
|
||||||
const uniq = [];
|
const uniq = [];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
|
|
@ -102,16 +102,25 @@ export function renderTagPills(tags) {
|
||||||
if (uniq.length >= 6) break;
|
if (uniq.length >= 6) break;
|
||||||
}
|
}
|
||||||
if (uniq.length === 0) return "";
|
if (uniq.length === 0) return "";
|
||||||
|
|
||||||
|
// Get impact color class: negative=red, neutral=blue, positive=green
|
||||||
|
const getImpactClass = (tag) => {
|
||||||
|
const impact = topicImpacts?.[tag] ?? topicImpacts?.[tag.toLowerCase()] ?? 0;
|
||||||
|
if (impact < 0) return "sw-tag-negative";
|
||||||
|
if (impact > 0) return "sw-tag-positive";
|
||||||
|
return "sw-tag-neutral";
|
||||||
|
};
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="sw-modal-tags">
|
<div class="sw-modal-tags">
|
||||||
${uniq.map((t) => `<span class="sw-modal-tag">#${escapeHtml(t)}</span>`).join("")}
|
${uniq.map((t) => `<span class="sw-modal-tag ${getImpactClass(t)}">#${escapeHtml(t)}</span>`).join("")}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setModalTags(modalWrap, post) {
|
export function setModalTags(modalWrap, post) {
|
||||||
if (!modalWrap) return;
|
if (!modalWrap) return;
|
||||||
const tagsHtml = renderTagPills(post?.event_tags || post?.tags);
|
const tagsHtml = renderTagPills(post?.event_tags || post?.tags, post?.topic_impacts);
|
||||||
const existing = modalWrap.querySelector(".sw-modal-tags");
|
const existing = modalWrap.querySelector(".sw-modal-tags");
|
||||||
if (tagsHtml) {
|
if (tagsHtml) {
|
||||||
if (existing) {
|
if (existing) {
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,23 @@
|
||||||
export const TEMPLATE_SPECS = {
|
export const TEMPLATE_SPECS = {
|
||||||
polaroid: {
|
polaroid: {
|
||||||
mini_spec: {
|
mini_spec: {
|
||||||
size: { w: 100, h: 130 },
|
size: { w: 80, h: 104 },
|
||||||
background: { type: "solid", value: "#ffffff" },
|
background: { type: "solid", value: "#ffffff" },
|
||||||
radius: 4,
|
radius: 3,
|
||||||
layers: [
|
layers: [
|
||||||
{ id: "image", type: "image", x: 5, y: 5, w: 90, h: 90, bind: "data.image", radius: 2 },
|
{ id: "image", type: "image", x: 4, y: 4, w: 72, h: 72, bind: "data.image", radius: 2 },
|
||||||
{ id: "title", type: "text", x: 5, y: 98, w: 90, h: 18, bind: "data.headline", style: "text.polaroidTitle", maxLines: 2 },
|
{ id: "title", type: "text", x: 4, y: 78, w: 72, h: 14, bind: "data.headline", style: "text.polaroidTitle", maxLines: 2 },
|
||||||
{ id: "author", type: "text", x: 5, y: 116, w: 90, h: 10, bind: "data.source", style: "text.polaroidCaption", maxLines: 1 },
|
{ id: "author", type: "text", x: 4, y: 92, w: 72, h: 8, bind: "data.source", style: "text.polaroidCaption", maxLines: 1 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
full_spec: {
|
full_spec: {
|
||||||
size: { w: 190, h: 230 },
|
size: { w: 150, h: 180 },
|
||||||
background: { type: "solid", value: "#ffffff" },
|
background: { type: "solid", value: "#ffffff" },
|
||||||
radius: 8,
|
radius: 6,
|
||||||
layers: [
|
layers: [
|
||||||
{ id: "image", type: "image", x: 10, y: 10, w: 170, h: 170, bind: "data.image", radius: 4 },
|
{ id: "image", type: "image", x: 8, y: 8, w: 134, h: 134, bind: "data.image", radius: 3 },
|
||||||
{ id: "author", type: "text", x: 10, y: 185, w: 170, h: 18, bind: "data.source", style: "text.polaroidCaption", maxLines: 1 },
|
{ id: "author", type: "text", x: 8, y: 146, w: 134, h: 14, bind: "data.source", style: "text.polaroidCaption", maxLines: 1 },
|
||||||
{ id: "caption", type: "text", x: 10, y: 205, w: 170, h: 22, bind: "data.headline", style: "text.polaroidText", maxLines: 2 },
|
{ id: "caption", type: "text", x: 8, y: 162, w: 134, h: 16, bind: "data.headline", style: "text.polaroidText", maxLines: 2 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -309,15 +309,13 @@ export function getTemplateKeyForPost(post) {
|
||||||
return 'camera';
|
return 'camera';
|
||||||
}
|
}
|
||||||
|
|
||||||
// If content_type is empty/unknown but post has images, show as polaroid
|
// Detect videos/cameras by URL if content_type not set
|
||||||
// This handles posts where content_type wasn't set but images exist
|
|
||||||
const hasImage = !!(post?.image_small || post?.image_medium || post?.image_large || post?.image || post?.thumbnail_url || post?.media_url);
|
|
||||||
const hasVideo = !!(post?.video_url || post?.embed_url);
|
const hasVideo = !!(post?.video_url || post?.embed_url);
|
||||||
if (!ct && hasImage && !hasVideo) {
|
if (!ct && hasVideo) {
|
||||||
return 'polaroid';
|
return 'video';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default: category-based template
|
// Default: category-based template (news, breaking, etc.)
|
||||||
return normCat(post);
|
return normCat(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1297,6 +1297,22 @@ body[data-theme="light"] .sw-modal-x{
|
||||||
color: var(--sw-modal-text, #e5e7eb);
|
color: var(--sw-modal-text, #e5e7eb);
|
||||||
letter-spacing: 0.2px;
|
letter-spacing: 0.2px;
|
||||||
}
|
}
|
||||||
|
/* Tag impact colors */
|
||||||
|
.sw-modal-tag.sw-tag-negative{
|
||||||
|
background: rgba(239, 68, 68, 0.25);
|
||||||
|
color: #fca5a5;
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.4);
|
||||||
|
}
|
||||||
|
.sw-modal-tag.sw-tag-neutral{
|
||||||
|
background: rgba(59, 130, 246, 0.25);
|
||||||
|
color: #93c5fd;
|
||||||
|
border: 1px solid rgba(59, 130, 246, 0.4);
|
||||||
|
}
|
||||||
|
.sw-modal-tag.sw-tag-positive{
|
||||||
|
background: rgba(34, 197, 94, 0.25);
|
||||||
|
color: #86efac;
|
||||||
|
border: 1px solid rgba(34, 197, 94, 0.4);
|
||||||
|
}
|
||||||
.sw-cluster-body{
|
.sw-cluster-body{
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(220px, 44%) minmax(240px, 56%);
|
grid-template-columns: minmax(220px, 44%) minmax(240px, 56%);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue