Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
55d809f
fix: warn when atomize blocks missing from highlight template
TyceHerrman Mar 19, 2026
342971e
Merge upstream/beta into fix/warn-atomize-blocks-missing
TyceHerrman Jun 1, 2026
f68a0f0
Merge pull request #99 from jsonMartin/master
johannrichard Jun 16, 2026
88665bb
chore: 🔖 set `package.json`, `manifest.json` and `package-lock.json` …
semantic-release-bot Jun 16, 2026
93acde3
Merge branch 'master' into beta
johannrichard Jun 16, 2026
8cb402a
fix: update manifest.json
johannrichard Jun 16, 2026
d15d150
chore: 🔖 set `package.json`, `manifest.json` and `package-lock.json` …
semantic-release-bot Jun 16, 2026
6bc1bcc
fix: 🚑️ fix empty frontmatter regression
johannrichard Jun 19, 2026
60ddf92
chore: 🔖 set `package.json`, `manifest.json` and `package-lock.json` …
semantic-release-bot Jun 19, 2026
cd74878
fix: 🚑️ fix settings not being written to disc anymore
johannrichard Jun 21, 2026
298ef32
fix: 🐛 fix base folder name reset
johannrichard Jun 21, 2026
d7c930e
chore: 🔖 set `package.json`, `manifest.json` and `package-lock.json` …
semantic-release-bot Jun 21, 2026
4d2ad03
fix: 🩹 improve load sequence to avoid stale context
johannrichard Jun 21, 2026
75a5595
fix: 🩹 update synced date on `reset-last-updated`
johannrichard Jun 21, 2026
db80f9b
chore: 🔖 set `package.json`, `manifest.json` and `package-lock.json` …
semantic-release-bot Jun 21, 2026
78aae75
Merge remote-tracking branch 'upstream/beta' into fix/warn-atomize-bl…
TyceHerrman Jun 26, 2026
cdff162
ci: 👷 simplify manifest sync after semantic-release
johannrichard Jun 27, 2026
02caa47
Merge pull request #90 from TyceHerrman/fix/warn-atomize-blocks-missing
johannrichard Jun 28, 2026
b47086a
fix: fix attestation path in .github/workflows/release.yml
johannrichard Jun 28, 2026
995277a
fix: bug: fix .github/workflows/release.yml
johannrichard Jun 28, 2026
ca5f45d
fix: :adhesive-bandage: normalise whitespace-only folder names before…
johannrichard Jun 28, 2026
0d4c2df
build: 🩹 fix settings
johannrichard Jun 28, 2026
0fb6a15
fix: 🚑️ fix regression with pagination (string → number)
johannrichard Jul 11, 2026
b4bea3f
fix: 🐛 use unfiltered tags for created and updated, and filtered high…
johannrichard Jul 11, 2026
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
28 changes: 26 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Release plugin version
on:
on:
workflow_dispatch:
push:
branches: [master, main, beta]
Expand Down Expand Up @@ -31,10 +31,34 @@ jobs:
run: |
npm audit --audit-level=high --production
npm audit signatures
continue-on-error: true # Don't fail the build, but report issues
continue-on-error: true # Don't fail the build, but report issues
- name: Build plugin
run: npm run build
- name: Release update
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx semantic-release
Comment thread
johannrichard marked this conversation as resolved.
- name: Configure Git
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
run: |
git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}`@users.noreply.github.com`"
- name: Commit manifest.json (main only)
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
run: |
if git diff --quiet -- manifest.json; then
echo "manifest.json unchanged; skipping commit"
exit 0
fi

git add manifest.json
git commit -m "chore: 🔖 sync manifest version [skip ci]"
git push
- name: Generate artifact attestation (main release)
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/attest@v4
Comment on lines +57 to +59

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the workflow around the attestation step
wc -l .github/workflows/release.yml
sed -n '1,120p' .github/workflows/release.yml

# Look for any attestation-related docs or permissions elsewhere in the repo
rg -n "attestations: write|actions/attest@v4|artifact attestation|attestation" .github README.md docs . 2>/dev/null | head -n 200

Repository: jsonMartin/readwise-mirror

Length of output: 2438


🌐 Web query:

GitHub Actions attestation action requires attestations: write permission actions/attest@v4

💡 Result:

To use the actions/attest@v4 action in a GitHub Actions workflow, you must explicitly configure specific permissions for the GITHUB_TOKEN [1][2]. The required permissions are [3][4]: permissions: id-token: write attestations: write artifact-metadata: write Explanation of required permissions: - id-token: write: Necessary for the action to mint the OIDC (OpenID Connect) token, which is required to request a Sigstore signing certificate [3][4]. - attestations: write: Required to persist the generated attestation [3][4][5]. - artifact-metadata: write: Required to create the artifact storage record [3][4]. When you define the permissions block within your job, all unspecified permissions are set to none [5]. Therefore, ensure you include any other permissions your workflow requires (such as contents: read or packages: write) alongside these three [1][2].

Citations:


Grant attestation and artifact-metadata write permissions. actions/attest@v4 also needs artifact-metadata: write alongside attestations: write, or this step can fail.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 59-59: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 57 - 59, The main release
attestation step using actions/attest@v4 is missing required write permissions.
Update the workflow permissions for the release job so the attestation step has
both attestations: write and artifact-metadata: write, then verify the Generate
artifact attestation (main release) step can run successfully under the existing
github.event_name and github.ref condition.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== release workflow excerpt ==\n'
nl -ba .github/workflows/release.yml | sed -n '45,75p'

printf '\n== search for attest and action pinning patterns ==\n'
rg -n "actions/attest@|uses: .*@[v0-9]|uses: .*@[a-f0-9]{40}|pin" .github/workflows -S || true

printf '\n== workflow files overview ==\n'
fd -a -e yml -e yaml .github/workflows || true

Repository: jsonMartin/readwise-mirror

Length of output: 236


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== release workflow excerpt ==\n'
awk 'NR>=45 && NR<=75 { printf "%d:%s\n", NR, $0 }' .github/workflows/release.yml

printf '\n== attest usage ==\n'
rg -n "actions/attest@" .github/workflows || true

printf '\n== pinned action examples ==\n'
rg -n "uses: .*@[a-f0-9]{40}" .github/workflows || true

printf '\n== workflow files ==\n'
find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) -print

Repository: jsonMartin/readwise-mirror

Length of output: 1208


Pin actions/attest to a commit SHA. actions/attest@v4 is still a mutable tag and will trip the unpinned-action policy; replace it with the resolved SHA here.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 59-59: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 59, The workflow step using
actions/attest is still referenced by a mutable tag, which violates the
unpinned-action policy. Update the uses entry in the release workflow to the
resolved commit SHA for actions/attest instead of v4, keeping the same step
intact and only changing the action reference.

Source: Linters/SAST tools

with:
subject-path: |
main.js
manifest.json
src/ui/styles/styles.css
13 changes: 0 additions & 13 deletions .releaserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,4 @@ plugins:
- path: main.js
- path: manifest.json
- path: src/ui/styles/styles.css
# Uncomment the following block if semantic release *should* commit the
# updated `manifest.json`, `versions.json` and `package.json` files with the new version number.
- - "@semantic-release/git"
- assets:
- manifest.json
- package.json
- package-lock.json
message: >-
chore: 🔖 set `package.json`, `manifest.json` and
`package-lock.json` to ${nextRelease.version} [skip ci]


${nextRelease.notes}
tagFormat: "${version}"
23 changes: 0 additions & 23 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,3 @@
{
"chat.tools.terminal.autoApprove": {
"/^obsidian vault=\"Obsidian Sandbox\" plugin:reload id=readwise-mirror$/": {
"approve": true,
"matchCommandLine": true
},
"/^obsidian vault=\"Obsidian Sandbox\" plugin id=readwise-mirror$/": {
"approve": true,
"matchCommandLine": true
},
"/^obsidian vault=\"Obsidian Sandbox\" commands filter=readwise$/": {
"approve": true,
"matchCommandLine": true
},
"/^obsidian vault=\"Obsidian Sandbox\" command id=readwise-mirror:reset-last-updated$/": {
"approve": true,
"matchCommandLine": true
},
"/^obsidian vault=\"Obsidian Sandbox\" command id=readwise-mirror:update$/": {
"approve": true,
"matchCommandLine": true
}
},
"jest.configPath": "jest.config.js"
}

2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "readwise-mirror",
"name": "Readwise Mirror",
"version": "2.4.1",
"version": "2.4.2-beta.4",
"minAppVersion": "1.6.6",
"description": "Mirror your Readwise library directly to your vault.",
"author": "jsonmartin, johannrichard",
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "readwise-mirror",
"version": "2.4.1",
"version": "2.4.2-beta.4",
"description": "This is a plugin for Obsidian (https://obsidian.md)",
"main": "main.js",
"scripts": {
Expand Down
5 changes: 3 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ Summary: {{ summary }}

# Highlights
`,
highlightTemplate: `{{ text }}{%- if category == 'books' %} ([{{ location }}]({{ location_url }})){%- endif %}{%- if color %} %% Color: {{ color }} %%{%- endif %} ^{{id}}{%- if note %}
highlightTemplate: `{% atomize id=id, basename=id, embed=true %}
{{ text }}{%- if category == 'books' %} ([{{ location }}]({{ location_url }})){%- endif %}{%- if color %} %% Color: {{ color }} %%{%- endif %} ^{{id}}{%- if note %}

Note: {{ note }}
{%- endif %}{%- if tags %}
Expand All @@ -73,6 +74,7 @@ Tags: {{ tags }}
{%- endif %}

---
{% endatomize %}
`,
useSlugify: false,
slugifySeparator: '-',
Expand All @@ -96,7 +98,6 @@ Tags: {{ tags }}
};

export const FRONTMATTER_TO_ESCAPE = ['title', 'sanitized_title', 'author', 'authorStr'];
export const EMPTY_FRONTMATTER: string = '---\n---\n';

// Core Template
export const NUNJUCKS_CORE_TEMPLATE = `{%- block header -%}
Expand Down
7 changes: 5 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ export default class ReadwiseMirror extends Plugin {
// exposed methods
notice: (message: string, duration?: number) => this.notify.notice(message, duration),
setStatusBarText: (message: string) => this.notify.setStatusBarText(message),
saveAndApplySettings: () => this.saveAndApplySettings(),
saveAndApplySettings: () => {
this.settings = ctx.settings;
return this.saveAndApplySettings();
},
Comment thread
johannrichard marked this conversation as resolved.
};
return ctx;
}
Expand All @@ -103,8 +106,8 @@ export default class ReadwiseMirror extends Plugin {

private async initializeUI() {
try {
this.addSettingTab(new ReadwiseMirrorSettingTab(this, this.ctx, this.env));
await this.loadAndApplySettings();
this.addSettingTab(new ReadwiseMirrorSettingTab(this, this.ctx, this.env));
this.logger.debug('Readwise Mirror plugin loaded.');

// Instantiate controller and attach to context
Expand Down
5 changes: 3 additions & 2 deletions src/services/atomizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export class AtomizeExtension implements nunjucks.Extension {

switch (this.pass) {
case 'FIRST': {
return new nunjucks.runtime.SafeString(`%%! atomize id=${_id}, basename="${basename.replace(/^\n+|\n+$/g, '').trim()}", embed=${embed} !%%
return new nunjucks.runtime.SafeString(`%%! atomize id=${_id}, basename="${String(basename ?? _id).replace(/^\n+|\n+$/g, '').trim()}", embed=${embed} !%%
%%! frontmatter !%%
${frontmatter}
%%! endfrontmatter !%%
Expand All @@ -163,7 +163,8 @@ ${content}
}

// Sanitize filename
const _basename = filenamify(basename.replace(/^\n+|\n+$/g, '').trim() ?? _id.toString(), {
const rawBasename = (basename ?? _id.toString());
const _basename = filenamify(String(rawBasename).replace(/^\n+|\n+$/g, '').trim() || _id.toString(), {
replacement: '-',
maxLength: 252,
})
Expand Down
25 changes: 19 additions & 6 deletions src/services/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Library } from 'types/library';
import type { TTrackedFile } from 'types/readwise-note';
import { getTrackingUrl, isFileInFolder, normalizeFilename } from 'utils/file-utils';
import { humanReadableFormat } from 'utils/format-utils';
import { hasAtomizeBlocks } from 'utils/template-utils';
import { isInReadwiseLibrary, isTrackedReadwiseNote } from 'utils/tracking-utils';
import type ReadwiseMirror from '../main';
import type { PluginContext } from '../types/plugin-context';
Expand Down Expand Up @@ -58,6 +59,21 @@ export class Controller {
}
}

private prepareAtomicHighlightsCategory(library: Library): void {
if (!this.ctx.settings.atomicHighlights) {
return;
}

library.categories.add('Highlight');

if (!hasAtomizeBlocks(this.ctx.settings.highlightTemplate)) {
this.ctx.notice(
'Readwise: Atomic highlights enabled but your highlight template has no atomize blocks. No atomic notes will be created.',
10000
);
}
}

public async sync() {
// Equivalent to plugin.sync()
if (this.ctx.syncLock?.isAcquired('library-sync')) {
Expand Down Expand Up @@ -87,6 +103,7 @@ export class Controller {
library = await this.api.downloadUpdates(this.ctx.settings.lastUpdated);
}
// ...existing filtering and writing logic...
this.prepareAtomicHighlightsCategory(library);
await this.plugin.writeLibraryToMarkdown(library);
if (this.ctx.settings.logFile) await this.plugin.writeLogToMarkdown(library);
this.ctx.settings.lastUpdated = new Date().toISOString();
Expand Down Expand Up @@ -149,9 +166,7 @@ export class Controller {
this.ctx.logger.debug(`Readwise: downloading current book with ID ${trackedFile.readwiseId}...`);
const library = await this.api.downloadSingleBook(trackedFile.readwiseId);
if (Object.keys(library.books).length > 0) {
if (this.ctx.settings.atomicHighlights) {
library.categories.add('Highlight');
}
this.prepareAtomicHighlightsCategory(library);
await this.plugin.writeLibraryToMarkdown(library);

if (this.ctx.settings.logFile) await this.plugin.writeLogToMarkdown(library);
Expand Down Expand Up @@ -379,9 +394,7 @@ export class Controller {
try {
const library = await this.api.downloadMultipleBooks(bookIds);
if (Object.keys(library.books).length > 0) {
if (this.ctx.settings.atomicHighlights) {
library.categories.add('Highlight');
}
this.prepareAtomicHighlightsCategory(library);

if (this.ctx.settings.syncNotifications)
this.ctx.notice(`Readwise: writing ${Object.keys(library.books).length} updated books to markdown...`);
Expand Down
13 changes: 8 additions & 5 deletions src/services/frontmatter-manager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type FileManager, parseYaml, type TFile } from 'obsidian';
import { Frontmatter, type FrontmatterData, FrontmatterError } from 'services/frontmatter';
import { renderFrontmatterTemplate } from 'services/template-rendering';
import { EMPTY_FRONTMATTER, READWISE_URI_FIELD } from 'src/constants';
import { READWISE_URI_FIELD } from 'src/constants';
import type { AtomicFile, BaseFile, ReadwiseDocument } from 'types/document';
import type { PluginContext } from 'types/plugin-context';
import type { ReadwiseEnvironment } from './readwise-environment';
Expand Down Expand Up @@ -100,18 +100,21 @@ export class FrontmatterManager {
* @returns The frontmatter record
*/
public getBaseFrontmatter(metadata: ReadwiseDocument): Frontmatter {
// Render a template if frontmatter is managed or file tracking is set
if (!this.settings.frontMatter && !this.settings.trackFiles) {
// Frontmatter parsing is only needed when frontmatter output is enabled.
// Tracking fields are injected later in getFrontmatter.
if (!this.settings.frontMatter) {
return new Frontmatter();
}
try {
// Get frontmatter template string
// Add Sync properties
const frontmatterTemplate = this.settings.frontMatter ? this.settings.frontMatterTemplate : EMPTY_FRONTMATTER;
const frontmatterTemplate = this.settings.frontMatterTemplate;
this.logger.debug(`Processing merged frontmatter template\n${frontmatterTemplate}`);

// Render and parse the template into YAML
const renderedTemplate = renderFrontmatterTemplate(frontmatterTemplate, this.env, metadata);
if (!renderedTemplate.trim()) {
return new Frontmatter();
}

const yaml: unknown = parseYaml(renderedTemplate);
if (typeof yaml !== 'object' || yaml === null) {
Expand Down
10 changes: 5 additions & 5 deletions src/services/readwise-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export default class ReadwiseApi {
): Promise<Export[]> {
const url = `${API_ENDPOINT}/${contentType}?`;
let data: Record<string, unknown> | undefined;
let nextPageCursor: string | undefined;
let nextPageCursor: number | undefined;
let rateLimitRetries = 0;

const results: Export[] = [];
Expand All @@ -138,8 +138,8 @@ export default class ReadwiseApi {
if (bookId && bookId.length > 0) {
queryParams.append('ids', bookId.join(','));
}
if (nextPageCursor) {
queryParams.append('pageCursor', nextPageCursor);
if (nextPageCursor !== undefined) {
queryParams.append('pageCursor', nextPageCursor.toString());
}
if (contentType === 'export' && includeDeleted) {
queryParams.append('includeDeleted', 'true');
Expand Down Expand Up @@ -196,8 +196,8 @@ export default class ReadwiseApi {
this.ctx.logger.warn('No results found in the response data.');
}
const rawNextPageCursor = pageData.nextPageCursor;
nextPageCursor = typeof rawNextPageCursor === 'string' ? rawNextPageCursor : '';
if (!nextPageCursor) {
nextPageCursor = typeof rawNextPageCursor === 'number' ? rawNextPageCursor : undefined;
if (nextPageCursor === undefined) {
break;
}
this.ctx.logger.debug(`There are more records left, proceeding to next page: ${nextPageCursor}`);
Expand Down
4 changes: 2 additions & 2 deletions src/services/readwise-document-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export function buildReadwiseDocument(
? `[[${author}]]`
: '';

const created = createdDate(filteredHighlights);
const updated = updatedDate(filteredHighlights);
const created = createdDate(highlights);
const updated = updatedDate(highlights);
const lastHighlightAt = lastHighlightedDate(filteredHighlights);

return {
Expand Down
27 changes: 25 additions & 2 deletions src/ui/settings-tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { PluginContext } from 'types/plugin-context';
import type { TemplateValidationResult } from 'types/utilities';
import { WarningDialog } from 'ui/dialog';
import { sanitizeFrontmatterTemplate, validateFrontmatterTemplate } from 'utils/frontmatter-utils';
import { hasAtomizeBlocks } from 'utils/template-utils';

interface SettingsTab {
id: string;
Expand Down Expand Up @@ -556,8 +557,8 @@ export default class ReadwiseMirrorSettingTab extends PluginSettingTab {
.setPlaceholder('Readwise')
.setValue(this.ctx.settings.baseFolderName)
.onChange(async (value) => {
if (!value) return;
this.ctx.settings.baseFolderName = value;
const normalized = value.trim();
this.ctx.settings.baseFolderName = normalized || DEFAULT_SETTINGS.baseFolderName;
await this.ctx.saveAndApplySettings();
})
);
Expand Down Expand Up @@ -641,6 +642,26 @@ export default class ReadwiseMirrorSettingTab extends PluginSettingTab {
attr: { style: 'color: var(--text-error);' },
});
}

if (this.ctx.settings.atomicHighlights && !hasAtomizeBlocks(this.ctx.settings.highlightTemplate)) {
fragment.createEl('br');
fragment.createEl('br');
const warningSpan = fragment.createSpan({
attr: { style: 'color: var(--text-warning);' },
});
warningSpan.appendText(
'Your highlight template does not contain atomize blocks. Atomic highlights will not be created until you add '
);
warningSpan.createEl('code', { text: '{% atomize %}...{% endatomize %}' });
warningSpan.appendText(' blocks. See the ');
warningSpan
.createEl('a', {
text: 'Wiki',
href: 'https://github.com/jsonMartin/readwise-mirror/wiki/Guide:-Atomic-highlights',
})
.setAttr('target', '_blank');
warningSpan.appendText(' for details.');
}
})
)
.addToggle((toggle) => {
Expand Down Expand Up @@ -676,6 +697,7 @@ export default class ReadwiseMirrorSettingTab extends PluginSettingTab {
void (async () => {
this.ctx.settings.atomicHighlights = true;
await this.ctx.saveAndApplySettings();
this.display();
})();
} else {
toggle.setValue(false);
Expand All @@ -685,6 +707,7 @@ export default class ReadwiseMirrorSettingTab extends PluginSettingTab {
} else {
this.ctx.settings.atomicHighlights = false;
await this.ctx.saveAndApplySettings();
this.display();
}
});
}
Expand Down
2 changes: 2 additions & 0 deletions src/utils/plugin-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Command } from 'obsidian';
import spacetime from 'spacetime';
import { Controller } from '../services/controller';
import type { PluginContext } from '../types/plugin-context';
import { humanReadableFormat } from './format-utils';

function toErrorMessage(err: unknown): string {
if (err instanceof Error) return err.message;
Expand Down Expand Up @@ -114,6 +115,7 @@ export function getPluginCommands(ctr: Controller, ctx: PluginContext): Command[
void ctx
.saveAndApplySettings()
.catch((err: unknown) => ctx.notice(`Failed to save settings: ${toErrorMessage(err)}`));
ctx.setStatusBarText(`Readwise: Synced ${humanReadableFormat(ctx.settings.lastUpdated)}`);
}
return true;
}
Expand Down
Loading
Loading