Skip to content

Commit b7d40c5

Browse files
authored
🔀 Merge pull request #1871 from alayham/feature/custom-list-widget
Added the custom list widget
2 parents 2576b54 + 9ee4d24 commit b7d40c5

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed

docs/widgets.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,155 @@ In other words: Private, noncomercial, moderate use of the API is tolerated. The
12981298

12991299
---
13001300

1301+
### Custom List
1302+
1303+
Renders custom schema-compliant JOSN in a list.
1304+
1305+
#### Options
1306+
**Field** | **Type** | **Required** | **Description**
1307+
--- | --- | --- | ---
1308+
**`url`** | `text` | Required | A string containing the url of a json file.
1309+
**`title`** | `text` | optional | A title for the widget. Can be helpful if stacking multiple lists in the same section.
1310+
**`daysForNew`** | `number` | Optional | Used to highlight new items.
1311+
1312+
#### Json Schema
1313+
The input file should comply with the following schema:
1314+
```json
1315+
{
1316+
"$schema": "http://json-schema.org/draft-04/schema#",
1317+
"type": "array",
1318+
"items": [
1319+
{
1320+
"type": "object",
1321+
"properties": {
1322+
"link": {
1323+
"type": "object",
1324+
"properties": {
1325+
"text": {
1326+
"type": "string"
1327+
},
1328+
"url": {
1329+
"type": "string"
1330+
},
1331+
"title": {
1332+
"type": "string"
1333+
}
1334+
},
1335+
"required": [
1336+
"text",
1337+
"url",
1338+
"title"
1339+
]
1340+
},
1341+
"value": {
1342+
"type": "object",
1343+
"properties": {
1344+
"text": {
1345+
"type": "string"
1346+
},
1347+
"title": {
1348+
"type": "string"
1349+
}
1350+
},
1351+
"required": [
1352+
"text",
1353+
"title"
1354+
]
1355+
},
1356+
"date": {
1357+
"type": "string"
1358+
}
1359+
},
1360+
"required": [
1361+
"link",
1362+
"value",
1363+
"date"
1364+
]
1365+
}
1366+
]
1367+
}
1368+
```
1369+
1370+
Example: This json data was generated by a data worflow that gets the new releases of a few projects from GitHub. The system used to build the data workflow is n8n.
1371+
1372+
```json
1373+
[
1374+
{
1375+
"link": {
1376+
"text": "jellyfin/jellyfin",
1377+
"url": "https://github.com/jellyfin/jellyfin/releases/tag/v10.10.7",
1378+
"title": ""
1379+
},
1380+
"value": {
1381+
"text": "v10.10.7",
1382+
"title": "2025-04-05"
1383+
},
1384+
"date": "2025-04-05T19:14:59Z"
1385+
},
1386+
{
1387+
"link": {
1388+
"text": "jellyfin/jellyfin-web",
1389+
"url": "https://github.com/jellyfin/jellyfin-web/releases/tag/v10.10.7",
1390+
"title": ""
1391+
},
1392+
"value": {
1393+
"text": "v10.10.7",
1394+
"title": "2025-04-05"
1395+
},
1396+
"date": "2025-04-05T19:15:00Z"
1397+
},
1398+
{
1399+
"link": {
1400+
"text": "lissy93/dashy",
1401+
"url": "https://github.com/Lissy93/dashy/releases/tag/3.1.1",
1402+
"title": ""
1403+
},
1404+
"value": {
1405+
"text": "3.1.1",
1406+
"title": "2024-05-30"
1407+
},
1408+
"date": "2024-05-30T17:20:53Z"
1409+
},
1410+
{
1411+
"link": {
1412+
"text": "VSCodium/vscodium",
1413+
"url": "https://github.com/VSCodium/vscodium/releases/tag/1.102.14746",
1414+
"title": ""
1415+
},
1416+
"value": {
1417+
"text": "1.102.14746",
1418+
"title": "2025-07-16"
1419+
},
1420+
"date": "2025-07-16T18:27:49Z"
1421+
}
1422+
]
1423+
```
1424+
#### Notes
1425+
- This widget is designed to render data generated by another system that complies with the schema. The example JSON data above was generated using a n8n workflow, and other ETL or workflow systems can generate similar results.
1426+
- To avoid requests to a different system in each refresh, you can save the input files locally in the user-data folder inside your Dashy installation.
1427+
- To use json files from a different domain, remember to add `useProxy: true` to the widget configuration. I have not tested this use case because I save all my input data locally on the Dashy server. Please open a ticket if you have an issue in this use case.
1428+
1429+
#### Example
1430+
1431+
This widget renders a json file that from a `json-data` directory inside the `user-data` directory on the Dashy server.
1432+
```yaml
1433+
- type: custom-list
1434+
options:
1435+
url: /json-data/github-releases.json
1436+
title: 'Github Releases'
1437+
daysForNew: 5
1438+
```
1439+
1440+
#### Info
1441+
1442+
- **CORS**: 🟢 Not needed for files hosted inside the `user-data` directory. Use `useProxy: true` to bypass CORS restrictions when using data from a different server.
1443+
- **Auth**: 🟢 Not Required
1444+
- **Price**: 🟢 Free
1445+
- **Host**: user defined
1446+
- **Privacy**: depends on the user defined host.
1447+
1448+
---
1449+
13011450
### Custom search
13021451

13031452
Allows web search using multiple user-defined search engines and other websites.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<template>
2+
<div class="custom-list">
3+
<div class="custom-list-title" v-if="title">
4+
{{ title }}
5+
</div>
6+
<div v-for="(item, key) in data" :key="key" class="custom-list-row">
7+
<div v-if="item.link" class="custom-list-cell">
8+
<a :href="item.link.url" :title="item.link.title" target="_blank">
9+
{{ item.link.text }}
10+
</a>
11+
</div>
12+
<div v-if="item.value" class="custom-list-cell" :title="item.value.title">
13+
{{ item.value.text }}
14+
<span v-if="item.isNew" class="custom-list-new-value"></span>
15+
</div>
16+
</div>
17+
</div>
18+
</template>
19+
20+
<script>
21+
import WidgetMixin from '@/mixins/WidgetMixin';
22+
23+
export default {
24+
mixins: [WidgetMixin],
25+
components: {},
26+
data() {
27+
return {
28+
data: [],
29+
};
30+
},
31+
computed: {
32+
url() {
33+
return this.options.url || '';
34+
},
35+
title() {
36+
return this.options.title || '';
37+
},
38+
daysForNew() {
39+
return parseInt(Number(this.options.daysForNew)) || false;
40+
}
41+
},
42+
methods: {
43+
fetchData() {
44+
if (this.url) {
45+
this.startLoading();
46+
this.makeRequest(this.options.url).then(this.processData);
47+
}
48+
},
49+
processData(data) {
50+
let today = new Date();
51+
this.data = data.sort((a, b) => new Date(a.date) < new Date(b.date));
52+
if (this.daysForNew) {
53+
let threshold = this.daysForNew * 1000 * 60 * 60 * 24;
54+
this.data = this.data.map((item) => {
55+
item.isNew = (today - new Date(item.date) < threshold);
56+
return item;
57+
});
58+
}
59+
this.finishLoading();
60+
},
61+
},
62+
};
63+
64+
</script>
65+
66+
<style scoped lang="scss">
67+
68+
.custom-list {
69+
.custom-list-title {
70+
outline: 2px solid transparent;
71+
border: 1px solid var(--outline-color);
72+
border-radius: var(--curve-factor);
73+
box-shadow: var(--item-shadow);
74+
color: var(--item-text-color);
75+
margin: .5rem;
76+
padding: 0.3rem;
77+
background: var(--item-background);
78+
text-align: center;
79+
80+
}
81+
.custom-list-row {
82+
display: flex;
83+
align-items: center;
84+
justify-content: space-between;
85+
color: var(--widget-text-color);
86+
font-size: 1.1rem;
87+
.custom-list-cell {
88+
display: inline-block;
89+
a {
90+
text-decoration: none;
91+
color: var(--item-text-color);
92+
}
93+
.custom-list-new-value{
94+
width: 0.8rem;
95+
height: 0.8rem;
96+
border-radius: 50%;
97+
background-color: var(--success);
98+
display: inline-block;
99+
}
100+
}
101+
&:not(:last-child) {
102+
border-bottom: 1px dashed var(--widget-text-color);
103+
}
104+
}
105+
}
106+
</style>

src/components/Widgets/WidgetBase.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const COMPAT = {
5353
'crypto-price-chart': 'CryptoPriceChart',
5454
'crypto-watch-list': 'CryptoWatchList',
5555
'custom-search': 'CustomSearch',
56+
'custom-list': 'CustomList',
5657
'cve-vulnerabilities': 'CveVulnerabilities',
5758
'domain-monitor': 'DomainMonitor',
5859
'code-stats': 'CodeStats',

0 commit comments

Comments
 (0)