minor changes
This commit is contained in:
117
web/app.js
117
web/app.js
@@ -242,6 +242,7 @@ const REFRESH_SETTINGS_KEY = 'trackerRefreshSettings';
|
||||
const SORT_SETTINGS_KEY = 'trackerSortSettings';
|
||||
const SORT_SETTINGS_LEGACY_KEY = 'trackerSortMode';
|
||||
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 DEFAULT_SORT_SETTINGS = { mode: 'created', direction: 'desc' };
|
||||
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) {
|
||||
if (!value) {
|
||||
return fallback;
|
||||
@@ -3454,14 +3548,6 @@ function createPostCard(post, status, meta = {}) {
|
||||
const tabTotalCount = typeof meta.tabTotalCount === 'number' ? meta.tabTotalCount : posts.length;
|
||||
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 classes = ['profile-line', `profile-line--${profileStatus.status}`];
|
||||
const isCurrentProfile = parseInt(profileStatus.profile_number, 10) === status.profileNumber;
|
||||
@@ -3555,6 +3641,10 @@ function createPostCard(post, status, meta = {}) {
|
||||
const creatorDisplay = creatorName || 'Unbekannt';
|
||||
|
||||
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 hasDeadline = Boolean(post.deadline_at);
|
||||
@@ -3598,7 +3688,6 @@ function createPostCard(post, status, meta = {}) {
|
||||
return `
|
||||
<div class="post-card ${status.isComplete ? 'complete' : ''}" id="post-${post.id}">
|
||||
<div class="post-header">
|
||||
${counterBadge}
|
||||
<div class="post-title-with-checkbox">
|
||||
<div class="post-title">${escapeHtml(titleText)}</div>
|
||||
<label class="success-checkbox success-checkbox--header">
|
||||
@@ -3607,6 +3696,9 @@ function createPostCard(post, status, meta = {}) {
|
||||
</label>
|
||||
</div>
|
||||
<div class="post-header-right">
|
||||
<div class="post-status ${status.isComplete ? 'complete' : ''}">
|
||||
${status.checkedCount}/${status.targetCount} ${status.isComplete ? '✓' : ''}
|
||||
</div>
|
||||
<div class="post-target">
|
||||
<span>Benötigte Profile:</span>
|
||||
<select class="control-select post-target__select" data-post-id="${post.id}">
|
||||
@@ -3616,9 +3708,6 @@ function createPostCard(post, status, meta = {}) {
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="post-status ${status.isComplete ? 'complete' : ''}">
|
||||
${status.checkedCount}/${status.targetCount} ${status.isComplete ? '✓' : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3640,9 +3729,8 @@ function createPostCard(post, status, meta = {}) {
|
||||
<div>Erstellt: ${escapeHtml(createdDate)}</div>
|
||||
<div>Letzte Änderung: ${escapeHtml(lastChangeDate)}</div>
|
||||
</div>
|
||||
<div class="post-creator">Erstellt von: ${escapeHtml(creatorDisplay)}</div>
|
||||
<div class="post-creator">Erstellt von: ${creatorContent}</div>
|
||||
</div>
|
||||
${directLinkHtml}
|
||||
|
||||
<div class="post-deadline-row" data-post-id="${post.id}">
|
||||
<span class="${deadlineClasses.join(' ')}" ${deadlineStyle}>Deadline: ${escapeHtml(deadlineText)}</span>
|
||||
@@ -3655,6 +3743,7 @@ function createPostCard(post, status, meta = {}) {
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
${directLinkHtml}
|
||||
|
||||
<div class="post-profiles">
|
||||
${profileRowsHtml}
|
||||
|
||||
@@ -577,26 +577,6 @@ h1 {
|
||||
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 {
|
||||
opacity: 0.7;
|
||||
border-left: 4px solid #059669;
|
||||
@@ -637,9 +617,11 @@ h1 {
|
||||
|
||||
.post-header-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.post-title-with-checkbox {
|
||||
@@ -663,18 +645,18 @@ h1 {
|
||||
.post-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
background: #f0f2f5;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
gap: 6px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.post-status.complete {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
background: transparent;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
|
||||
@@ -956,6 +938,7 @@ h1 {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.post-link__label {
|
||||
@@ -966,13 +949,27 @@ h1 {
|
||||
.post-link__anchor {
|
||||
color: #2563eb;
|
||||
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 {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.post-creator__link {
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.post-creator__link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.post-profiles {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1720,12 +1717,6 @@ h1 {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.post-counter {
|
||||
margin-bottom: 4px;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
Reference in New Issue
Block a user