Skip to content
Open
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
60 changes: 33 additions & 27 deletions library.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const nconf = nodebb.require('nconf');
const validator = nodebb.require('validator');
const benchpressjs = nodebb.require('benchpressjs');
const _ = nodebb.require('lodash');

Expand All @@ -26,12 +25,22 @@ Widget.init = async function (params) {
app = params.app;
};

async function renderWidget(widget, template, options) {
return await app.renderAsync(template, {
_i18n: widget?.res?.locals?._i18n,
...options,
});
}

Widget.renderHTMLWidget = async function (widget) {
if (!isVisibleInCategory(widget) || !isVisibleInTopic(widget)) {
return null;
}
const tpl = widget.data ? widget.data.html : '';
widget.html = await benchpressjs.compileRender(String(tpl), widget.templateData);
widget.html = await benchpressjs.compileRender(String(tpl), {
_i18n: widget.res.locals._i18n,
...widget.templateData,
});
return widget;
};

Expand Down Expand Up @@ -72,7 +81,7 @@ Widget.renderSearchWidget = async function (widget) {
option.selected = option.value === widget.data.defaultIn;
});

widget.html = await app.renderAsync('widgets/search', {
widget.html = await renderWidget(widget, 'widgets/search', {
inOptions: inOptions,
showInControl: widget.data.showInControl === 'on',
enableQuickSearch: widget.data.enableQuickSearch === 'on',
Expand Down Expand Up @@ -121,7 +130,7 @@ Widget.renderRecentViewWidget = async function (widget) {
data.config = data.config || {};
data.config.relative_path = nconf.get('relative_path');
data.canPost = allowedCids.length > 0;
widget.html = await app.renderAsync('recent', data);
widget.html = await renderWidget(widget, 'recent', data);
widget.html = widget.html.replace(/<ol[\s\S]*?<br \/>/, '').replace('<br>', '');
return widget;
};
Expand All @@ -132,7 +141,7 @@ Widget.renderOnlineUsersWidget = async function (widget) {
let userData = await user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline']);
userData = userData.filter(user => user.status !== 'offline');
userData.sort((a, b) => b.lastonline - a.lastonline);
widget.html = await app.renderAsync('widgets/onlineusers', {
widget.html = await renderWidget(widget,'widgets/onlineusers', {
online_users: userData,
sidebar: sidebarLocations.includes(widget.location),
relative_path: nconf.get('relative_path'),
Expand Down Expand Up @@ -161,7 +170,7 @@ Widget.renderActiveUsersWidget = async function (widget) {
}
});

widget.html = await app.renderAsync('widgets/activeusers', {
widget.html = await renderWidget(widget, 'widgets/activeusers', {
active_users: userData,
sidebar: sidebarLocations.includes(widget.location),
relative_path: nconf.get('relative_path'),
Expand All @@ -172,7 +181,7 @@ Widget.renderActiveUsersWidget = async function (widget) {
Widget.renderLatestUsersWidget = async function (widget) {
const count = Math.max(1, widget.data.numUsers || 24);
const users = await user.getUsersFromSet('users:joindate', widget.uid, 0, count - 1);
widget.html = await app.renderAsync('widgets/latestusers', {
widget.html = await renderWidget(widget, 'widgets/latestusers', {
users: users,
sidebar: sidebarLocations.includes(widget.location),
relative_path: nconf.get('relative_path'),
Expand All @@ -184,7 +193,7 @@ Widget.renderTopPostersWidget = async function (widget) {
const count = Math.max(1, widget.data.numUsers || 24);
const users = await user.getUsersFromSet('users:postcount', widget.uid, 0, count - 1);

widget.html = await app.renderAsync('widgets/topposters', {
widget.html = await renderWidget(widget, 'widgets/topposters', {
users: users,
sidebar: sidebarLocations.includes(widget.location),
relative_path: nconf.get('relative_path'),
Expand All @@ -207,7 +216,7 @@ Widget.renderModeratorsWidget = async function (widget) {
if (!moderators.length) {
return null;
}
widget.html = await app.renderAsync('widgets/moderators', {
widget.html = await renderWidget(widget, 'widgets/moderators', {
moderators: moderators,
relative_path: nconf.get('relative_path'),
});
Expand All @@ -229,7 +238,7 @@ Widget.renderForumStatsWidget = async function (widget) {
online: utils.makeNumberHumanReadable(onlineCount + guestCount),
statsClass: widget.data.statsClass,
};
widget.html = await app.renderAsync('widgets/forumstats', stats);
widget.html = await renderWidget(widget, 'widgets/forumstats', stats);
return widget;
};

Expand All @@ -255,14 +264,14 @@ Widget.renderRecentPostsWidget = async function (widget) {
);
postsData = await posts.getPostSummaryByPids(pids, widget.uid, { stripTags: true });
}
const data = {

widget.html = await renderWidget(widget, 'widgets/recentposts', {
uuid: utils.generateUUID(),
posts: postsData,
numPosts: numPosts,
cid: cid,
relative_path: nconf.get('relative_path'),
};
widget.html = await app.renderAsync('widgets/recentposts', data);
});
return widget;
};

Expand Down Expand Up @@ -291,7 +300,7 @@ Widget.renderRecentTopicsWidget = async function (widget) {
};
}
});
widget.html = await app.renderAsync('widgets/recenttopics', {
widget.html = await renderWidget(widget, 'widgets/recenttopics', {
topics: data.topics,
numTopics: numTopics,
relative_path: nconf.get('relative_path'),
Expand All @@ -304,7 +313,7 @@ Widget.renderCategories = async function (widget) {
let categoryData = await categories.getCategoriesByPrivilege('categories:cid', widget.uid, 'find');
categoryData = categoryData.filter(c => c && c.cid !== -1);
const tree = categories.getTree(categoryData, 0);
widget.html = await app.renderAsync('widgets/categories', {
widget.html = await renderWidget(widget, 'widgets/categories', {
categories: tree,
relative_path: nconf.get('relative_path'),
});
Expand Down Expand Up @@ -332,7 +341,7 @@ Widget.renderPopularTags = async function (widget) {
t.widthPercent = ((t.score / maxCount) * 100).toFixed(2);
});

widget.html = await app.renderAsync('widgets/populartags', {
widget.html = await renderWidget(widget, 'widgets/populartags', {
tags: tags,
display,
template: widget.templateData.template,
Expand All @@ -351,7 +360,7 @@ Widget.renderPopularTopics = async function (widget) {
term: widget.data.duration || 'alltime',
sort: 'posts',
});
widget.html = await app.renderAsync('widgets/populartopics', {
widget.html = await renderWidget(widget, 'widgets/populartopics', {
topics: data.topics,
numTopics: numTopics,
relative_path: nconf.get('relative_path'),
Expand All @@ -370,7 +379,7 @@ Widget.renderTopTopics = async function (widget) {
term: widget.data.duration || 'alltime',
sort: 'votes',
});
widget.html = await app.renderAsync('widgets/toptopics', {
widget.html = await renderWidget(widget, 'widgets/toptopics', {
topics: data.topics,
numTopics: numTopics,
relative_path: nconf.get('relative_path'),
Expand All @@ -385,7 +394,7 @@ Widget.renderMyGroups = async function (widget) {
const groupsData = await groups.getUserGroups([uid]);
let userGroupData = groupsData.length ? groupsData[0] : [];
userGroupData = userGroupData.slice(0, numGroups);
widget.html = await app.renderAsync('widgets/groups', {
widget.html = await renderWidget(widget, 'widgets/groups', {
groups: userGroupData,
relative_path: nconf.get('relative_path'),
});
Expand All @@ -395,15 +404,15 @@ Widget.renderMyGroups = async function (widget) {
Widget.renderGroupPosts = async function (widget) {
const numPosts = parseInt(widget.data.numPosts, 10) || 4;
const postsData = await groups.getLatestMemberPosts(widget.data.groupName, numPosts, widget.uid);
widget.html = await app.renderAsync('widgets/groupposts', { posts: postsData });
widget.html = await renderWidget(widget, 'widgets/groupposts', { posts: postsData });
return widget;
};

Widget.renderNewGroups = async function (widget) {
const numGroups = parseInt(widget.data.numGroups, 10) || 8;
const groupNames = await db.getSortedSetRevRange('groups:visible:createtime', 0, numGroups - 1);
const groupsData = await groups.getGroupsData(groupNames);
widget.html = await app.renderAsync('widgets/groups', {
widget.html = await renderWidget(widget, 'widgets/groups', {
groups: groupsData.filter(Boolean),
relative_path: nconf.get('relative_path'),
});
Expand Down Expand Up @@ -441,7 +450,7 @@ Widget.renderSuggestedTopics = async function (widget) {
}


widget.html = await app.renderAsync('widgets/suggestedtopics', {
widget.html = await renderWidget(widget, 'widgets/suggestedtopics', {
topics: topicData,
config: widget.templateData.config,
sidebar: sidebarLocations.includes(widget.location),
Expand Down Expand Up @@ -480,7 +489,7 @@ Widget.renderUserPost = async function (widget) {
if (!postObjs.length) {
return null;
}
widget.html = await app.renderAsync('widgets/userpost', {
widget.html = await renderWidget(widget, 'widgets/userpost', {
posts: postObjs,
config: widget.templateData.config,
relative_path: nconf.get('relative_path'),
Expand Down Expand Up @@ -512,7 +521,7 @@ Widget.renderChatRoom = async function (widget) {
}
});

widget.html = await app.renderAsync('widgets/chat', {
widget.html = await renderWidget(widget, 'widgets/chat', {
roomId: roomId,
isWidget: true,
...roomData,
Expand Down Expand Up @@ -668,9 +677,6 @@ Widget.defineWidgets = async function (widgets) {
const groupNames = await db.getSortedSetRevRange('groups:visible:createtime', 0, -1);
let groupsData = await groups.getGroupsData(groupNames);
groupsData = groupsData.filter(Boolean);
groupsData.forEach((group) => {
group.name = validator.escape(String(group.name));
});

const html = await app.renderAsync('admin/partials/widgets/groupposts', { groups: groupsData });
widgets.push({
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"readmeFilename": "README.md",
"_from": "nodebb-widget-essentials@~0.0.1",
"nbbpm": {
"compatibility": "^4.12.0"
"compatibility": "^4.14.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
Expand Down
2 changes: 1 addition & 1 deletion public/templates/widgets/activeusers.tpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="{{{ if sidebar }}}row row-cols-1 px-3 gy-2{{{ else }}}row row-cols-2 row-cols-md-3 row-cols-lg-4 row-cols-xl-5 g-4{{{ end }}} mb-2">
{{{ each active_users }}}
<a href="{config.relative_path}/user/{./userslug}" class="btn btn-ghost d-flex gap-2 ff-secondary align-items-start text-start p-2 ff-base">
{buildAvatar(@value, "48px", true, "flex-shrink-0")}
{{buildAvatar(@value, "48px", true, "flex-shrink-0")}}
<div class="d-flex flex-column gap-1 text-truncate">
<div class="fw-semibold text-truncate" title="{./displayname}">{./displayname}</div>
<div class="text-xs text-muted text-truncate">
Expand Down
6 changes: 3 additions & 3 deletions public/templates/widgets/categories.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
<li component="categories/category" data-cid="{./cid}" data-parent-cid="{../parentCid}" class="category-{./cid}">
<div class="content d-flex gap-2">
<div>
{buildCategoryIcon(@value, "24px", "rounded-1")}
{{buildCategoryIcon(@value, "24px", "rounded-1")}}
</div>
<div class="flex-grow-1 align-items-start d-flex flex-column gap-2">
<div class="d-grid gap-0">
<div class="title fw-semibold">
<!-- IMPORT partials/categories/link.tpl -->
</div>
{{{ if ./descriptionParsed }}}
<div class="description text-muted text-xs w-100">{./descriptionParsed}</div>
<div class="description text-muted text-xs w-100">{{./descriptionParsed}}</div>
{{{ end }}}
</div>
{{{ if !config.hideSubCategories }}}
Expand All @@ -22,7 +22,7 @@
<span class="category-children-item small">
<div class="d-flex align-items-center gap-1">
<i class="fa fa-fw fa-caret-right text-primary"></i>
<a href="{{{ if ./link }}}{./link}{{{ else }}}{config.relative_path}/category/{./slug}{{{ end }}}" class="text-reset fw-semibold">{./name}</a>
<a href="{{{ if ./link }}}{./link}{{{ else }}}{config.relative_path}/category/{./slug}{{{ end }}}" class="text-reset fw-semibold">{tx(./name)}</a>
</div>
</span>
{{{ end }}}
Expand Down
2 changes: 1 addition & 1 deletion public/templates/widgets/latestusers.tpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="{{{ if sidebar }}}row row-cols-1 px-3 gy-2{{{ else }}}row row-cols-2 row-cols-md-3 row-cols-lg-4 row-cols-xl-5 g-4{{{ end }}} mb-2">
{{{ each users }}}
<a href="{config.relative_path}/user/{./userslug}" class="btn btn-ghost d-flex gap-2 ff-secondary align-items-start text-start p-2 ff-base">
{buildAvatar(@value, "48px", true, "flex-shrink-0")}
{{buildAvatar(@value, "48px", true, "flex-shrink-0")}}
<div class="d-flex flex-column gap-1 text-truncate">
<div class="fw-semibold text-truncate" title="{./displayname}">{./displayname}</div>
<div class="text-xs text-muted text-truncate">
Expand Down
2 changes: 1 addition & 1 deletion public/templates/widgets/moderators.tpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="d-flex flex-column gap-1 mb-2">
{{{ each moderators }}}
<a href="{config.relative_path}/user/{./userslug}" class="btn btn-ghost d-flex gap-2 ff-secondary align-items-start text-start p-2 ff-base flex-grow-1">
{buildAvatar(@value, "48px", true, "flex-shrink-0")}
{{buildAvatar(@value, "48px", true, "flex-shrink-0")}}
<div class="d-flex flex-column gap-1 text-truncate">
<div class="fw-semibold text-truncate" title="{./displayname}">{./displayname}</div>
<div class="text-xs text-muted text-truncate">@{./username}</div>
Expand Down
2 changes: 1 addition & 1 deletion public/templates/widgets/onlineusers.tpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="{{{ if sidebar }}}row row-cols-1 px-3 gy-2{{{ else }}}row row-cols-2 row-cols-md-3 row-cols-lg-4 row-cols-xl-5 g-4{{{ end }}} mb-2">
{{{ each online_users }}}
<a href="{config.relative_path}/user/{./userslug}" class="btn btn-ghost d-flex gap-2 ff-secondary align-items-start text-start p-2 ff-base">
{buildAvatar(@value, "48px", true, "flex-shrink-0")}
{{buildAvatar(@value, "48px", true, "flex-shrink-0")}}
<div class="d-flex flex-column gap-1 text-truncate">
<div class="fw-semibold text-truncate" title="{./displayname}">{./displayname}</div>
<div class="text-xs text-muted text-truncate">
Expand Down
6 changes: 3 additions & 3 deletions public/templates/widgets/partials/posts.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
<li data-pid="{./pid}" class="widget-posts d-flex flex-column gap-1">
<div class="d-flex gap-2 align-items-center text-sm">
<a class="text-decoration-none avatar-tooltip" title="{./user.displayname}" href="{{{ if ./user.userslug }}}{relative_path}/user/{./user.userslug}{{{ else }}}#{{{ end }}}">
{buildAvatar(./user, "24px", true)}
{{buildAvatar(./user, "24px", true)}}
</a>

<div class="post-author d-flex align-items-center gap-1">
<a class="lh-1 fw-semibold" href="{relative_path}/user/{./user.userslug}">{../user.displayname}</a>
<a class="lh-1 fw-semibold" href="{relative_path}/user/{./user.userslug}">{./user.displayname}</a>
</div>
<span class="timeago text-muted lh-1" title="{./timestampISO}"></span>
</div>
<div class="line-clamp-6 text-sm text-break">
{./content}
{{./content}}
</div>

<div class="text-end text-xs post-preview-footer">
Expand Down
4 changes: 2 additions & 2 deletions public/templates/widgets/partials/topics.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<li class="widget-topics d-flex gap-2 flex-column">
<div class="d-flex gap-3 justify-content-between">

<a class="topic-title fw-semibold fs-6 text-reset text-break d-block" href="{relative_path}/topic/{./slug}">{./title}</a>
<a class="topic-title fw-semibold fs-6 text-reset text-break d-block" href="{relative_path}/topic/{./slug}">{{generateTopicTitle(@value)}}</a>
{{{ if ./thumbs.length }}}
<a class="topic-thumbs text-decoration-none flex-shrink-0 d-inline-block" href="{config.relative_path}/topic/{./slug}{{{ if ./bookmark }}}/{./bookmark}{{{ end }}}" aria-label="[[topic:thumb-image]]">
<img class="topic-thumb rounded-1 bg-light" style="width: auto; max-width: 5.33rem; height: 3.3rem; object-fit: contain;" src="{./thumbs.0.url}" alt=""/>
Expand All @@ -14,7 +14,7 @@
<div class="d-flex flex-column gap-2 flex-grow-1">
<div class="d-flex gap-2 align-items-center text-sm">
<a class="text-decoration-none avatar-tooltip" title="{./user.displayname}" href="{{{ if ./teaser.user.userslug }}}{relative_path}/user/{./teaser.user.userslug}{{{ else }}}#{{{ end }}}">
{buildAvatar(./teaser.user, "24px", true)}
{{buildAvatar(./teaser.user, "24px", true)}}
</a>

<div class="post-author d-flex align-items-center gap-1">
Expand Down
4 changes: 2 additions & 2 deletions public/templates/widgets/populartags.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<div data-width="{./widthPercent}" class="popular-tags-bar position-absolute bg-info opacity-50 start-0 top-0" style="transition: width 750ms ease-out; width: 0%; height:100%; z-index: 0;"></div>

<a style="background-color: transparent!important; z-index: 1;" class="d-inline-block w-100 text-decoration-none text-bg-info position-relative text-truncate align-middle" href="{{{ if template.category }}}?tag={./valueEncoded}{{{ else }}}{relative_path}/tags/{./valueEncoded}{{{ end }}}"><span class="text-nowrap tag-class-{tags.class}">{./valueEscaped}</span></a>
<a style="background-color: transparent!important; z-index: 1;" class="d-inline-block w-100 text-decoration-none text-bg-info position-relative text-truncate align-middle" href="{{{ if template.category }}}?tag={./valueEncoded}{{{ else }}}{relative_path}/tags/{./valueEncoded}{{{ end }}}"><span class="text-nowrap tag-class-{tags.class}">{./value}</span></a>
</div>

<div class="text-center fw-bold p-1 text-end w-25 tag-topic-count border rounded">{./score}</div>
Expand All @@ -19,7 +19,7 @@
{{{ each tags }}}
<div>
<a href="{config.relative_path}/tags/{./valueEncoded}" data-tag="{./valueEscaped}" class="btn btn-ghost ff-base d-flex flex-column gap-1 align-items-start justify-content-start text-truncate p-2">
<div class="fw-semibold text-nowrap tag-item w-100 text-start text-truncate">{./valueEscaped}</div>
<div class="fw-semibold text-nowrap tag-item w-100 text-start text-truncate">{./value}</div>
<div class="text-xs text-muted text-nowrap tag-topic-count">[[global:x-topics, {txEscape(formattedNumber(./score))}]]</div>
</a>
</div>
Expand Down
2 changes: 1 addition & 1 deletion public/templates/widgets/search.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
{{{ if showInControl }}}
<select name="in" class="form-select">
{{{ each inOptions }}}
<option value="{./value}" {{{ if ./selected }}} selected {{{ end }}}>{./label}</option>
<option value="{./value}" {{{ if ./selected }}} selected {{{ end }}}>{tx(./label)}</option>
{{{ end }}}
</select>
{{{ end }}}
Expand Down
2 changes: 1 addition & 1 deletion public/templates/widgets/topposters.tpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="{{{ if sidebar }}}row row-cols-1 px-3 gy-2{{{ else }}}row row-cols-2 row-cols-md-3 row-cols-lg-4 row-cols-xl-5 g-4{{{ end }}} mb-2">
{{{ each users }}}
<a href="{config.relative_path}/user/{./userslug}" class="btn btn-ghost d-flex gap-2 ff-secondary align-items-start text-start p-2 ff-base">
{buildAvatar(@value, "48px", true, "flex-shrink-0")}
{{buildAvatar(@value, "48px", true, "flex-shrink-0")}}
<div class="d-flex flex-column gap-1 text-truncate">
<div class="fw-semibold text-truncate" title="{./displayname}">{./displayname}</div>
<div class="text-xs text-muted text-truncate">{formattedNumber(./postcount)}</div>
Expand Down