Merge pull request #833 from callumalpass/release-notes

Add release notes viewer
This commit is contained in:
callumalpass 2025-10-05 14:16:22 +11:00 committed by GitHub
commit 85bcc4da82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 418 additions and 5 deletions

View file

@ -71,7 +71,11 @@ jobs:
- name: Run linter
run: npm run lint || echo "Linting completed with warnings"
timeout-minutes: 10
- name: Generate release notes import
run: node generate-release-notes-import.mjs
timeout-minutes: 1
- name: Run type check
run: npm run typecheck
timeout-minutes: 5

3
.gitignore vendored
View file

@ -16,6 +16,9 @@ main.js
# It's built from source files in styles/ directory during CI/build process.
styles.css
# Don't include the auto-generated release notes import file
src/releaseNotes.ts
# Exclude sourcemaps
*.map

View file

@ -25,6 +25,17 @@ Example:
-->
## Added
- Added release notes viewer
- New view displays release notes in a dedicated tab when plugin updates
- Automatically opens after updating to a new version
- Access anytime via command palette "TaskNotes: View release notes" or Settings > General
- Release notes are rendered using native Obsidian markdown rendering
- Includes link to view all releases on GitHub
- Fully internationalized with i18n support
- Auto-generation system ensures correct version is loaded without manual updates
## Fixed
- (#440) Fixed inline task replacement not working for links with relative paths

View file

@ -1,6 +1,7 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
import { readFileSync } from "fs";
const banner =
`/*
@ -11,6 +12,20 @@ if you want to view the source, please visit the github repository of this plugi
const prod = (process.argv[2] === "production");
// Plugin to import markdown files as strings
const markdownPlugin = {
name: 'markdown',
setup(build) {
build.onLoad({ filter: /\.md$/ }, async (args) => {
const text = readFileSync(args.path, 'utf8');
return {
contents: `export default ${JSON.stringify(text)}`,
loader: 'js',
};
});
}
};
const context = await esbuild.context({
banner: {
js: banner,
@ -39,6 +54,7 @@ const context = await esbuild.context({
treeShaking: true,
outfile: "main.js",
minify: prod,
plugins: [markdownPlugin],
});
if (prod) {

View file

@ -0,0 +1,20 @@
import { readFileSync, writeFileSync } from "fs";
// Read current version from manifest.json
const manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
const version = manifest.version;
// Generate the TypeScript file that imports the current version's release notes
const content = `// Auto-generated file - do not edit manually
// This file is regenerated during the build process to import the current version's release notes
import releaseNotes from "../docs/releases/${version}.md";
export const CURRENT_VERSION = "${version}";
export const CURRENT_RELEASE_NOTES = releaseNotes;
`;
// Write to src/releaseNotes.ts
writeFileSync("src/releaseNotes.ts", content);
console.log(`✓ Generated release notes import for version ${version}`);

View file

@ -127,6 +127,10 @@
"views.stats.sections.projectBreakdown": "c856b0f89a0d1f51f02f82726206cb07cd1ae95e",
"views.stats.sections.dateRange": "6bb4b674b3232e32feb264e11f19ee1dbed354a3",
"views.stats.filters.minTime": "77f4b7a081b20f78557dd404e14d91b89b52a27c",
"views.releaseNotes.title": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"views.releaseNotes.header": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"views.releaseNotes.viewAllLink": "b37e5d2fb2faa630817557713df6363f4bd0f5cc",
"views.releaseNotes.starMessage": "de98980ccdae9c93e30d1172d4c15c059ea0508c",
"settings.tabs.general": "9239ee2cda84eca4c3440e2a7b50148af67da3d4",
"settings.tabs.taskProperties": "770bd2ee4e0a455df6d1332def9c6c58f44994bc",
"settings.tabs.defaults": "809ab386aa1c870ee286190b1af3eace7f0bfe30",
@ -845,6 +849,7 @@
"commands.pauseResumePomodoro": "1ac6f3fd60f918b25cfb95be70b9d43f3b3281bb",
"commands.refreshCache": "0b56ae2b187bdf931d24258272f284d7046ad543",
"commands.exportAllTasksIcs": "3f3714587b4802efeb6baf380a59615b8faf0c10",
"commands.viewReleaseNotes": "04c2072af97cf70c22fbf9efa9b62eb2574d23d6",
"modals.task.titlePlaceholder": "c8196f1d1039c2fb290add52ee990c571173d6f1",
"modals.task.titleLabel": "768e0c1c69573fb588f61f1308a015c11468e05f",
"modals.task.titleDetailedPlaceholder": "da3700738242c039de32d3f5e3a0ba10bc28ba0b",

View file

@ -512,6 +512,22 @@
"source": "77f4b7a081b20f78557dd404e14d91b89b52a27c",
"translation": "a6bce6cf45ab6987a0491268052e0770559f80a4"
},
"views.releaseNotes.title": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "13e0a79179479dd7bd03740e53af56d3b965f8fc"
},
"views.releaseNotes.header": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "13e0a79179479dd7bd03740e53af56d3b965f8fc"
},
"views.releaseNotes.viewAllLink": {
"source": "b37e5d2fb2faa630817557713df6363f4bd0f5cc",
"translation": "beead282ed357e1ec1e06f7d34a3e9dfe3309181"
},
"views.releaseNotes.starMessage": {
"source": "de98980ccdae9c93e30d1172d4c15c059ea0508c",
"translation": "3886f7795b91fd4ed8d6fcdf37424e5e0b59857d"
},
"settings.tabs.general": {
"source": "9239ee2cda84eca4c3440e2a7b50148af67da3d4",
"translation": "21a7b4ecae6d99d7c9ae0fe00f0cc0a33f1e2b6f"
@ -3372,6 +3388,10 @@
"source": "3f3714587b4802efeb6baf380a59615b8faf0c10",
"translation": "2b3a3551e913a0618f09a1378c63fa6b832fe6cf"
},
"commands.viewReleaseNotes": {
"source": "04c2072af97cf70c22fbf9efa9b62eb2574d23d6",
"translation": "d69d1c26f388903bba0094ba00f61217c47fa732"
},
"modals.task.titlePlaceholder": {
"source": "c8196f1d1039c2fb290add52ee990c571173d6f1",
"translation": "03f97eff6f24910100617f3fcbc4665e2e65cd7e"
@ -6042,6 +6062,22 @@
"source": "77f4b7a081b20f78557dd404e14d91b89b52a27c",
"translation": "01c1479a894b4e8a3b6e679bd3630c1389c7cd60"
},
"views.releaseNotes.title": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "d77248708380c01950bad45e2eee3593861fe33c"
},
"views.releaseNotes.header": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "d77248708380c01950bad45e2eee3593861fe33c"
},
"views.releaseNotes.viewAllLink": {
"source": "b37e5d2fb2faa630817557713df6363f4bd0f5cc",
"translation": "5411820364300214189c01bbd57acdd6e8ac0a04"
},
"views.releaseNotes.starMessage": {
"source": "de98980ccdae9c93e30d1172d4c15c059ea0508c",
"translation": "f41b51d65c2637f895c175954e78a9c70d470034"
},
"settings.tabs.general": {
"source": "9239ee2cda84eca4c3440e2a7b50148af67da3d4",
"translation": "9239ee2cda84eca4c3440e2a7b50148af67da3d4"
@ -8902,6 +8938,10 @@
"source": "3f3714587b4802efeb6baf380a59615b8faf0c10",
"translation": "8f93640a115b91031d8f53e89543cc0bf04e300b"
},
"commands.viewReleaseNotes": {
"source": "04c2072af97cf70c22fbf9efa9b62eb2574d23d6",
"translation": "6b3d99d1c66a1ab48efa9303a68404ff95c10897"
},
"modals.task.titlePlaceholder": {
"source": "c8196f1d1039c2fb290add52ee990c571173d6f1",
"translation": "dee0035d1027b937237c8bbdd6e77288df356ff4"
@ -11572,6 +11612,22 @@
"source": "77f4b7a081b20f78557dd404e14d91b89b52a27c",
"translation": "b6a500e983b64d611aab3a9d5b2ec65fd4511f10"
},
"views.releaseNotes.title": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "eb15567c6afcd90288bcd72e1a8b2529f4f9c30f"
},
"views.releaseNotes.header": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "eb15567c6afcd90288bcd72e1a8b2529f4f9c30f"
},
"views.releaseNotes.viewAllLink": {
"source": "b37e5d2fb2faa630817557713df6363f4bd0f5cc",
"translation": "bb9554fdea3b306e3154c79005b21c6230c2cc5b"
},
"views.releaseNotes.starMessage": {
"source": "de98980ccdae9c93e30d1172d4c15c059ea0508c",
"translation": "25d41272c6bff10b1fba1a61f6db2e8f9df40425"
},
"settings.tabs.general": {
"source": "9239ee2cda84eca4c3440e2a7b50148af67da3d4",
"translation": "5376df989fac86247d5cd9f983d11ece28e75fcd"
@ -14432,6 +14488,10 @@
"source": "3f3714587b4802efeb6baf380a59615b8faf0c10",
"translation": "e4c5963ce721f4bb182d474d1a9c0c604325c274"
},
"commands.viewReleaseNotes": {
"source": "04c2072af97cf70c22fbf9efa9b62eb2574d23d6",
"translation": "e8e1d32313d229666301e29a37df418032835142"
},
"modals.task.titlePlaceholder": {
"source": "c8196f1d1039c2fb290add52ee990c571173d6f1",
"translation": "4ad6efca173a22d4bf622d5729c3ad13612e6902"
@ -17036,6 +17096,22 @@
"source": "77f4b7a081b20f78557dd404e14d91b89b52a27c",
"translation": "7deced4ee8b6e75d5dcafff58c7bd89905c98fd6"
},
"views.releaseNotes.title": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "b04d2e20a60a122b2f4926df86d88940f3050b39"
},
"views.releaseNotes.header": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "b04d2e20a60a122b2f4926df86d88940f3050b39"
},
"views.releaseNotes.viewAllLink": {
"source": "b37e5d2fb2faa630817557713df6363f4bd0f5cc",
"translation": "968c8961a28e089f98db843076f0e224b4770f94"
},
"views.releaseNotes.starMessage": {
"source": "de98980ccdae9c93e30d1172d4c15c059ea0508c",
"translation": "e3b958998079c079c491b83f4f167887d8ba3c01"
},
"settings.tabs.general": {
"source": "9239ee2cda84eca4c3440e2a7b50148af67da3d4",
"translation": "f0aaccbc0dcda37daa045cb4cda85177bedafdb0"
@ -19890,6 +19966,10 @@
"source": "3f3714587b4802efeb6baf380a59615b8faf0c10",
"translation": "4f7b9505464a8b039fee24745afe2a1c951b81d3"
},
"commands.viewReleaseNotes": {
"source": "04c2072af97cf70c22fbf9efa9b62eb2574d23d6",
"translation": "2add1152032ee15c68c3fc05a7923fc50d2d0c31"
},
"modals.task.titlePlaceholder": {
"source": "c8196f1d1039c2fb290add52ee990c571173d6f1",
"translation": "0e65840f941f2c5e030cf553e4e5a3cc315fe73d"
@ -22419,6 +22499,22 @@
"source": "77f4b7a081b20f78557dd404e14d91b89b52a27c",
"translation": "a82231aecd1ee2ec08d32031cf19b6f6a4f2842e"
},
"views.releaseNotes.title": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "92e2d6dcf90925e3073f30916467f8d01ead921a"
},
"views.releaseNotes.header": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "92e2d6dcf90925e3073f30916467f8d01ead921a"
},
"views.releaseNotes.viewAllLink": {
"source": "b37e5d2fb2faa630817557713df6363f4bd0f5cc",
"translation": "4caaa179ece78972b5be06967cd5e5c37ee1d241"
},
"views.releaseNotes.starMessage": {
"source": "de98980ccdae9c93e30d1172d4c15c059ea0508c",
"translation": "c2f8b36f59bc9f660cf0c1bd566fde7f11490dd6"
},
"settings.tabs.general": {
"source": "9239ee2cda84eca4c3440e2a7b50148af67da3d4",
"translation": "fdd17d17b54069e4f68253b0af0b1a50d389b199"
@ -25246,6 +25342,10 @@
"source": "3f3714587b4802efeb6baf380a59615b8faf0c10",
"translation": "1ce699b6bccf33a45ff58320f4dfeb2593c3111e"
},
"commands.viewReleaseNotes": {
"source": "04c2072af97cf70c22fbf9efa9b62eb2574d23d6",
"translation": "060c1aa179717a8ebedce8eeae250cf12307291d"
},
"modals.task.titlePlaceholder": {
"source": "c8196f1d1039c2fb290add52ee990c571173d6f1",
"translation": "fb99b9f5be8916e4a7b038e3d1d9d9087f8a5653"
@ -27850,6 +27950,22 @@
"source": "77f4b7a081b20f78557dd404e14d91b89b52a27c",
"translation": "227f72b88ebec3e37158e855352776b82662a27c"
},
"views.releaseNotes.title": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "f2780c1c443bdaf2d9eba3d81667be844efaf310"
},
"views.releaseNotes.header": {
"source": "5939771f391859041bc57e5e05b04235b1ef8ee7",
"translation": "f2780c1c443bdaf2d9eba3d81667be844efaf310"
},
"views.releaseNotes.viewAllLink": {
"source": "b37e5d2fb2faa630817557713df6363f4bd0f5cc",
"translation": "60abcbc397bbac657dcc27c7bb37870086d7dd18"
},
"views.releaseNotes.starMessage": {
"source": "de98980ccdae9c93e30d1172d4c15c059ea0508c",
"translation": "68e964844caa7bf91e74ffd31d2121483f126d1c"
},
"settings.tabs.general": {
"source": "9239ee2cda84eca4c3440e2a7b50148af67da3d4",
"translation": "f1484fa78b69777c083f5b19555ee4d232d70163"
@ -30695,6 +30811,10 @@
"source": "3f3714587b4802efeb6baf380a59615b8faf0c10",
"translation": "f4fa2d18fff78b531c07f83bbc7753c26dbc05d9"
},
"commands.viewReleaseNotes": {
"source": "04c2072af97cf70c22fbf9efa9b62eb2574d23d6",
"translation": "524c479f706f46cb1f6e70be920671d9ed610e5c"
},
"modals.task.titlePlaceholder": {
"source": "c8196f1d1039c2fb290add52ee990c571173d6f1",
"translation": "37d9b0a0a59872345ce5e206027f8eccf793e6fa"

View file

@ -23,7 +23,9 @@ module.exports = {
// Mock utility modules
'^../../src/utils/helpers$': '<rootDir>/tests/__mocks__/utils.ts',
'^../../src/utils/filenameGenerator$': '<rootDir>/tests/__mocks__/utils.ts',
'^../../src/utils/dateUtils$': '<rootDir>/tests/__mocks__/utils.ts'
'^../../src/utils/dateUtils$': '<rootDir>/tests/__mocks__/utils.ts',
// Mock markdown files (for release notes)
'\\.md$': '<rootDir>/tests/__mocks__/markdown.ts'
},
collectCoverageFrom: [
'src/services/**/*.ts',

View file

@ -4,12 +4,12 @@
"description": "Note-based task management with calendar, pomodoro and time-tracking integration.",
"main": "main.js",
"scripts": {
"build": "npm run build-css && tsc -noEmit --skipLibCheck && node esbuild.config.mjs production",
"build": "npm run build-css && node generate-release-notes-import.mjs && tsc -noEmit --skipLibCheck && node esbuild.config.mjs production",
"dev": "npm run build && npm run copy-files",
"dev:watch": "npm run build-css && node esbuild.config.mjs",
"dev:watch": "npm run build-css && node generate-release-notes-import.mjs && node esbuild.config.mjs",
"copy-files": "node copy-files.mjs",
"build-css": "node build-css.mjs",
"version": "node version-bump.mjs && git add manifest.json versions.json",
"version": "node version-bump.mjs && node generate-release-notes-import.mjs && git add manifest.json versions.json src/releaseNotes.ts",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",

View file

@ -204,6 +204,12 @@ export const de: TranslationTree = {
minTime: "Min. Zeit (Minuten)",
},
},
releaseNotes: {
title: "Was ist neu in TaskNotes {version}",
header: "Was ist neu in TaskNotes {version}",
viewAllLink: "Alle Versionshinweise auf GitHub anzeigen →",
starMessage: "Wenn Sie TaskNotes nützlich finden, geben Sie ihm bitte einen Stern auf GitHub",
},
},
settings: {
tabs: {
@ -1530,6 +1536,7 @@ export const de: TranslationTree = {
pauseResumePomodoro: "Pomodoro-Timer pausieren/fortsetzen",
refreshCache: "Cache aktualisieren",
exportAllTasksIcs: "Alle Aufgaben als ICS-Datei exportieren",
viewReleaseNotes: "Versionshinweise anzeigen",
},
modals: {
task: {

View file

@ -204,6 +204,12 @@ export const en: TranslationTree = {
minTime: "Min Time (minutes)",
},
},
releaseNotes: {
title: "What's new in TaskNotes {version}",
header: "What's new in TaskNotes {version}",
viewAllLink: "View all release notes on GitHub →",
starMessage: "If you find TaskNotes useful, please consider giving it a star on GitHub",
},
},
settings: {
tabs: {
@ -1496,6 +1502,7 @@ export const en: TranslationTree = {
pauseResumePomodoro: "Pause/resume pomodoro timer",
refreshCache: "Refresh cache",
exportAllTasksIcs: "Export all tasks as ICS file",
viewReleaseNotes: "View release notes",
},
modals: {
task: {

View file

@ -204,6 +204,12 @@ export const es: TranslationTree = {
minTime: "Tiempo mínimo (minutos)",
},
},
releaseNotes: {
title: "Novedades en TaskNotes {version}",
header: "Novedades en TaskNotes {version}",
viewAllLink: "Ver todas las notas de versión en GitHub →",
starMessage: "Si TaskNotes te resulta útil, considera darle una estrella en GitHub",
},
},
settings: {
tabs: {
@ -1540,6 +1546,7 @@ export const es: TranslationTree = {
pauseResumePomodoro: "Pausar/reanudar temporizador pomodoro",
refreshCache: "Actualizar caché",
exportAllTasksIcs: "Exportar todas las tareas como archivo ICS",
viewReleaseNotes: "Ver notas de versión",
},
modals: {
task: {

View file

@ -204,6 +204,12 @@ export const fr: TranslationTree = {
minTime: "Temps min (minutes)",
},
},
releaseNotes: {
title: "Nouveautés de TaskNotes {version}",
header: "Nouveautés de TaskNotes {version}",
viewAllLink: "Voir toutes les notes de version sur GitHub →",
starMessage: "Si vous trouvez TaskNotes utile, pensez à lui donner une étoile sur GitHub",
},
},
settings: {
tabs: {
@ -1541,6 +1547,7 @@ export const fr: TranslationTree = {
pauseResumePomodoro: "Mettre en pause/reprendre le minuteur Pomodoro",
refreshCache: "Actualiser le cache",
exportAllTasksIcs: "Exporter toutes les tâches en fichier ICS",
viewReleaseNotes: "Voir les notes de version",
},
modals: {
task: {

View file

@ -205,6 +205,12 @@ export const ja: TranslationTree = {
minTime: "最小時間(分)",
},
},
releaseNotes: {
title: "TaskNotes {version} の新機能",
header: "TaskNotes {version} の新機能",
viewAllLink: "GitHubですべてのリリースートを表示 →",
starMessage: "TaskNotesが役に立ったら、GitHubでスターをお願いします",
},
},
settings: {
tabs: {
@ -1487,6 +1493,7 @@ export const ja: TranslationTree = {
pauseResumePomodoro: "ポモドーロタイマーを一時停止/再開",
refreshCache: "キャッシュを更新",
exportAllTasksIcs: "すべてのタスクをICSファイルとしてエクスポート",
viewReleaseNotes: "リリースノートを表示",
},
modals: {
task: {

View file

@ -204,6 +204,12 @@ export const ru: TranslationTree = {
minTime: "Мин. время (минуты)",
},
},
releaseNotes: {
title: "Что нового в TaskNotes {version}",
header: "Что нового в TaskNotes {version}",
viewAllLink: "Посмотреть все примечания к выпуску на GitHub →",
starMessage: "Если TaskNotes полезен для вас, поставьте звезду на GitHub",
},
},
settings: {
tabs: {
@ -1507,6 +1513,7 @@ export const ru: TranslationTree = {
pauseResumePomodoro: "Приостановить/возобновить таймер помодоро",
refreshCache: "Обновить кэш",
exportAllTasksIcs: "Экспортировать все задачи как файл ICS",
viewReleaseNotes: "Посмотреть примечания к выпуску",
},
modals: {
task: {

View file

@ -204,6 +204,12 @@ export const zh: TranslationTree = {
minTime: "最少时间(分钟)",
},
},
releaseNotes: {
title: "TaskNotes {version} 的新功能",
header: "TaskNotes {version} 的新功能",
viewAllLink: "在 GitHub 上查看所有版本说明 →",
starMessage: "如果您觉得 TaskNotes 有用,请在 GitHub 上给它加星",
},
},
settings: {
tabs: {
@ -1432,6 +1438,7 @@ export const zh: TranslationTree = {
pauseResumePomodoro: "暂停/恢复番茄钟计时器",
refreshCache: "刷新缓存",
exportAllTasksIcs: "导出所有任务为ICS文件",
viewReleaseNotes: "查看版本说明",
},
modals: {
task: {

View file

@ -94,6 +94,8 @@ import { AutoExportService } from "./services/AutoExportService";
// Type-only import for HTTPAPIService (actual import is dynamic on desktop only)
import type { HTTPAPIService } from "./services/HTTPAPIService";
import { createI18nService, I18nService, TranslationKey } from "./i18n";
import { ReleaseNotesView, RELEASE_NOTES_VIEW_TYPE } from "./views/ReleaseNotesView";
import { CURRENT_RELEASE_NOTES } from "./releaseNotes";
interface TranslatedCommandDefinition {
id: string;
@ -461,6 +463,11 @@ export default class TaskNotesPlugin extends Plugin {
this.registerView(KANBAN_VIEW_TYPE, (leaf) => new KanbanView(leaf, this));
this.registerView(
RELEASE_NOTES_VIEW_TYPE,
(leaf) => new ReleaseNotesView(leaf, this, CURRENT_RELEASE_NOTES, this.manifest.version)
);
// Register essential editor extensions (now safe after layout ready)
this.registerEditorExtension(createTaskLinkOverlay(this));
@ -639,6 +646,9 @@ export default class TaskNotesPlugin extends Plugin {
// Migration check was moved to early startup - just show prompts here if needed
await this.showMigrationPromptsIfNeeded();
// Check for version updates and show release notes if needed
await this.checkForVersionUpdate();
} catch (error) {
console.error("Error during lazy service initialization:", error);
}
@ -886,6 +896,36 @@ export default class TaskNotesPlugin extends Plugin {
}
}
/**
* Check for version updates and show release notes if needed
*/
private async checkForVersionUpdate(): Promise<void> {
try {
const currentVersion = this.manifest.version;
const lastSeenVersion = this.settings.lastSeenVersion;
// If this is a new install or version has changed, show release notes
if (lastSeenVersion && lastSeenVersion !== currentVersion) {
// Show release notes after a delay to ensure UI is ready
setTimeout(async () => {
await this.activateReleaseNotesView();
// Update lastSeenVersion immediately after showing the release notes
// This ensures they only show once per version
this.settings.lastSeenVersion = currentVersion;
await this.saveSettings();
}, 1500); // Slightly longer delay than migration to avoid conflicts
}
// Update lastSeenVersion if it hasn't been set yet (new install)
if (!lastSeenVersion) {
this.settings.lastSeenVersion = currentVersion;
await this.saveSettings();
}
} catch (error) {
console.error("Error checking for version update:", error);
}
}
/**
* Public method for views to wait for migration completion
*/
@ -1429,6 +1469,13 @@ export default class TaskNotesPlugin extends Plugin {
}
},
},
{
id: "view-release-notes",
nameKey: "commands.viewReleaseNotes",
callback: async () => {
await this.activateReleaseNotesView();
},
},
];
this.registerCommands();
@ -1560,6 +1607,10 @@ export default class TaskNotesPlugin extends Plugin {
return this.activateView(KANBAN_VIEW_TYPE);
}
async activateReleaseNotesView() {
return this.activateView(RELEASE_NOTES_VIEW_TYPE);
}
/**
* Open and activate the search pane with a tag query
* (Renamed from openSearchPaneWithTag for cleaner API)

5
src/markdown.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
// Type declarations for importing markdown files as strings
declare module "*.md" {
const content: string;
export default content;
}

View file

@ -238,6 +238,8 @@ export const DEFAULT_SETTINGS: TaskNotesSettings = {
customPriorities: DEFAULT_PRIORITIES,
// Migration defaults
recurrenceMigrated: false,
// Release notes defaults
lastSeenVersion: undefined,
// Status bar defaults
showTrackedTasksInStatusBar: false,
// Time tracking defaults

View file

@ -1,4 +1,5 @@
// import { TAbstractFile } from 'obsidian';
import { Setting } from "obsidian";
import TaskNotesPlugin from "../../main";
import {
createSectionHeader,
@ -226,4 +227,20 @@ export function renderGeneralTab(
save();
},
});
// Release Notes Section
createSectionHeader(container, "Release Notes");
createHelpText(container, `Current version: ${plugin.manifest.version}`);
new Setting(container)
.setName("View release notes")
.setDesc("See what's new in the latest version of TaskNotes")
.addButton((button) =>
button
.setButtonText("View release notes")
.setCta()
.onClick(async () => {
await plugin.activateReleaseNotesView();
})
);
}

View file

@ -85,6 +85,8 @@ export interface TaskNotesSettings {
customPriorities: PriorityConfig[];
// Migration tracking
recurrenceMigrated?: boolean;
// Release notes tracking
lastSeenVersion?: string;
// Status bar settings
showTrackedTasksInStatusBar: boolean;
// Time tracking settings

View file

@ -0,0 +1,102 @@
import { ItemView, WorkspaceLeaf, MarkdownRenderer } from "obsidian";
import TaskNotesPlugin from "../main";
export const RELEASE_NOTES_VIEW_TYPE = "tasknotes-release-notes";
const GITHUB_RELEASES_URL = "https://github.com/callumalpass/tasknotes/releases";
export class ReleaseNotesView extends ItemView {
plugin: TaskNotesPlugin;
private releaseNotes: string;
private version: string;
constructor(leaf: WorkspaceLeaf, plugin: TaskNotesPlugin, releaseNotes: string, version: string) {
super(leaf);
this.plugin = plugin;
this.releaseNotes = releaseNotes;
this.version = version;
}
getViewType(): string {
return RELEASE_NOTES_VIEW_TYPE;
}
getDisplayText(): string {
return this.plugin.i18n.translate("views.releaseNotes.title", { version: this.version });
}
getIcon(): string {
return "book-open";
}
/**
* Transform issue references like (#123) into clickable GitHub issue links
*/
private transformIssueLinks(markdown: string): string {
const repoUrl = "https://github.com/callumalpass/tasknotes";
return markdown.replace(/\(#(\d+)\)/g, `([#$1](${repoUrl}/issues/$1))`);
}
async onOpen() {
const { contentEl } = this;
contentEl.empty();
contentEl.addClass("tasknotes-release-notes-view");
// Create a container for the markdown content
const container = contentEl.createDiv({ cls: "tasknotes-release-notes-container" });
container.style.padding = "20px";
container.style.maxWidth = "900px";
container.style.margin = "0 auto";
// Header with version
const header = container.createEl("div", { cls: "release-notes-header" });
header.style.marginBottom = "20px";
header.createEl("h1", {
text: this.plugin.i18n.translate("views.releaseNotes.header", { version: this.version })
});
// Markdown content
const markdownContainer = container.createEl("div", { cls: "release-notes-content" });
markdownContainer.style.marginBottom = "30px";
// Transform issue references into clickable links and render the markdown
const transformedNotes = this.transformIssueLinks(this.releaseNotes);
await MarkdownRenderer.render(
this.plugin.app,
transformedNotes,
markdownContainer,
"",
this as any
);
// Footer with link to all releases
const footer = container.createEl("div", { cls: "release-notes-footer" });
footer.style.borderTop = "1px solid var(--background-modifier-border)";
footer.style.paddingTop = "20px";
footer.style.textAlign = "center";
const link = footer.createEl("a", {
text: this.plugin.i18n.translate("views.releaseNotes.viewAllLink"),
href: GITHUB_RELEASES_URL,
});
link.style.color = "var(--text-accent)";
link.style.textDecoration = "none";
link.addEventListener("click", (e) => {
e.preventDefault();
window.open(GITHUB_RELEASES_URL, "_blank");
});
// Star message
const starMessage = footer.createEl("p", {
text: this.plugin.i18n.translate("views.releaseNotes.starMessage"),
});
starMessage.style.marginTop = "16px";
starMessage.style.fontSize = "0.9em";
starMessage.style.color = "var(--text-muted)";
}
async onClose() {
const { contentEl } = this;
contentEl.empty();
}
}

View file

@ -0,0 +1,3 @@
// Mock for markdown file imports
// Used to prevent Jest from trying to parse .md files as JavaScript
export default "# Mock Release Notes\n\nThis is a mock markdown file for testing purposes.";

View file

@ -25,6 +25,7 @@ describe('Settings UI - User Fields (optional)', () => {
(plugin as any).settings = { ...DEFAULT_SETTINGS };
(plugin as any).i18n = createI18nService();
(plugin as any).registerEvent = jest.fn();
(plugin as any).manifest = { version: '0.0.0' };
const tab = new TaskNotesSettingTab(app, plugin);
tab.display();