Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: hide broken image if image fails to load #335

Merged
merged 1 commit into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 70 additions & 87 deletions src/svelte/app/components/grid-card.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@
import store from "../../shared/services/store";
import Wrap from "src/svelte/shared/components/wrap.svelte";
import Stack from "src/svelte/shared/components/stack.svelte";
import { afterUpdate, createEventDispatcher, onMount } from "svelte";
import { createEventDispatcher, onMount } from "svelte";
import { HOVER_LINK_SOURCE_ID } from "src/constants";
import EventManager from "src/event/event-manager";
import Icon from "src/svelte/shared/components/icon.svelte";
import { getIconIdForFile } from "../services/file-icon";
import { PluginEvent } from "src/event/types";
import { openContextMenu } from "../services/context-menu";
import { openInCurrentTab } from "../services/open-file";
import Flex from "src/svelte/shared/components/flex.svelte";
import { getDomainFromUrl } from "../services/utils/url-utils";
import Spacer from "src/svelte/shared/components/spacer.svelte";
import Divider from "src/svelte/shared/components/divider.svelte";
Expand All @@ -25,6 +24,11 @@
} from "../services/social-media-image-cache";
import { CoverImageFit } from "src/types";

type SocialMediaImageResult = {
status: "SUCCESS" | "NOT_FOUND" | "EXPIRED" | "NO_IMAGE";
url?: string; // Only present when status is 'SUCCESS'
};

export let displayName: string;
export let path: string;
export let baseName: string;
Expand All @@ -40,9 +44,8 @@
let plugin: VaultExplorerPlugin;
let enableFileIcons: boolean = false;
let loadSocialMediaImage: boolean = true;
let imgSrc: string | null;

let isCoverImageLoaded = false;
let imgSrc: string | null = null;
let isImageLoaded = false;

store.plugin.subscribe((p) => {
plugin = p;
Expand All @@ -52,25 +55,10 @@

const dispatch = createEventDispatcher();

let renderKey = 0;

onMount(() => {
updateImgSrc();
});

afterUpdate(() => {
if (imageUrl === null) {
imgSrc = null;
} else {
updateImgSrc();
}
});

onMount(() => {
function handleLoadSocialMediaImageChange() {
const newValue = plugin.settings.views.grid.loadSocialMediaImage;
loadSocialMediaImage = newValue;
renderKey++;
}

EventManager.getInstance().on(
Expand Down Expand Up @@ -102,29 +90,6 @@
};
});

async function updateImgSrc() {
if (imageUrl !== null) {
const entry = await getSocialMediaImageEntry(imageUrl);
if (entry) {
const isExpired = await isSocialMediaImageEntryExpired(entry);
if (!isExpired) {
const socialUrl = entry.socialMediaImageUrl;

//The url is null when the social media image does not exist
//This will always happen for sites like Twitter (x.com)
//To avoid fetching the same non-existent image, we will set imgSrc to null
if (socialUrl === null) {
imgSrc = null;
} else {
imgSrc = socialUrl;
}
return;
}
}
imgSrc = imageUrl;
}
}

function handleUrlClick(e: Event) {
e.stopPropagation();
}
Expand Down Expand Up @@ -164,29 +129,71 @@
handleCardClick();
}

$: hasFooterContent =
tags != null || custom1 != null || custom2 != null || custom3 != null;
function handleImageLoad() {
isImageLoaded = true;
}

async function handleImageError(event: Event) {
const target = event.target as HTMLImageElement;
target.onerror = null; // Prevent infinite loop

if (!imgSrc) return;
let websiteUrl = target.src;
if (websiteUrl.endsWith("/")) {
websiteUrl = websiteUrl.slice(0, -1); // Remove the trailing slash
}

if (loadSocialMediaImage) {
const socialUrl = await fetchSocialMediaImage(imgSrc);
const socialUrl = await fetchSocialMediaImage(websiteUrl);
if (socialUrl) {
putSocialMediaImageUrl(imgSrc, socialUrl);
await putSocialMediaImageUrl(websiteUrl, socialUrl);
target.src = socialUrl;
} else {
putSocialMediaImageUrl(imgSrc, null);
await putSocialMediaImageUrl(websiteUrl, null);
}
}
}

function handleImageLoad() {
isCoverImageLoaded = true;
async function getCachedSocialMediaImageUrl(
websiteUrl: string,
): Promise<SocialMediaImageResult> {
const entry = await getSocialMediaImageEntry(websiteUrl);

if (entry) {
const { socialMediaImageUrl } = entry;

if (socialMediaImageUrl) {
const isExpired = await isSocialMediaImageEntryExpired(entry);
if (!isExpired) {
return { status: "SUCCESS", url: socialMediaImageUrl };
} else {
return { status: "EXPIRED" }; // Image found but expired
}
} else {
return { status: "NO_IMAGE" }; // Social image was fetched but doesn't exist
}
}

return { status: "NOT_FOUND" }; // Image not cached
}

$: if (imageUrl) {
isImageLoaded = false;
getCachedSocialMediaImageUrl(imageUrl).then((result) => {
const { status, url } = result;
if (status === "SUCCESS") {
imgSrc = url!;
} else if (status === "EXPIRED" || status === "NOT_FOUND") {
imgSrc = imageUrl;
} else if (status === "NO_IMAGE") {
//Do nothing
//This is for websites like x.com where the social image is not found
//We don't want to keep trying to fetch the image
}
});
}

$: hasFooterContent =
tags != null || custom1 != null || custom2 != null || custom3 != null;
</script>

<div
Expand All @@ -207,35 +214,21 @@
on:mouseover={handleCardMouseOver}
>
<div class="vault-explorer-grid-card__cover">
{#each [renderKey] as k (k)}
{#if imgSrc !== null}
<!-- svelte-ignore a11y-missing-attribute -->
<img
class="vault-explorer-grid-card__image"
src={imgSrc}
style="display: {isCoverImageLoaded
? 'block'
: 'none'}; object-fit: {coverImageFit};"
on:load={handleImageLoad}
on:error={handleImageError}
/>
{/if}
{/each}
{#if imgSrc !== null}
<!-- svelte-ignore a11y-missing-attribute -->
<img
class="vault-explorer-grid-card__image"
src={imgSrc}
style="display: {isImageLoaded
? 'block'
: 'none'}; object-fit: {coverImageFit};"
on:load={handleImageLoad}
on:error={handleImageError}
/>
{/if}
{#if imageUrl === null}
<div class="vault-explorer-grid-card__image"></div>
{/if}
<!-- {#if isFavorite === true}
<div class="vault-explorer-grid-card__favorite">
<Flex
justify="center"
align="center"
width="100%"
height="100%"
>
<Icon iconId="star" ariaLabel="Favorite" />
</Flex>
</div>
{/if} -->
</div>
<div class="vault-explorer-grid-card__body">
<div
Expand Down Expand Up @@ -341,16 +334,6 @@
border-top-right-radius: var(--radius-m);
}

/* .vault-explorer-grid-card__favorite {
position: absolute;
top: 8px;
right: 8px;
width: 20px;
height: 20px;
background-color: var(--background-primary);
border-radius: 50%;
} */

.vault-explorer-grid-card__body {
padding: 8px 16px;
}
Expand Down
8 changes: 0 additions & 8 deletions src/svelte/app/services/fetch-social-media-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@ export const fetchSocialMediaImage = async (url: string) => {

const ogImage = getMetaTagContent(document, "og:image");
const twitterImage = getMetaTagContent(document, "twitter:image");
// const maskIcon = getLinkTagContent(document, "mask-icon");
// const appleTouchIcon = getLinkTagContent(document, "apple-touch-icon");

let imageUrl = ogImage || twitterImage;
// || appleTouchIcon || maskIcon;

if (imageUrl) {
//Handle edge case where social media image URL has slashes at the beginning
Expand Down Expand Up @@ -84,11 +81,6 @@ export const fetchSocialMediaImage = async (url: string) => {
}
};

// const getLinkTagContent = (document: Document, property: string) => {
// const tag = document.querySelector(`link[rel='${property}']`);
// return tag ? tag.getAttribute("href") : null;
// };

const getMetaTagContent = (document: Document, property: string) => {
const tag =
document.querySelector(`meta[property='${property}']`) ||
Expand Down
4 changes: 2 additions & 2 deletions src/svelte/app/services/social-media-image-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const putSocialMediaImageUrl = async (
socialMediaImageUrl: string | null
) => {
const db = await openDatabase();
db.put(STORE_NAME, {
await db.put(STORE_NAME, {
url,
socialMediaImageUrl,
timestamp: Date.now(),
Expand Down Expand Up @@ -76,7 +76,7 @@ export const clearSocialMediaImageCache = async () => {
}
};

const openDatabase = (): Promise<IDBPDatabase<SocialMediaImageDB>> => {
const openDatabase = () => {
return openDB<SocialMediaImageDB>(DATABASE_NAME, 1, {
upgrade(db) {
db.createObjectStore(STORE_NAME, { keyPath: "url" });
Expand Down
Loading