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
24 changes: 5 additions & 19 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,10 @@ We hold our code to standard, and these standards are documented below.

We use [prettier](https://prettier.io/) for automatic formatting a lot all our files. The configuration is in our `prettier.config.mjs` file.

To run prettier, use `node --run lint:prettier`.
And we use [ESLint](https://eslint.org) to lint our JavaScript, Markdown, and CSS files. The configuration is in our `eslint.config.mjs` file.

### JavaScript: Run ESLint

We use [ESLint](https://eslint.org) to lint our JavaScript files. The configuration is in our `eslint.config.mjs` file.

To run ESLint, use `node --run lint:js`.

### CSS: Run StyleLint

We use [StyleLint](https://stylelint.io) to lint our CSS. The configuration is in our `stylelint.config.mjs` file.

To run StyleLint, use `node --run lint:css`.

### Markdown: Run markdownlint

We use [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) to lint our markdown files. The configuration is in our `.markdownlint.json` file.

To run markdownlint, use `node --run lint:markdown`.
To check for formatting and linting errors, use `node --run test:lint`
To fix formatting and linting errors, use `node --run lint:fix`.

## Testing

Expand All @@ -43,6 +28,7 @@ The `package.json` scripts expose finer-grained test commands:
- `test:watch` – keep Vitest in watch mode for fast local feedback
- `test:ui` – open the Vitest UI dashboard (needs OS file-watch support enabled)
- `test:calendar` – run the legacy calendar debug helper
- `test:css`, `test:markdown`, `test:prettier`, `test:spelling`, `test:js` – lint-only scripts that enforce formatting, spelling, markdown style, and ESLint.
- `test:lint` – run linter and formatter checks
- `test:spelling` – run the spell checker

You can invoke any script with `node --run <script>` (or `npm run <script>`). Individual files can still be targeted directly, e.g. `npx vitest run tests/e2e/env_spec.js`.
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Hello and thank you for wanting to contribute to the MagicMirror² project!
> - What does the pull request accomplish? Use a list if needed.
> - If it includes major visual changes please add screenshots.
>
> 3. Please run `node --run lint:prettier` before submitting so that
> 3. Please run `node --run lint:fix` before submitting so that
> style issues are fixed.

**Note**: Sometimes the development moves very fast. It is highly
Expand Down
16 changes: 6 additions & 10 deletions .github/workflows/automated-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
timeout-minutes: 15
steps:
- name: "Checkout code"
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: "Use Node.js"
uses: actions/setup-node@v6
with:
Expand All @@ -33,23 +33,20 @@ jobs:
node --run install-mm:dev
- name: "Run linter tests"
run: |
node --run test:prettier
node --run test:js
node --run test:css
node --run test:markdown
node --run test:lint
test:
runs-on: ubuntu-24.04
timeout-minutes: 30
strategy:
matrix:
node-version: [22.x, 24.x, 25.x]
node-version: [22.x, 24.x, 26.x]
steps:
- name: Install electron dependencies and labwc
run: |
sudo apt-get update
sudo apt-get install -y libnss3 libasound2t64 labwc
- name: "Checkout code"
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: "Use Node.js ${{ matrix.node-version }}"
uses: actions/setup-node@v6
with:
Expand All @@ -64,9 +61,8 @@ jobs:
npx playwright install --with-deps chromium
- name: "Prepare environment for tests"
run: |
# Fix chrome-sandbox permissions:
sudo chown root:root ./node_modules/electron/dist/chrome-sandbox
sudo chmod 4755 ./node_modules/electron/dist/chrome-sandbox
# Download electron binary
npx install-electron
# Start labwc
WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1 WLR_RENDERER=pixman labwc &
touch config/custom.css
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
language: [actions, javascript-typescript]

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: github/codeql-action/init@v4
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/dep-review.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ jobs:
runs-on: ubuntu-slim
steps:
- name: "Checkout code"
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: "Dependency Review"
uses: actions/dependency-review-action@v4
uses: actions/dependency-review-action@v5
4 changes: 2 additions & 2 deletions .github/workflows/electron-rebuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ jobs:
runs-on: ubuntu-slim
strategy:
matrix:
node-version: [22.x, 24.x, 25.x]
node-version: [22.x, 24.x, 26.x]
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: "Use Node.js ${{ matrix.node-version }}"
uses: actions/setup-node@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-notes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
timeout-minutes: 15
steps:
- name: "Checkout code"
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: "0"
- name: "Use Node.js"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/spellcheck.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-slim
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
ref: develop
- name: Set up Node.js
Expand Down
6 changes: 0 additions & 6 deletions .markdownlint.json

This file was deleted.

17 changes: 9 additions & 8 deletions cspell.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
"armv",
"ashishtank",
"autoplay",
"Autorestart",
"avghumidity",
"avgtemp",
"Autorestart",
"beada",
"Behaviour",
"Beschreibung",
Expand All @@ -32,6 +32,7 @@
"btoconnor",
"bughaver",
"bugsounet",
"buienradar",
"buxxi",
"byday",
"calcage",
Expand Down Expand Up @@ -183,8 +184,8 @@
"Lightspeed",
"loadingcircle",
"locationforecast",
"logg",
"lockstring",
"logg",
"lstrip",
"Luciella",
"luxon",
Expand All @@ -201,8 +202,8 @@
"Meteo",
"michaelteeuw",
"michmich",
"mintemp",
"Midori",
"mintemp",
"mirontoli",
"MISSINGLANG",
"mixasgr",
Expand All @@ -220,8 +221,8 @@
"NEWSFEED",
"newsfeedfetcher",
"newsfetcher",
"newyear",
"newsitems",
"newyear",
"nextdaysrelative",
"nfogal",
"njwilliams",
Expand Down Expand Up @@ -256,12 +257,13 @@
"pubdate",
"radokristof",
"rajniszp",
"RDATE",
"rebuilded",
"Reis",
"rejas",
"relativehumidity",
"resultstring",
"Resig",
"resultstring",
"roboto",
"rohitdharavath",
"Rosso",
Expand Down Expand Up @@ -290,7 +292,6 @@
"sthuber",
"Stieber",
"strinner",
"stylelintrc",
"sunaction",
"suncalc",
"suntimes",
Expand All @@ -302,8 +303,8 @@
"Teil",
"TESTMODE",
"testpass",
"testuser",
"teststring",
"testuser",
"thomasrockhu",
"thumbslider",
"timeformat",
Expand All @@ -324,12 +325,12 @@
"updatenotification",
"uxdt",
"Vaice",
"VCALENDAR",
"veeck",
"verjaardag",
"VEVENT",
"vgtu",
"Vitest",
"VCALENDAR",
"Voelt",
"Vorberechnung",
"vppencilsharpener",
Expand Down
15 changes: 14 additions & 1 deletion defaultmodules/calendar/calendarfetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,21 @@ class CalendarFetcher {
*/
async #handleResponse (response) {
try {
// 304 Not Modified has no body: keep previously fetched events and just re-broadcast them.
if (response.status === 304) {
this.lastFetch = Date.now();
this.broadcastEvents();
return;
}

const responseData = await response.text();
const parsed = ical.parseICS(responseData);

const filteredData = await CalendarFetcherUtils.preFilterICS(responseData, {
includePastEvents: this.includePastEvents,
maximumNumberOfDays: this.maximumNumberOfDays
});

const parsed = await ical.async.parseICS(filteredData);

Log.debug(`Parsed iCal data from ${this.url} with ${Object.keys(parsed).length} entries.`);

Expand Down
30 changes: 30 additions & 0 deletions defaultmodules/calendar/calendarfetcherutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,36 @@ const CalendarFetcherUtils = {
return moment.tz.guess();
},

/**
* Calculate the time window of events to keep, as [start, end].
* Without includePastEvents the window starts now; otherwise it also
* reaches maximumNumberOfDays into the past.
* @param {object} config Needs includePastEvents (boolean) and maximumNumberOfDays (number).
* @returns {[Date, Date]} The start and end of the window.
*/
calculateFilterWindow (config) {
const today = moment().startOf("day");
const start = config.includePastEvents
? today.clone().subtract(config.maximumNumberOfDays, "days").toDate()
: new Date();
const end = today.clone().add(config.maximumNumberOfDays, "days").toDate();
return [start, end];
},

/**
* Drop ICS data outside the configured time window before it is parsed,
* so that node-ical only has to process events we might actually show.
* @param {string} rawICS The raw ICS text.
* @param {object} config Needs includePastEvents (boolean) and maximumNumberOfDays (number).
* @returns {Promise<string>} The filtered ICS text.
*/
async preFilterICS (rawICS, config) {
// ics-filter is ESM-only, so we import it dynamically from this CommonJS file.
const { icsFilter } = await import("ics-filter");
const [start, end] = CalendarFetcherUtils.calculateFilterWindow(config);
return icsFilter(rawICS, start, end);
},

/**
* Filter the events from ical according to the given config
* @param {object} data the calendar data from ical
Expand Down
2 changes: 1 addition & 1 deletion defaultmodules/calendar/calendarutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const CalendarUtils = {
let replacement = transform.replace;
if (typeof transform.yearmatchgroup !== "undefined" && transform.yearmatchgroup !== "") {
const yearmatch = [...title.matchAll(needle)];
if (yearmatch[0].length >= transform.yearmatchgroup + 1 && yearmatch[0][transform.yearmatchgroup] * 1 >= 1900) {
if (yearmatch.length > 0 && yearmatch[0].length >= transform.yearmatchgroup + 1 && yearmatch[0][transform.yearmatchgroup] * 1 >= 1900) {
let calcage = new Date().getFullYear() - yearmatch[0][transform.yearmatchgroup] * 1;
let searchstr = `$${transform.yearmatchgroup}`;
replacement = replacement.replace(searchstr, calcage);
Expand Down
2 changes: 1 addition & 1 deletion defaultmodules/compliments/compliments.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ Module.register("compliments", {
timeOfDay = "evening";
}

if (timeOfDay && this.config.compliments.hasOwnProperty(timeOfDay)) {
if (this.config.compliments.hasOwnProperty(timeOfDay)) {
compliments = [...this.config.compliments[timeOfDay]];
}

Expand Down
5 changes: 3 additions & 2 deletions defaultmodules/newsfeed/newsfeed.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ Module.register("newsfeed", {
prohibitedWords: [],
scrollLength: 500,
logFeedWarnings: false,
dangerouslyDisableAutoEscaping: false
dangerouslyDisableAutoEscaping: false,
allowedBasicHtmlTags: []
},

getUrlPrefix (item) {
Expand Down Expand Up @@ -148,7 +149,7 @@ Module.register("newsfeed", {
}
return Promise.resolve(wrapper);
}
return this._super();
return Module.prototype.getDom.call(this);
},

//Override fetching of template name
Expand Down
12 changes: 6 additions & 6 deletions defaultmodules/newsfeed/newsfeed.njk
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@
{% if config.showPublishDate %}{{ item.publishDate }}:{% endif %}
</div>
{% endif %}
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">{{ escapeTitle(item.title, item.url, config.dangerouslyDisableAutoEscaping, config.showTitleAsUrl) }}</div>
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">{{ escapeTitle(item.title, item.url, config.dangerouslyDisableAutoEscaping or (config.allowedBasicHtmlTags | length), config.showTitleAsUrl) }}</div>
{% if config.showDescription %}
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
{% if config.truncDescription %}
{{ escapeText(item.description | truncate(config.lengthDescription) , config.dangerouslyDisableAutoEscaping) }}
{{ escapeText(item.description | truncate(config.lengthDescription) , config.dangerouslyDisableAutoEscaping or (config.allowedBasicHtmlTags | length)) }}
{% else %}
{{ escapeText(item.description, config.dangerouslyDisableAutoEscaping) }}
{{ escapeText(item.description, config.dangerouslyDisableAutoEscaping or (config.allowedBasicHtmlTags | length)) }}
{% endif %}
</div>
{% endif %}
Expand All @@ -68,13 +68,13 @@
{% if config.showPublishDate %}{{ publishDate }}:{% endif %}
</div>
{% endif %}
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">{{ escapeTitle(title, url, config.dangerouslyDisableAutoEscaping, config.showTitleAsUrl) }}</div>
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">{{ escapeTitle(title, url, config.dangerouslyDisableAutoEscaping or (config.allowedBasicHtmlTags | length), config.showTitleAsUrl) }}</div>
{% if config.showDescription %}
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
{% if config.truncDescription %}
{{ escapeText(description | truncate(config.lengthDescription) , config.dangerouslyDisableAutoEscaping) }}
{{ escapeText(description | truncate(config.lengthDescription) , config.dangerouslyDisableAutoEscaping or (config.allowedBasicHtmlTags | length)) }}
{% else %}
{{ escapeText(description, config.dangerouslyDisableAutoEscaping) }}
{{ escapeText(description, config.dangerouslyDisableAutoEscaping or (config.allowedBasicHtmlTags | length)) }}
{% endif %}
</div>
{% endif %}
Expand Down
Loading
Loading