Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .claude/rules/code-styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Conventions for NoteRoot code. They describe how the code is written today. When

## Formatting

Prettier decides (`.prettierrc`): 2 spaces, double quotes, semicolons, line width 100, trailing commas where ES5 allows them, CRLF line endings (`.editorconfig`). Prettier is a dev dependency: run `npm run format` after editing and before finishing (`npm run format:check` only checks). `media/` (the webview page) is not formatted by it. Always use braces for `if`, `for` and `while` (`curly` is a lint rule). Use `===` (`eqeqeq`).
Prettier decides (`.prettierrc`): 2 spaces, double quotes, semicolons, line width 100, trailing commas where ES5 allows them, CRLF line endings (`.editorconfig`). Prettier is a dev dependency: run `npm run format` after editing and before finishing (`npm run format:check` only checks). `webview/` (the pages of the webviews: .js and .css) is not formatted by it. Always use braces for `if`, `for` and `while` (`curly` is a lint rule). Use `===` (`eqeqeq`).

## Naming

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ jobs:
- name: Package
run: npx vsce package --no-dependencies --out noteroot.vsix

# The package holds only the bundle, media/, README, CHANGELOG, LICENSE and package.json, and stays small
# The package holds only the bundle, media/, webview/, README, CHANGELOG, LICENSE and package.json, and stays small
# (the screenshots in docs/ must never end up in it).
- name: Check the package contents
if: runner.os == 'Linux'
shell: bash
run: |
unexpected=$(unzip -Z1 noteroot.vsix | grep -v '/$' | grep -v -E '^(\[Content_Types\]\.xml|extension\.vsixmanifest|extension/(package\.json|readme\.md|changelog\.md|LICENSE\.txt|dist/extension\.js|media/.+))$' || true)
unexpected=$(unzip -Z1 noteroot.vsix | grep -v '/$' | grep -v -E '^(\[Content_Types\]\.xml|extension\.vsixmanifest|extension/(package\.json|readme\.md|changelog\.md|LICENSE\.txt|dist/extension\.js|media/.+|webview/.+))$' || true)
if [ -n "$unexpected" ]; then
echo "::error::Unexpected files in the package:"
echo "$unexpected"
Expand Down
3 changes: 2 additions & 1 deletion .vscodeignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Ship only the bundle, media/, README, CHANGELOG, LICENSE and package.json.
# Ship only the bundle, media/, webview/, README, CHANGELOG, LICENSE and package.json.
# Source maps stay out: the production build has none, and an old dev map must not slip in.
**
!dist/extension.js
!media/**
!webview/**
!README.md
!CHANGELOG.md
!LICENSE
Expand Down
9 changes: 6 additions & 3 deletions CLAUDE.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions MANUAL-TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ NoteRoot has no automated tests, so check these by hand before a release. Press
- [ ] Every row opens the right editor (choice, text, number, list, switch, action) and rejects bad input with a message.
- [ ] **Reset to Default** appears only on changed rows; the folder value is removed first, a second reset removes the user value. Rows show **· this folder** when the notes folder has its own value.
- [ ] A change made in the VS Code Settings editor shows up in the panel at once.
- [ ] The pencil button of the Settings view opens **NoteRoot Settings** in an editor tab (a second click shows the same tab). Every kind of row can be changed there (select, checkbox, text box on Enter or leaving it, list with **Apply** or Ctrl+Enter, **Change…** for the folder and the Quick Note file); a bad value shows a message under the row and is not saved; **Reset** and the search box and **Only changed** work; a change made in the Settings view shows up in the page and the other way round.

## Tags & Categories page

- [ ] The pencil button of the Tags & Categories view opens **NoteRoot Tags & Categories** in an editor tab (a second click shows the same tab). Categories, tags, patterns and the **Every file** list match the view; the counts appear a moment after the lists.
- [ ] New, Rename, Keyword, Change file, Remove file / keyword and Delete ask the same questions as the view and the page updates at once; the two switches turn the automatic features on and off; a note, a category file and a tag of a pattern open from their links.
- [ ] A change made in the view, in the Quick Note panel or in the config file shows up in the page; with no notes folder the page offers **Select Notes Folder**.

## Encryption

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ Your notes are plain files in a folder you pick. NoteRoot is built so you can tr

- **Sync.** Push your notes to GitHub and pull them on another computer, with no git and no other app. It can be encrypted. See below.
- **Settings panel.** Every setting is in one panel, with its value: click a row to change it, right-click to reset it or show it in the VS Code Settings editor. Settings that belong to your notes are saved in the notes folder, so they travel with it (and with Sync); those are not in the VS Code Settings editor. The panel has four groups:
- **Settings page.** The pencil button of the Settings panel opens all settings in an editor tab, with search, an *Only changed* filter, defaults and Reset.
- **Tags & Categories page.** The pencil button of the Tags & Categories panel opens everything in an editor tab: categories, tags, tag patterns, the categories of every file and the two automatic switches, with their notes and files.
- **Notes:** the notes folder, sort order, default extension, hidden files and folders, the large file warning, the note header and which file types count as notes.
- **Quick Note:** the Quick Note file, where new notes go (start or end), the heading and date format above each note, the line with the tags, and *Tag when saving*.
- **Tags & Categories:** the Advanced options, *Category for every file* and *Tag from pattern* switches (and whether they start on), and the files and folders that are never tagged automatically.
Expand Down
22 changes: 21 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@
"title": "NoteRoot: Open Settings Editor",
"icon": "$(settings-gear)"
},
{
"command": "noteRoot.openSettingsPage",
"title": "NoteRoot: Open Settings Page",
"icon": "$(edit)"
},
{
"command": "noteRoot.syncConnect",
"title": "NoteRoot: Connect to a Sync Service"
Expand Down Expand Up @@ -532,6 +537,11 @@
"title": "Refresh",
"icon": "$(refresh)"
},
{
"command": "noteRoot.openLabelsPage",
"title": "NoteRoot: Open Tags & Categories Page",
"icon": "$(edit)"
},
{
"command": "noteRoot.openAtLine",
"title": "Open at Line"
Expand Down Expand Up @@ -654,6 +664,11 @@
"when": "view == noteRoot.labels && noteRoot.hasRoot",
"group": "navigation@2"
},
{
"command": "noteRoot.openLabelsPage",
"when": "view == noteRoot.labels && noteRoot.hasRoot",
"group": "navigation@3"
},
{
"command": "noteRoot.enableAutoCategories",
"when": "view == noteRoot.labels && noteRoot.hasRoot && !noteRoot.autoCategories",
Expand All @@ -675,10 +690,15 @@
"group": "navigation@1"
},
{
"command": "noteRoot.openAllSettings",
"command": "noteRoot.openSettingsPage",
"when": "view == noteRoot.settings",
"group": "navigation@0"
},
{
"command": "noteRoot.openAllSettings",
"when": "view == noteRoot.settings",
"group": "navigation@1"
},
{
"command": "noteRoot.syncRefresh",
"when": "view == noteRoot.sync && noteRoot.syncConnected",
Expand Down
15 changes: 13 additions & 2 deletions src/commands/labelViewCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { LabelService } from "../services/LabelService";
import { Logger } from "../services/Logger";
import { PathGuard } from "../services/PathGuard";
import { LabelEntryItem, LabelsTreeProvider } from "../tree/LabelsTreeProvider";
import type { LabelsPageProvider } from "../webview/LabelsPageProvider";
import { askCategoryFile } from "./labelDialogs";
import { performLabelAction } from "./labelCommands";
import { registerSafeCommand } from "./safe";
Expand All @@ -18,14 +19,23 @@ export interface LabelViewCommandDependencies {
pathGuard: PathGuard;
labelService: LabelService;
labelsProvider: LabelsTreeProvider;
labelsPage: LabelsPageProvider;
autoCategories: AutoCategoryService;
advancedMode: AdvancedModeService;
}

/** The commands of the Tags & Categories view. They share their questions with the Manage list. */
export function registerLabelViewCommands(deps: LabelViewCommandDependencies): void {
const { context, logger, pathGuard, labelService, labelsProvider, autoCategories, advancedMode } =
deps;
const {
context,
logger,
pathGuard,
labelService,
labelsProvider,
labelsPage,
autoCategories,
advancedMode,
} = deps;
const register = (id: string, handler: (...args: unknown[]) => unknown): void =>
registerSafeCommand(context, logger, id, handler);

Expand Down Expand Up @@ -160,6 +170,7 @@ export function registerLabelViewCommands(deps: LabelViewCommandDependencies): v
});

register(Commands.refreshLabels, () => labelsProvider.refresh());
register(Commands.openLabelsPage, () => labelsPage.open());

// A click on a note of a tag: open it with the tag selected.
register(
Expand Down
17 changes: 9 additions & 8 deletions src/commands/settingsCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ import {
findSettingDef,
isChanged,
resetSetting,
typedValue,
writeSwitch,
type SettingDef,
type SettingsContext,
} from "../services/SettingsCatalog";
import { SettingItem, choiceLabel } from "../tree/SettingsTreeProvider";
import type { SettingsPageProvider } from "../webview/SettingsPageProvider";
import { registerSafeCommand, reportError } from "./safe";
import { showStatus } from "../util/status";

export interface SettingsCommandDependencies {
context: vscode.ExtensionContext;
logger: Logger;
settingsContext: SettingsContext;
settingsPage: SettingsPageProvider;
}

/** What the user asked for in the list of a setting. */
Expand Down Expand Up @@ -49,7 +52,7 @@ const listButtons: Record<"edit" | "delete", ListButton> = {

/** The commands of the Settings view: change a setting, take it back to its default, show it in the Settings editor. */
export function registerSettingsCommands(deps: SettingsCommandDependencies): void {
const { context, logger, settingsContext } = deps;
const { context, logger, settingsContext, settingsPage } = deps;
const { settings } = settingsContext;
const text = Strings.settingsView;

Expand Down Expand Up @@ -96,13 +99,7 @@ export function registerSettingsCommands(deps: SettingsCommandDependencies): voi
if (typed === undefined || def.key === undefined) {
return;
}
// A date format may have spaces in it on purpose; everything else is taken as it is typed, without the spaces around it.
const next =
def.kind === "number"
? (def.parse?.(typed) ?? Number(typed))
: def.id === "quickNoteTimestampFormat"
? typed
: typed.trim();
const next = typedValue(def, typed);
if (next !== def.read(settingsContext)) {
await settings.setValue(def.key, next);
say(text.updated(text.items[def.id].label));
Expand Down Expand Up @@ -229,6 +226,10 @@ export function registerSettingsCommands(deps: SettingsCommandDependencies): voi
}
});

registerSafeCommand(context, logger, Commands.openSettingsPage, () => {
settingsPage.open();
});

registerSafeCommand(context, logger, Commands.openAllSettings, async () => {
await vscode.commands.executeCommand(BuiltinCommands.openSettings, SETTINGS_SECTION);
});
Expand Down
13 changes: 9 additions & 4 deletions src/commands/tagPatternCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,19 @@ export function registerTagPatternCommands(deps: TagPatternCommandDependencies):
registerSafeCommand(context, logger, Commands.addTagPattern, async () => {
await perform({ type: "add" }, await labelService.getTagPatterns());
});
// The Tags & Categories page runs the same two commands with the pattern as text.
const patternOf = (item: unknown): string | undefined =>
item instanceof PatternNode ? item.pattern : typeof item === "string" ? item : undefined;
registerSafeCommand(context, logger, Commands.editTagPattern, async (item?: unknown) => {
if (item instanceof PatternNode) {
await perform({ type: "edit", pattern: item.pattern }, await labelService.getTagPatterns());
const pattern = patternOf(item);
if (pattern !== undefined) {
await perform({ type: "edit", pattern }, await labelService.getTagPatterns());
}
});
registerSafeCommand(context, logger, Commands.deleteTagPattern, async (item?: unknown) => {
if (item instanceof PatternNode) {
await perform({ type: "delete", pattern: item.pattern }, await labelService.getTagPatterns());
const pattern = patternOf(item);
if (pattern !== undefined) {
await perform({ type: "delete", pattern }, await labelService.getTagPatterns());
}
});

Expand Down
2 changes: 2 additions & 0 deletions src/constants/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export const Commands = {
resetSetting: "noteRoot.resetSetting",
showSettingInEditor: "noteRoot.showSettingInEditor",
openAllSettings: "noteRoot.openAllSettings",
openSettingsPage: "noteRoot.openSettingsPage",
openLabelsPage: "noteRoot.openLabelsPage",
tagNote: "noteRoot.tagNote",
tagCurrentNote: "noteRoot.tagCurrentNote",
insertHeader: "noteRoot.insertHeader",
Expand Down
70 changes: 70 additions & 0 deletions src/constants/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,75 @@ export const Strings = {
current: "current",
},

/** The Tags & Categories page: what the view has, in an editor tab, to review and change. */
labelsPage: {
title: "NoteRoot Tags & Categories",
intro:
"Review and change your categories, tags and the automatic features. A change is saved as soon as you make it.",
refresh: "Refresh",
searchLabel: "Search categories, tags and patterns",
searchPlaceholder: "Search",
noMatches: "Nothing matches.",
needsAdvanced:
"The Advanced options of the Quick Note panel are off. Categories, tags and the automatic features only work while they are on.",
counting: "Counting…",
categories: "Categories",
newCategory: "New category…",
noCategories: "No categories yet.",
usesQuickNoteFile: "Quick Note file",
notCreated: "not created yet",
everyFile: "Every file",
everyFileSwitch: "Category for every file",
everyFileHint: "Every note file is a category that writes to that file.",
everyFileNone: "No note files yet.",
everyFileMore: "More files exist than are listed.",
tags: "Tags",
newTag: "New tag…",
noTags: "No tags yet.",
patterns: "Tag Patterns",
patternsSwitch: "Tag from pattern",
patternsHint:
"Text of a note that matches a pattern becomes a tag when the note is saved. The tag is written in the note only.",
newPattern: "Add pattern…",
noPatterns: "No patterns yet.",
noMatchesYet: "No matches yet.",
keyword: "Keyword",
changeFile: "Change file…",
removeFile: "Remove file",
setKeyword: "Keyword…",
removeKeyword: "Remove keyword",
rename: "Rename…",
delete: "Delete",
edit: "Change…",
noteOne: "1 note",
noteMany: "{n} notes",
tagOne: "1 tag",
tagMany: "{n} tags",
on: "On",
off: "Off",
},
/** The Settings page: every setting in an editor tab, to review and change. */
settingsPage: {
title: "NoteRoot Settings",
intro: "Review and change every NoteRoot setting. A change is saved as soon as you make it.",
searchLabel: "Search settings",
searchPlaceholder: "Search settings",
onlyChanged: "Only changed",
noMatches: "No settings match.",
changed: "changed",
sourceFolder: "this folder",
sourceUser: "your settings",
reset: "Reset",
change: "Change…",
apply: "Apply",
listHint: "One entry per line.",
on: "On",
off: "Off",
defaultNone: "none",
units: { mb: "MB", ms: "ms", px: "px", s: "s", count: "" },
saved: "Saved",
defaultLabel: "Default:",
},
settingsView: {
groups: { notes: "Notes", quickNote: "Quick Note", tags: "Tags & Categories", more: "More" },
items: {
Expand Down Expand Up @@ -278,6 +347,7 @@ export const Strings = {
invalidFormat: "Use one line of at most 60 characters.",
invalidNumber: "Enter a number of 0 or more.",
emptyNotAllowed: "This cannot be empty.",
invalidChoice: "This value is not allowed for this setting.",
updated: (label: string): string => `${label} updated`,
resetDone: (label: string): string => `${label} is back to its default`,
listTitle: (label: string): string => `Change: ${label}`,
Expand Down
18 changes: 18 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ import { TreeNavigator } from "./tree/TreeNavigator";
import { TreeStateController } from "./tree/TreeStateController";
import { configureStatusMessages } from "./util/status";
import { QuickNoteViewProvider } from "./webview/QuickNoteViewProvider";
import { LabelsPageProvider } from "./webview/LabelsPageProvider";
import { SettingsPageProvider } from "./webview/SettingsPageProvider";

/** Wiring only: creates services and providers and registers them. No business logic here. */
export function activate(context: vscode.ExtensionContext): void {
Expand Down Expand Up @@ -177,6 +179,7 @@ export function activate(context: vscode.ExtensionContext): void {
syncProvider.attach(syncView);
const settingsContext = { settings, advancedMode, autoCategories, autoTagPatterns };
const settingsProvider = new SettingsTreeProvider(settingsContext);
const settingsPage = new SettingsPageProvider(context.extensionUri, settingsContext, logger);
const labelsProvider = new LabelsTreeProvider(
labelService,
tagIndex,
Expand All @@ -187,6 +190,17 @@ export function activate(context: vscode.ExtensionContext): void {
autoTagPatterns,
logger
);
const labelsPage = new LabelsPageProvider(
context.extensionUri,
labelService,
labelsProvider,
rootService,
autoCategories,
autoTagPatterns,
advancedMode,
settings,
logger
);
const quickNoteService = new QuickNoteService(
rootService,
pathGuard,
Expand Down Expand Up @@ -234,6 +248,8 @@ export function activate(context: vscode.ExtensionContext): void {
syncProvider,
syncView,
settingsProvider,
settingsPage,
labelsPage,
vscode.window.registerTreeDataProvider(ViewIds.settings, settingsProvider),
vscode.window.registerTreeDataProvider(ViewIds.recent, pinnedRecentProvider),
quickNoteService,
Expand Down Expand Up @@ -265,6 +281,7 @@ export function activate(context: vscode.ExtensionContext): void {
recentService,
pinnedService,
labelsProvider,
labelsPage,
autoCategories,
autoTagPatterns,
taggingService,
Expand All @@ -275,6 +292,7 @@ export function activate(context: vscode.ExtensionContext): void {
excludeMatcher,
noTagMatcher,
settingsContext,
settingsPage,
syncService,
onlineView,
});
Expand Down
Loading
Loading