Skip to content

Commit 0cb5757

Browse files
committed
Enable popularity
1 parent 83e4dcb commit 0cb5757

3 files changed

Lines changed: 118 additions & 38 deletions

File tree

docs/javascripts/usermods.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ function getControlValues() {
205205
const usernameFilter = document.querySelector('select[name="username-filter"]');
206206

207207
const filter = tagFilter ? tagFilter.value : 'all';
208-
const sort = orderSelect ? orderSelect.value : (typeof defaultSort !== 'undefined' ? defaultSort : 'title');
208+
const sort = orderSelect ? orderSelect.value : (typeof defaultSort !== 'undefined' ? defaultSort : 'popularity');
209209
const order = getCurrentSortOrder();
210210
const username = usernameFilter ? usernameFilter.value : 'all';
211211

@@ -316,7 +316,7 @@ function applyFiltersAndSort(filterValue, sortValue, sortOrder, usernameValue, r
316316
}
317317

318318
// Apply sorting
319-
const sortBy = sortValue || (typeof defaultSort !== 'undefined' ? defaultSort : 'title');
319+
const sortBy = sortValue || (typeof defaultSort !== 'undefined' ? defaultSort : 'popularity');
320320
const sortDir = sortOrder || (typeof defaultSortOrder !== 'undefined' ? defaultSortOrder : 'ascending');
321321

322322
items.sort((a, b) => {
@@ -490,8 +490,8 @@ function initUsermodModal() {
490490
function openModal(modData) {
491491
document.getElementById('usermod-modal-title').textContent = modData.name;
492492
document.getElementById('usermod-modal-author').textContent = modData.username;
493-
document.getElementById('usermod-modal-views').textContent = modData.views;
494-
document.getElementById('usermod-modal-downloads').textContent = modData.downloads;
493+
document.getElementById('usermod-modal-views').textContent = modData.views_formatted || modData.views;
494+
document.getElementById('usermod-modal-downloads').textContent = modData.downloads_formatted || modData.downloads;
495495
document.getElementById('usermod-modal-created').textContent = String(modData.created_date).split('T')[0];
496496

497497
document.getElementById('usermod-modal-readme').innerHTML = marked.parse(modData.readme || '');
@@ -741,8 +741,8 @@ function initUsermodModal() {
741741
name: mod['name'] || '',
742742
username: mod['username'] || '',
743743
repository: mod['repository'] || '',
744-
views: mod['views'] !== undefined ? mod['views'] : 0,
745-
downloads: mod['downloads'] !== undefined ? mod['downloads'] : 0,
744+
views: mod['views_formatted'] !== undefined ? mod['views_formatted'] : 0,
745+
downloads: mod['downloads_formatted'] !== undefined ? mod['downloads_formatted'] : 0,
746746
created_date: mod['created_date'] || '',
747747
images: mod['images'] || [],
748748
readme: mod['readme_data'] || '',

docs/usermods.md

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,11 @@ search:
44
---
55

66
<!-- The usermod will not be displayed if it has one of these keyword in the title. -->
7-
{% set excludes = ['superseded', 'superseeded', 'deprecated'] %}
8-
9-
<!-- Set up popularity weights -->
10-
{% set first_view_weight = 1.0 %}
11-
{% set first_download_weight = 3.0 %}
12-
{% set repeat_view_weight = 0.1 %}
13-
{% set repeat_download_weight = 0.2 %}
14-
15-
<!-- Set limit for repeat views/downloads -->
16-
{% set repeat_download_limit = 50 %}
17-
{% set repeat_view_limit = 100 %}
18-
19-
<!-- Macro to calculate popularity -->
20-
{% macro get_popularity(f_views, f_downloads, r_views, r_downloads) %}
21-
{% set r_views = [r_views, repeat_view_limit]|min %}
22-
{% set r_downloads = [r_downloads, repeat_download_limit]|min %}
23-
24-
{% set popularity = (f_views * first_view_weight) + (r_views * repeat_view_weight) + (f_downloads * first_download_weight) + (r_downloads * repeat_download_weight) %}
25-
{{ popularity }}
26-
{% endmacro %}
7+
{% set excludes = ['superseded', 'superseeded', 'deprecated', 'depreciated'] %}
278

289
<!-- The variables that mods can be sorted by and their default orientation -->
29-
{# set orders = [('popularity', 'descending'), ('title', 'ascending'), ('username', 'ascending'), ('created_date', 'descending')] #}
30-
{% set orders = [('title', 'ascending'), ('username', 'ascending'), ('created_date', 'descending')] %}
10+
{% set orders = [('popularity', 'descending'), ('title', 'ascending'), ('username', 'ascending'), ('created_date', 'descending')] %}
11+
3112

3213
<!-- GA4 data -->
3314
{% set ga4_data = {} %}
@@ -75,12 +56,16 @@ search:
7556
{% set repeat_views = ga4_mod.get('repeat_views', 0) %}
7657
{% set repeat_downloads = ga4_mod.get('repeat_downloads', 0) %}
7758

78-
{% set popularity = get_popularity(first_time_views, first_time_downloads, repeat_views, repeat_downloads)|float %}
59+
{% set popularity = get_popularity(first_time_views, first_time_downloads, repeat_views, repeat_downloads, mod.created_date)|float %}
60+
{% set views_fmt = format_count(first_time_views|default(0)) %}
61+
{% set downloads_fmt = format_count(first_time_downloads|default(0)) %}
7962

8063
{% set _ = mod.update({'username': user.username}) %}
8164
{% set _ = mod.update({'repository': repo.name}) %}
8265
{% set _ = mod.update({'views': first_time_views|default(0)}) %}
8366
{% set _ = mod.update({'downloads': first_time_downloads|default(0)}) %}
67+
{% set _ = mod.update({'views_formatted': views_fmt}) %}
68+
{% set _ = mod.update({'downloads_formatted': downloads_fmt}) %}
8469
{% set _ = mod.update({'popularity': popularity}) %}
8570
{% set _ = mod.update({'tags': tags}) %}
8671

@@ -97,8 +82,7 @@ search:
9782
{% endfor %}
9883
{% endfor %}
9984

100-
{# set all_mods = all_mods|sort(attribute='popularity')|reverse|list #}
101-
{% set all_mods = all_mods|sort(attribute='username')|reverse|list %}
85+
{% set all_mods = all_mods|sort(attribute='popularity')|reverse|list %}
10286
{% set all_tags = all_tags|sort %}
10387
{% set all_usernames = all_usernames|unique|sort|list %}
10488

@@ -122,8 +106,8 @@ search:
122106
<script>
123107
var usermods = {{ all_mods|tojson }};
124108
var per_page = 20;
125-
var defaultSort = '{{ orders[1][0] }}';
126-
var defaultSortOrder = '{{ orders[1][1] }}';
109+
var defaultSort = '{{ orders[0][0] }}';
110+
var defaultSortOrder = '{{ orders[0][1] }}';
127111
</script>
128112

129113
<!-- Template for the modal -->
@@ -138,13 +122,13 @@ var defaultSortOrder = '{{ orders[1][1] }}';
138122
<span id="usermod-modal-author" class="usermod-modal-author"></span>
139123
<div class="usermod-modal-meta-row">
140124
<!-- Views -->
141-
<span class="twemoji" style="display: none;">
125+
<span class="twemoji">
142126
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0 8a5 5 0 0 1-5-5 5 5 0 0 1 5-5 5 5 0 0 1 5 5 5 5 0 0 1-5 5m0-12.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5"></path></svg>
143127
</span>
144-
<span id="usermod-modal-views" style="display: none;"></span>
128+
<span id="usermod-modal-views"></span>
145129
<!-- Downloads -->
146-
<span class="twemoji" style="display: none;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 20h14v-2H5m14-9h-4V3H9v6H5l7 7z"></path></svg></span>
147-
<span id="usermod-modal-downloads" style="display: none;"></span>
130+
<span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 20h14v-2H5m14-9h-4V3H9v6H5l7 7z"></path></svg></span>
131+
<span id="usermod-modal-downloads"></span>
148132
<!-- Created Date -->
149133
<span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 19H5V8h14m-3-7v2H8V1H6v2H5c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2h-1V1m-1 11h-5v5h5z"></path></svg></span>
150134
<span id="usermod-modal-created"></span>
@@ -251,7 +235,7 @@ If you would like your mods featured here, please consider [Submitting a Usermod
251235
**{{ mod.title }}**{ .custom-card-title }
252236
*{{ mod.username }}*{ .custom-card-author }
253237

254-
<span class="usermod-card-stats" style="display: none;">:material-eye: {{ mod.views }} :material-download: {{ mod.downloads }}</span>
238+
<span class="usermod-card-stats">:material-eye: {{ mod.views_formatted }} :material-download: {{ mod.downloads_formatted }}</span>
255239
<span class="usermod-card-date">:material-calendar: {{ mod.created_date.split('T')[0] }}</span>
256240

257241
<ul class="usermod-tags">

main.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,102 @@ def usermod_author(url):
7777
return match.group(1)
7878
return ""
7979

80+
@env.macro
81+
def format_count(count):
82+
"""
83+
Format view/download counts for display.
84+
Numbers over 999 are converted to 'k' format with increments of 100.
85+
86+
Examples:
87+
999 -> "999"
88+
1000 -> "1k"
89+
1050 -> "1k"
90+
1100 -> "1.1k"
91+
1500 -> "1.5k"
92+
2340 -> "2.3k"
93+
94+
Args:
95+
count: Integer count to format
96+
97+
Returns:
98+
Formatted string
99+
"""
100+
try:
101+
count = int(count)
102+
except (ValueError, TypeError):
103+
return "0"
104+
105+
if count < 1000:
106+
return str(count)
107+
108+
# Convert to thousands and round to nearest 0.1
109+
k_value = count / 1000.0
110+
# Round to 1 decimal place
111+
k_rounded = round(k_value, 1)
112+
113+
# Format: if it's a whole number, don't show decimal
114+
if k_rounded == int(k_rounded):
115+
return f"{int(k_rounded)}k"
116+
else:
117+
return f"{k_rounded}k"
118+
119+
@env.macro
120+
def get_popularity(f_views, f_downloads, r_views, r_downloads, created_date=None):
121+
"""
122+
Calculate popularity score with conversion rate and time decay.
123+
124+
Args:
125+
f_views: First-time views
126+
f_downloads: First-time downloads
127+
r_views: Repeat views
128+
r_downloads: Repeat downloads
129+
created_date: ISO date string (optional, for time decay calculation)
130+
131+
Returns:
132+
Float popularity score
133+
"""
134+
import math
135+
from datetime import datetime
136+
137+
# Weights
138+
FIRST_VIEW_WEIGHT = 1.0
139+
FIRST_DOWNLOAD_WEIGHT = 10.0
140+
REPEAT_VIEW_WEIGHT = 0.1
141+
REPEAT_DOWNLOAD_WEIGHT = 0.5
142+
143+
# Limits
144+
REPEAT_VIEW_LIMIT = 100
145+
REPEAT_DOWNLOAD_LIMIT = 50
146+
147+
# Apply limits to repeat metrics
148+
r_views = min(r_views, REPEAT_VIEW_LIMIT)
149+
r_downloads = min(r_downloads, REPEAT_DOWNLOAD_LIMIT)
150+
151+
# Base popularity score
152+
popularity = (
153+
(f_views * FIRST_VIEW_WEIGHT) +
154+
(r_views * REPEAT_VIEW_WEIGHT) +
155+
(f_downloads * FIRST_DOWNLOAD_WEIGHT) +
156+
(r_downloads * REPEAT_DOWNLOAD_WEIGHT)
157+
)
158+
159+
# Apply time decay if created_date is provided
160+
if created_date:
161+
try:
162+
# Parse ISO date string
163+
created = datetime.fromisoformat(str(created_date).split('T')[0])
164+
now = datetime.now()
165+
age_in_days = (now - created).days
166+
167+
# Apply exponential decay (180-day half-life)
168+
decay_factor = math.exp(-age_in_days / 180)
169+
popularity = popularity * decay_factor
170+
except (ValueError, AttributeError):
171+
# If date parsing fails, skip decay
172+
pass
173+
174+
return popularity
175+
80176
@env.macro
81177
def github_contributors(repo="DraftShift/Docs"):
82178
"""Fetch and display GitHub contributors for a repository (cached during build)"""

0 commit comments

Comments
 (0)