Skip to content
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
64 changes: 55 additions & 9 deletions src/.vitepress/theme/components/ExtensionDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<!-- Copyright © 2026 Inkdex -->

<script setup lang="ts">
import { watch } from "vue";
import { computed, watch } from "vue";
import {
getContentRatingBg,
getContentRatingColor,
Expand All @@ -11,17 +11,47 @@ import {
hasChapterProviding,
hasCloudflareBypassProviding,
hasMangaProgressProviding,
type CustomRepository,
type Extension,
} from "../lib/extensions";
import { formatRating } from "../lib/uiUtils";

interface Props {
extension: Extension | null;
show: boolean;
customRepos: CustomRepository[];
}

const props = defineProps<Props>();

const sourceUrl = computed(() => {
if (!props.extension) return null;

if (props.extension.source === "inkdex" && props.extension.sourceRepo) {
return `https://github.com/${props.extension.sourceRepo}`;
}

const repo = props.customRepos.find((r) => r.id === props.extension?.source);
if (repo) {
return `https://github.com/${repo.owner}/${repo.name}`;
}

return null;
});

const sourceDisplayName = computed(() => {
if (!props.extension) return "";

if (props.extension.source === "inkdex") {
return "Inkdex";
}

const repo = props.customRepos.find((r) => r.id === props.extension?.source);
return repo
? `${repo.owner}/${repo.name}`
: props.extension.repoId || props.extension.source;
});

const emit = defineEmits<{
hide: [];
install: [extension: Extension];
Expand Down Expand Up @@ -170,13 +200,29 @@ watch(

<div class="details-section">
<h3>Repository</h3>
<span class="source-badge">
{{
extension.source === "inkdex"
? "Inkdex"
: extension.repoId || extension.source
}}
</span>
<div class="repo-badges">
<span class="source-badge">
{{ sourceDisplayName }}
</span>
<a
v-if="sourceUrl"
:href="sourceUrl"
target="_blank"
rel="noopener noreferrer"
class="source-link"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
/>
</svg>
Source
</a>
</div>
</div>

<div
Expand All @@ -193,7 +239,7 @@ watch(
:key="dev.name"
class="details-developer"
>
<div class="dev-name">{{ dev.name }}</div>
<div class="dev-name">- {{ dev.name }}</div>
<div v-if="dev.github || dev.website" class="dev-links">
<a
v-if="dev.github"
Expand Down
33 changes: 32 additions & 1 deletion src/.vitepress/theme/components/ExtensionList.css
Original file line number Diff line number Diff line change
Expand Up @@ -1037,7 +1037,7 @@
font-size: 0.85rem;
color: var(--vp-c-text-1);
text-decoration: none;
padding: 0.4rem 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 8px;
background: var(--vp-c-bg);
transition: all 0.2s ease;
Expand All @@ -1057,6 +1057,37 @@
height: 14px;
}

.source-link {
font-size: 0.85rem;
color: var(--vp-c-text-1);
text-decoration: none;
padding: 0.25rem 0.5rem;
border-radius: 8px;
background: var(--vp-c-bg);
transition: all 0.2s ease;
border: 1px solid var(--vp-c-divider);
display: inline-flex;
align-items: center;
gap: 0.5rem;
width: fit-content;
line-height: 1.5rem;
}

.repo-badges {
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.source-link:hover {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-2);
border-color: var(--vp-c-border);
}
.source-link svg {
width: 14px;
height: 14px;
}

/* Notification Toast - styled like install button and slides from right */
.notification-toast {
position: fixed;
Expand Down
165 changes: 65 additions & 100 deletions src/.vitepress/theme/components/ExtensionList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const {
customRepos,
loadRepos,
fetchAllExtensions,
fetchExtensionsForRepo,
addCustomRepo,
removeCustomRepo,
} = useExtensions();
Expand Down Expand Up @@ -200,7 +201,6 @@ const handleAddRepo = async () => {
const result = await addCustomRepo(newRepoUrl.value);

if (!result.success) {
// Silently fail if repository is already added
if (result.error?.includes("already added")) {
newRepoUrl.value = "";
checkingRepo.value = false;
Expand All @@ -211,17 +211,33 @@ const handleAddRepo = async () => {
return;
}

const repoUrl = newRepoUrl.value;
newRepoUrl.value = "";

// Extract owner/repo to find the added repo and fetch its extensions
let repoId: string | undefined;
const cleanUrl = repoUrl.replace(/^https?:\/\//, "");
const match = cleanUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
if (match) {
repoId = `${match[1]}/${match[2]}`;
} else if (repoUrl.includes("/")) {
const parts = repoUrl.trim().split("/");
if (parts.length >= 2 && parts[0] && parts[1]) {
repoId = `${parts[0]}/${parts[1]}`;
}
}

showSuccess(
"Repository Added",
`${newRepoUrl.value} has been added successfully`,
`${repoId || repoUrl} has been added successfully`,
);
newRepoUrl.value = "";

// Fetch extensions dynamically without showing loading spinner
await fetchAllExtensions(false);

// Invalidate cache to refresh available languages and labels after extensions are updated
invalidateCache();
if (repoId) {
const addedRepo = customRepos.value.find((r) => r.id === repoId);
if (addedRepo) {
await fetchExtensionsForRepo(addedRepo);
}
}
checkingRepo.value = false;
} catch (e) {
showError("Error", "An error occurred while adding repository");
Expand All @@ -230,6 +246,18 @@ const handleAddRepo = async () => {
}
};

const addRepoFromId = async (repoId: string): Promise<boolean> => {
const trimmed = repoId.trim();
if (trimmed === "inkdex") return true;
if (trimmed.toLowerCase().includes("inkdex/extensions")) return true;

const existing = customRepos.value.find((r) => r.id === trimmed);
if (existing) return true;

const result = await addCustomRepo(trimmed);
return result.success;
};

const handleToggleSource = (source: string) => {
toggleSource(source);
};
Expand Down Expand Up @@ -467,7 +495,6 @@ onMounted(async () => {
urlParams.has("nb") ||
urlParams.has("r") ||
urlParams.has("nr") ||
urlParams.has("ar") ||
urlParams.has("sel") ||
urlParams.has("oss") ||
urlParams.has("m") ||
Expand All @@ -478,105 +505,42 @@ onMounted(async () => {
// Step 1: Load repos and process repositories from URL
loadRepos();

// Helper function to add repository from URL
const addRepoFromUrl = async (repoUrl: string): Promise<boolean> => {
const trimmedUrl = repoUrl.trim();
// Skip inkdex as it's always available
if (trimmedUrl === "inkdex") return true;
if (trimmedUrl.toLowerCase().includes("inkdex/extensions")) return true;

const result = await addCustomRepo(trimmedUrl);
return result.success;
};

// Process ar parameter (additional repositories)
const arParam = urlParams.get("ar");
if (arParam) {
try {
const repoUrls = decodeURIComponent(arParam).split(",");
for (const repoUrl of repoUrls) {
const trimmedUrl = repoUrl.trim();
// Validate URL format before processing
if (
trimmedUrl &&
(trimmedUrl.startsWith("https://github.com/") ||
/^[^/]+\/[^/]+$/.test(trimmedUrl))
) {
await addRepoFromUrl(trimmedUrl);
}
}
} catch (error) {
console.warn("Failed to parse ar parameter:", error);
// Add repos from r/nr params before fetching extensions
const repoIdsToAdd: string[] = [];
const rParam = urlParams.get("r");
if (rParam) {
repoIdsToAdd.push(
...rParam
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0),
);
}
const nrParam = urlParams.get("nr");
if (nrParam) {
repoIdsToAdd.push(
...nrParam
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0),
);
}
for (const repoId of repoIdsToAdd) {
if (
repoId !== "inkdex" &&
!repoId.toLowerCase().includes("inkdex/extensions")
) {
await addRepoFromId(repoId);
}
}

// Step 2: Parse basic URL parameters that don't depend on extensions
// Note: Search query, selected extensions, and only show selected flag
// are now handled by useUrlSync.ts parseUrlParams() function

// Step 2: Fetch extensions after repositories are set up
await fetchAllExtensions();

// Wait for computed properties to update after extensions are loaded
// Use double nextTick to ensure computed properties fully update with new extension data
await nextTick();
await nextTick();

// Step 3: Use centralized URL parsing from useUrlSync (already instantiated above)

// Parse all URL parameters using centralized logic
// Step 3: Parse URL parameters using centralized logic
parseUrlParams();

// Step 4: Auto-select repositories from ar parameter if no explicit r/nr filters
const additionalReposParam = urlParams.get("ar");
if (additionalReposParam && !urlParams.has("r") && !urlParams.has("nr")) {
const repoUrls = decodeURIComponent(additionalReposParam).split(",");
const autoSelectRepoIds: string[] = [];

for (const repoUrl of repoUrls) {
const trimmedUrl = repoUrl.trim();
// Skip inkdex as it's always available and shouldn't be auto-selected
if (trimmedUrl.toLowerCase().includes("inkdex/extensions")) continue;

// Extract repo ID from URL to match with customRepos
const match = trimmedUrl.match(/github\.com\/([^/]+)\/([^/?]+)/);
if (match) {
const [, owner, name] = match;
const repoId = `${owner}-${name}`;

// Check if this repo exists in our custom repos
if (customRepos.value.some((r) => r.id === repoId)) {
autoSelectRepoIds.push(repoId);
}
}
}

// Auto-select repositories from ar parameter
autoSelectRepoIds.forEach((repoId) => {
selectedSources.value.add(repoId);
});

// Update URL to include auto-selected repositories for consistency
if (autoSelectRepoIds.length > 0) {
urlParams.set("r", autoSelectRepoIds.join(","));
}
}

// Step 3: Fetch extensions after repositories are set up
await fetchAllExtensions();

// Wait for computed properties to update after extensions are loaded
await nextTick();

// Step 4: Parse remaining URL parameters that depend on extensions data
// Note: Content ratings, services, languages, and badges parsing
// are now handled by useUrlSync.ts parseUrlParams() function

// Step 5: Parse filter modes from URL (only when relevant filters are present)
// Note: Filter modes parsing and conflict resolution are now handled
// by useUrlSync.ts parseUrlParams() function

// Step 6: Set default SAFE rating if no filters were present
// Step 4: Set default SAFE rating if no filters were present
if (!hasAnyUrlFilters) {
selectedRatings.value = new Set([CONTENT_RATINGS[0]]); // SAFE is first rating
}
Expand Down Expand Up @@ -1113,6 +1077,7 @@ onMounted(async () => {
<ExtensionDetails
:extension="selectedExtension"
:show="showDetails"
:custom-repos="customRepos"
@hide="hideExtensionDetails"
@install="installExtension"
/>
Expand Down
Loading