minor changes

This commit is contained in:
2025-11-22 13:28:42 +01:00
parent 2e4a6ae7c4
commit 89ab27540d
2 changed files with 131 additions and 51 deletions

View File

@@ -242,6 +242,7 @@ const REFRESH_SETTINGS_KEY = 'trackerRefreshSettings';
const SORT_SETTINGS_KEY = 'trackerSortSettings'; const SORT_SETTINGS_KEY = 'trackerSortSettings';
const SORT_SETTINGS_LEGACY_KEY = 'trackerSortMode'; const SORT_SETTINGS_LEGACY_KEY = 'trackerSortMode';
const FACEBOOK_TRACKING_PARAMS = ['__cft__', '__tn__', '__eep__', 'mibextid', 'set', 'comment_id', 'hoisted_section_header_type']; const FACEBOOK_TRACKING_PARAMS = ['__cft__', '__tn__', '__eep__', 'mibextid', 'set', 'comment_id', 'hoisted_section_header_type'];
const FACEBOOK_PROFILE_SEGMENT_BLOCKLIST = new Set(['reel', 'reels', 'watch', 'video.php', 'videos', 'photo.php', 'photos', 'story.php', 'permalink.php', 'share', 'posts']);
const VALID_SORT_MODES = new Set(['created', 'deadline', 'smart', 'lastCheck', 'lastChange']); const VALID_SORT_MODES = new Set(['created', 'deadline', 'smart', 'lastCheck', 'lastChange']);
const DEFAULT_SORT_SETTINGS = { mode: 'created', direction: 'desc' }; const DEFAULT_SORT_SETTINGS = { mode: 'created', direction: 'desc' };
const BOOKMARKS_BASE_URL = 'https://www.facebook.com/search/top'; const BOOKMARKS_BASE_URL = 'https://www.facebook.com/search/top';
@@ -444,6 +445,99 @@ function formatUrlForDisplay(url) {
} }
} }
function deriveFacebookProfileUrl(rawValue) {
if (typeof rawValue !== 'string') {
return null;
}
const trimmed = rawValue.trim();
if (!trimmed) {
return null;
}
let parsed;
try {
parsed = new URL(trimmed);
} catch (error) {
try {
parsed = new URL(trimmed, window.location.origin);
} catch (innerError) {
try {
parsed = new URL(trimmed, 'https://www.facebook.com');
} catch (fallbackError) {
return null;
}
}
}
const hostname = parsed.hostname.toLowerCase();
if (!hostname.endsWith('facebook.com')) {
return null;
}
const origin = `${parsed.protocol}//${parsed.hostname}`;
const normalizedPath = parsed.pathname.replace(/\/+$/, '') || '/';
const segments = normalizedPath.split('/').filter(Boolean);
const firstSegment = segments[0] || '';
const lowerFirst = firstSegment.toLowerCase();
const idParam = parsed.searchParams.get('id');
if (lowerFirst === 'groups' && segments[1]) {
return `${origin}/groups/${segments[1]}`;
}
if (lowerFirst === 'profile.php' && idParam) {
return `${origin}/profile.php?id=${encodeURIComponent(idParam)}`;
}
if (idParam && (lowerFirst === 'story.php' || lowerFirst === 'permalink.php')) {
return `${origin}/profile.php?id=${encodeURIComponent(idParam)}`;
}
if (lowerFirst === 'people' && segments[1] && segments[2]) {
return `${origin}/people/${segments[1]}/${segments[2]}`;
}
if (firstSegment && !FACEBOOK_PROFILE_SEGMENT_BLOCKLIST.has(lowerFirst)) {
return `${origin}/${firstSegment}`;
}
if (idParam) {
return `${origin}/profile.php?id=${encodeURIComponent(idParam)}`;
}
return null;
}
function getProfileLinkForPost(post) {
if (!post || typeof post !== 'object') {
return null;
}
const candidates = [];
if (typeof post.url === 'string') {
candidates.push(post.url);
}
if (Array.isArray(post.alternate_urls)) {
for (const alt of post.alternate_urls) {
if (typeof alt === 'string') {
candidates.push(alt);
}
}
}
for (const candidate of candidates) {
const profileUrl = deriveFacebookProfileUrl(candidate);
if (profileUrl) {
return profileUrl;
}
}
return null;
}
function toTimestamp(value, fallback = null) { function toTimestamp(value, fallback = null) {
if (!value) { if (!value) {
return fallback; return fallback;
@@ -3454,14 +3548,6 @@ function createPostCard(post, status, meta = {}) {
const tabTotalCount = typeof meta.tabTotalCount === 'number' ? meta.tabTotalCount : posts.length; const tabTotalCount = typeof meta.tabTotalCount === 'number' ? meta.tabTotalCount : posts.length;
const searchActive = !!meta.searchActive; const searchActive = !!meta.searchActive;
const counterBadge = displayIndex !== null
? `
<div class="post-counter" aria-hidden="true">
<span class="post-counter__value">${String(displayIndex).padStart(2, '0')}</span>
</div>
`
: '';
const profileRowsHtml = status.profileStatuses.map((profileStatus) => { const profileRowsHtml = status.profileStatuses.map((profileStatus) => {
const classes = ['profile-line', `profile-line--${profileStatus.status}`]; const classes = ['profile-line', `profile-line--${profileStatus.status}`];
const isCurrentProfile = parseInt(profileStatus.profile_number, 10) === status.profileNumber; const isCurrentProfile = parseInt(profileStatus.profile_number, 10) === status.profileNumber;
@@ -3555,6 +3641,10 @@ function createPostCard(post, status, meta = {}) {
const creatorDisplay = creatorName || 'Unbekannt'; const creatorDisplay = creatorName || 'Unbekannt';
const titleText = (post.title && post.title.trim()) ? post.title.trim() : creatorDisplay; const titleText = (post.title && post.title.trim()) ? post.title.trim() : creatorDisplay;
const profileLink = getProfileLinkForPost(post);
const creatorContent = profileLink
? `<a href="${escapeHtml(profileLink)}" target="_blank" rel="noopener noreferrer" class="post-creator__link">${escapeHtml(creatorDisplay)}</a>`
: escapeHtml(creatorDisplay);
const deadlineText = formatDeadline(post.deadline_at); const deadlineText = formatDeadline(post.deadline_at);
const hasDeadline = Boolean(post.deadline_at); const hasDeadline = Boolean(post.deadline_at);
@@ -3598,7 +3688,6 @@ function createPostCard(post, status, meta = {}) {
return ` return `
<div class="post-card ${status.isComplete ? 'complete' : ''}" id="post-${post.id}"> <div class="post-card ${status.isComplete ? 'complete' : ''}" id="post-${post.id}">
<div class="post-header"> <div class="post-header">
${counterBadge}
<div class="post-title-with-checkbox"> <div class="post-title-with-checkbox">
<div class="post-title">${escapeHtml(titleText)}</div> <div class="post-title">${escapeHtml(titleText)}</div>
<label class="success-checkbox success-checkbox--header"> <label class="success-checkbox success-checkbox--header">
@@ -3607,6 +3696,9 @@ function createPostCard(post, status, meta = {}) {
</label> </label>
</div> </div>
<div class="post-header-right"> <div class="post-header-right">
<div class="post-status ${status.isComplete ? 'complete' : ''}">
${status.checkedCount}/${status.targetCount} ${status.isComplete ? '✓' : ''}
</div>
<div class="post-target"> <div class="post-target">
<span>Benötigte Profile:</span> <span>Benötigte Profile:</span>
<select class="control-select post-target__select" data-post-id="${post.id}"> <select class="control-select post-target__select" data-post-id="${post.id}">
@@ -3616,9 +3708,6 @@ function createPostCard(post, status, meta = {}) {
`).join('')} `).join('')}
</select> </select>
</div> </div>
<div class="post-status ${status.isComplete ? 'complete' : ''}">
${status.checkedCount}/${status.targetCount} ${status.isComplete ? '✓' : ''}
</div>
</div> </div>
</div> </div>
@@ -3640,9 +3729,8 @@ function createPostCard(post, status, meta = {}) {
<div>Erstellt: ${escapeHtml(createdDate)}</div> <div>Erstellt: ${escapeHtml(createdDate)}</div>
<div>Letzte Änderung: ${escapeHtml(lastChangeDate)}</div> <div>Letzte Änderung: ${escapeHtml(lastChangeDate)}</div>
</div> </div>
<div class="post-creator">Erstellt von: ${escapeHtml(creatorDisplay)}</div> <div class="post-creator">Erstellt von: ${creatorContent}</div>
</div> </div>
${directLinkHtml}
<div class="post-deadline-row" data-post-id="${post.id}"> <div class="post-deadline-row" data-post-id="${post.id}">
<span class="${deadlineClasses.join(' ')}" ${deadlineStyle}>Deadline: ${escapeHtml(deadlineText)}</span> <span class="${deadlineClasses.join(' ')}" ${deadlineStyle}>Deadline: ${escapeHtml(deadlineText)}</span>
@@ -3655,6 +3743,7 @@ function createPostCard(post, status, meta = {}) {
</button> </button>
` : ''} ` : ''}
</div> </div>
${directLinkHtml}
<div class="post-profiles"> <div class="post-profiles">
${profileRowsHtml} ${profileRowsHtml}

View File

@@ -577,26 +577,6 @@ h1 {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
} }
.post-counter {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 26px;
height: 26px;
margin-right: 10px;
border-radius: 999px;
background: #f3f4f6;
color: #1f2937;
font-weight: 600;
font-size: 13px;
}
.post-counter__value::before {
content: '#';
margin-right: 2px;
color: #6b7280;
}
.post-card.complete { .post-card.complete {
opacity: 0.7; opacity: 0.7;
border-left: 4px solid #059669; border-left: 4px solid #059669;
@@ -637,9 +617,11 @@ h1 {
.post-header-right { .post-header-right {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
align-items: flex-end; align-items: center;
gap: 8px; justify-content: flex-end;
flex-wrap: wrap;
gap: 10px;
} }
.post-title-with-checkbox { .post-title-with-checkbox {
@@ -663,18 +645,18 @@ h1 {
.post-status { .post-status {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 6px;
padding: 6px 12px; padding: 0;
background: #f0f2f5; background: transparent;
border-radius: 6px; border-radius: 0;
font-size: 14px; font-size: 13px;
font-weight: 600; font-weight: 600;
white-space: nowrap; white-space: nowrap;
} }
.post-status.complete { .post-status.complete {
background: #d1fae5; background: transparent;
color: #065f46; color: #059669;
} }
@@ -956,6 +938,7 @@ h1 {
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 8px;
font-size: 13px; font-size: 13px;
width: 100%;
} }
.post-link__label { .post-link__label {
@@ -966,13 +949,27 @@ h1 {
.post-link__anchor { .post-link__anchor {
color: #2563eb; color: #2563eb;
text-decoration: none; text-decoration: none;
word-break: break-all; word-break: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
min-width: 0;
} }
.post-link__anchor:hover { .post-link__anchor:hover {
text-decoration: underline; text-decoration: underline;
} }
.post-creator__link {
color: #2563eb;
text-decoration: none;
}
.post-creator__link:hover {
text-decoration: underline;
}
.post-profiles { .post-profiles {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -1720,12 +1717,6 @@ h1 {
padding-left: 20px; padding-left: 20px;
} }
.post-counter {
margin-bottom: 4px;
min-width: 24px;
height: 24px;
}
.header-controls { .header-controls {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;