Skip to content
Closed
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
25 changes: 25 additions & 0 deletions .eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ module.exports = function (eleventyConfig) {
// eslint-disable-next-line no-unused-vars
const live_packages = Object.entries(data.packages).map(([id, pkg]) => pkg).filter(pkg => !pkg.removed);

// Load libraries data
const librariesData = JSON.parse(fs.readFileSync("libraries.json", "utf8"));
const libraries = librariesData.libraries || [];

eleventyConfig.addCollection("packages", () => {
return live_packages.map(pkg => ({
...pkg,
Expand Down Expand Up @@ -72,6 +76,27 @@ module.exports = function (eleventyConfig) {
}).slice(0,9);
});

// Add libraries collection
eleventyConfig.addCollection("libraries", () => {
return libraries.map(lib => {
// Extract unique Python versions from all releases
const pythonVersions = [...new Set(
(lib.releases || [])
.flatMap(release => release.python_versions || [])
)].sort();

return {
name: lib.name,
description: lib.description || '',
author: lib.author || '',
issues: lib.issues || '',
releases: lib.releases || [],
python_versions: pythonVersions,
permalink: `/library/${lib.name}/`
};
}).sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
});

// simple to date string for some dates without times
eleventyConfig.addFilter("date_format", (date) => {
if (typeof date !== "string" ) return date;
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ _site/*
# Database #
######################
workspace.json
libraries.json
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
build:
npm install
curl -o workspace.json -L "https://github.com/packagecontrol/thecrawl/releases/download/crawler-status/workspace.json"
curl -o libraries.json -L "https://raw.githubusercontent.com/packagecontrol/channel/refs/heads/main/repository.json"
npx @11ty/eleventy

fetch:
curl -o workspace.json -L "https://github.com/packagecontrol/thecrawl/releases/download/crawler-status/workspace.json"
curl -o libraries.json -L "https://raw.githubusercontent.com/packagecontrol/channel/refs/heads/main/repository.json"

lint:
npx eslint

clean:
rm -rf _site/*
rm -f libraries.json

serve:
open http://localhost:8080/
Expand Down
1 change: 1 addition & 0 deletions _includes/header.njk
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<nav>
<a href="https://github.com/wbond/package_control_channel?tab=readme-ov-file#package-control-default-channel">Submitting packages</a>
<a href="https://packagecontrol.io/docs/usage">Using package control</a>
<a href="/libraries/">Libraries</a>
</nav>

<h2><a href="/">Package Control <span class="hot">R</span></a></h2>
Expand Down
58 changes: 58 additions & 0 deletions libraries/index.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
layout: layout.njk
permalink: "/libraries/"
date: Last Modified
---

{% import "libraries/macros.njk" as libs %}

<h1>
<span class="counter" data-all="{{ collections.libraries | length }}">
{{ collections.libraries | length }}
</span>
Libraries
</h1>

<form name="search">
<div class="search-controls">
<label for="search-field" class="screenreader-only">Search</label>
<input type="text" autocomplete="off" name="q" id="search-field" placeholder="Search libraries ...">

<div class="button">
<label for="sort-field">Sort by</label>
<select name="sort-field" id="sort-field">
<option value="relevance">Magic</option>
<option value="name" selected>Name (A-Z)</option>
<option value="name-desc">Name (Z-A)</option>
<option value="author">Author (A-Z)</option>
<option value="author-desc">Author (Z-A)</option>
</select>
</div>
</div>
</form>

<section name="result" style="display:none;">
<h2>Results</h2>
<ul class="libraries-list"></ul>
</section>

<section name="all" id="all-libraries">
<h2>All Libraries</h2>
<ul class="libraries-list" id="libraries-list">
<!-- Libraries will be loaded dynamically -->
</ul>
</section>

{% import "libraries/macros.njk" as libs %}
<template id="library-card">
{{ libs.card({
"name": "placeholder-name",
"description": "placeholder-description",
"author": "placeholder-author",
"issues": "placeholder-issues",
"python_versions": ["3.8"],
"permalink": "placeholder-permalink"
}) }}
</template>

<script src="/static/index.js" type="module" async></script>
60 changes: 60 additions & 0 deletions libraries/library.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
layout: layout.njk
permalink: "library/{{ lib.name | safe }}/"
pagination:
data: collections.libraries
size: 1
alias: lib
eleventyComputed:
title: "{{ lib.name }}"
description: "{{ lib.description }}"
---

{% import "libraries/macros.njk" as libs %}

<h1 class="big">{{ lib.name }}</h1>
<p class="description">{{ lib.description }}</p>

<p>
By {{ lib.author }}
{% if lib.python_versions and lib.python_versions | length > 0 %}
<br>
Python versions:
{% for version in lib.python_versions %}
<span class="py-version">{{ version }}</span>{{ ", " if not loop.last }}
{% endfor %}
{% endif %}
</p>

{% if lib.releases | length > 1 %}
<h2>Releases</h2>
{% else %}
<h2>Release</h2>
{% endif %}

{% for release in lib.releases %}
<p>
{% if release.base %}
<a class="release" href="{{ release.base }}">{{ release.asset or 'Latest Release' }}</a>
{% endif %}
{% if release.python_versions and release.python_versions | length > 0 %}
<br>
Python: {{ release.python_versions | join(', ') }}
{% endif %}
</p>
{% endfor %}

<h2>Links</h2>

<ul class="button-group links">
{% if lib.releases and lib.releases[0] and lib.releases[0].base %}
<li>
<a class="button" href="{{ lib.releases[0].base }}">{{ libs.logo('repo') }} <span>Repository</span></a>
</li>
{% endif %}
{% if lib.issues %}
<li>
<a class="button" href="{{ lib.issues }}">{{ libs.logo('bug') }} <span>Issues</span></a>
</li>
{% endif %}
</ul>
38 changes: 38 additions & 0 deletions libraries/macros.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{% macro card(lib) %}
<div class="library-item">
<h3>
<a href="/library/{{ lib.name | urlencode }}/">
{{ lib.name }}
</a>
</h3>
<p class="description">{{ lib.description }}</p>
<div class="meta">
<span class="author">by {{ lib.author }}</span>
<div class="meta-second-line">
{% if lib.issues %}
<a href="{{ lib.issues }}" class="issues-link">{{ logo('bug') }} Issues</a>
{% endif %}
{% if lib.python_versions %}
<div class="python-versions">
{% for version in lib.python_versions %}
<span class="py-version">py{{ version }}</span>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
{% endmacro %}

{% macro logo(which) %}
{# https://primer.style/octicons #}
{% if which == 'library' %}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z"></path></svg>
{% elif which == 'home' %}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M6.906.664a1.749 1.749 0 0 1 2.187 0l5.25 4.2c.415.332.657.835.657 1.367v7.019A1.75 1.75 0 0 1 13.25 15h-3.5a.75.75 0 0 1-.75-.75V9H7v5.25a.75.75 0 0 1-.75.75h-3.5A1.75 1.75 0 0 1 1 13.25V6.23c0-.531.242-1.034.657-1.366l5.25-4.2Zm1.25 1.171a.25.25 0 0 0-.312 0l-5.25 4.2a.25.25 0 0 0-.094.196v7.019c0 .138.112.25.25.25H5.5V8.25a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 .75.75v5.25h2.75a.25.25 0 0 0 .25-.25V6.23a.25.25 0 0 0-.094-.195Z"></path></svg>
{% elif which == 'bug' %}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M4.72.22a.75.75 0 0 1 1.06 0l1 .999a3.488 3.488 0 0 1 2.441 0l.999-1a.748.748 0 0 1 1.265.332.75.75 0 0 1-.205.729l-.775.776c.616.63.995 1.493.995 2.444v.327c0 .1-.009.197-.025.292.408.14.764.392 1.029.722l1.968-.787a.75.75 0 0 1 .556 1.392L13 7.258V9h2.25a.75.75 0 0 1 0 1.5H13v.5c0 .409-.049.806-.141 1.186l2.17.868a.75.75 0 0 1-.557 1.392l-2.184-.873A4.997 4.997 0 0 1 8 16a4.997 4.997 0 0 1-4.288-2.427l-2.183.873a.75.75 0 0 1-.558-1.392l2.17-.868A5.036 5.036 0 0 1 3 11v-.5H.75a.75.75 0 0 1 0-1.5H3V7.258L.971 6.446a.75.75 0 0 1 .558-1.392l1.967.787c.265-.33.62-.583 1.03-.722a1.677 1.677 0 0 1-.026-.292V4.5c0-.951.38-1.814.995-2.444L4.72 1.28a.75.75 0 0 1 0-1.06Zm.53 6.28a.75.75 0 0 0-.75.75V11a3.5 3.5 0 1 0 7 0V7.25a.75.75 0 0 0-.75-.75ZM6.173 5h3.654A.172.172 0 0 0 10 4.827V4.5a2 2 0 1 0-4 0v.327c0 .096.077.173.173.173Z"></path></svg>
{% elif which == 'repo' %}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z"></path></svg>
{% endif %}
{% endmacro %}
15 changes: 15 additions & 0 deletions libraries/searchindex.json.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
permalink: "/libraries/searchindex.json"
---
[
{% for lib in collections.libraries -%}
{
"name": "{{ lib.name }}",
"description": "{{ lib.description | nl2br | replace('\n', '') }}",
"author": "{{ lib.author }}",
"issues": "{{ lib.issues }}",
"python_versions": {{ lib.python_versions | dump | safe }},
"permalink": "/library/{{ lib.name | urlencode }}/"
}{{ "," if not loop.last }}
{%- endfor %}
]
65 changes: 51 additions & 14 deletions static/index.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import { Data } from './module/data.js';
import { List } from './module/list.js';
import { Search } from './module/search.js';
import { AppFactory } from './module/app-factory.js';
import { Sort } from './module/sort.js';

const data = await new Data().get();
const list = new List();
// Detect app type based on current path
const isLibrariesPage = window.location.pathname.startsWith('/libraries');
const appType = isLibrariesPage ? 'libraries' : 'packages';

function goSearch(value, sortBy = 'relevance', page = 1) {
const srch = new Search(value, data);
// Create app components using factory
const app = await AppFactory.createApp(appType);
const { data, list, search: createSearch, defaultSort } = app;

// Update counter for static pages
const counter = document.querySelector('.counter, h1 .counter');
if (counter) {
counter.textContent = data.length;
counter.setAttribute('data-all', data.length);
}

// Render initial data for libraries (packages handle this differently)
if (isLibrariesPage) {
const initialSorted = Sort.sort(data, 'name');
list.renderAll(initialSorted);
}

function goSearch(value, sortBy = defaultSort, page = 1) {
const srch = createSearch(value, data);

// Update URL with search query, sort parameter, and page
const params = new URLSearchParams();
if (value.length > 0) {
params.set('q', value);
}
if (sortBy !== 'relevance') {
if (sortBy !== defaultSort) {
params.set('sort', sortBy);
}
if (page > 1) {
params.set('page', page);
}

const queryString = params.toString();
const newUrl = queryString ? '?' + queryString : '/';
const basePath = isLibrariesPage ? '/libraries/' : '/';
const newUrl = queryString ? basePath + '?' + queryString : basePath;

// Only push state if URL is actually changing
if (window.location.search !== (queryString ? '?' + queryString : '')) {
Expand All @@ -33,8 +50,14 @@ function goSearch(value, sortBy = 'relevance', page = 1) {
list.clear();

if (value.length < 1) {
// no search query - revert to static homepage
// no search query - revert to homepage
list.revertToNormal();

// Re-render sorted data for libraries (packages handle this differently)
if (isLibrariesPage) {
const sortedData = Sort.sort(data, sortBy);
list.renderAll(sortedData);
}
return
}

Expand Down Expand Up @@ -70,7 +93,7 @@ input.value = query;

// Only show search results if there's a query or explicit sort parameter
if (query || sortBy || urlParams.has('page')) {
const effectiveSortBy = sortBy ?? 'relevance';
const effectiveSortBy = sortBy ?? defaultSort;
sortSelect.value = effectiveSortBy;
goSearch(query.toLowerCase(), effectiveSortBy, page);
}
Expand All @@ -80,9 +103,17 @@ const handleInput = () => {

if (query === '') {
list.revertToNormal();

// Re-render sorted data for libraries
if (isLibrariesPage) {
const sortedData = Sort.sort(data, sortSelect.value);
list.renderAll(sortedData);
}

// Update URL to remove search parameters
if (window.location.pathname !== '/' || window.location.search !== '') {
history.pushState({}, '', '/');
const basePath = isLibrariesPage ? '/libraries/' : '/';
if (window.location.pathname !== basePath || window.location.search !== '') {
history.pushState({}, '', basePath);
}
} else {
goSearch(query, sortSelect.value);
Expand Down Expand Up @@ -127,10 +158,16 @@ window.addEventListener('popstate', () => {

// Handle navigation
if (query || sortBy || urlParams.has('page')) {
const effectiveSortBy = sortBy || (query ? 'relevance' : 'name');
const effectiveSortBy = sortBy || (query ? defaultSort : defaultSort);
sortSelect.value = effectiveSortBy;
goSearch(query, effectiveSortBy, page);
} else {
list.revertToNormal();

// Re-render sorted data for libraries
if (isLibrariesPage) {
const sortedData = Sort.sort(data, sortSelect.value || defaultSort);
list.renderAll(sortedData);
}
}
});
Loading