diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000..73f0986c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,24 @@
+
+
+## Expected Behavior
+
+
+
+## Current Behavior
+
+
+
+## Possible Solution
+
+
+
+## Steps to Reproduce (for bugs)
+
+1.
+2.
+3.
+4.
+
+## Context
+
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..4a54c35e
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,18 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ # Maintain dependencies for GitHub Actions
+ - package-ecosystem: "github-actions"
+ # Workflow files stored in the default location of `.github/workflows`. (You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.)
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ # Maintain dependencies for npm
+ - package-ecosystem: "npm" # See documentation for possible values
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "weekly"
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..e2a3e0d6
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,20 @@
+# Description
+
+Please include a summary of the change and which (if any) issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
+
+Fixes # (issue)
+
+## Type of change
+
+Please delete options that are not relevant.
+
+- [ ] Bug fix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] This change requires a documentation update
+
+# How Has This Been Tested?
+
+- [ ] If this is a bug fix, did you add or update a test file to the examples directory that verifies the bug is fixed?
+- [ ] If this is a new feature, did you add files to the examples directory to demonstrate the feature?
+- [ ] If this is a new feature, did you add documentation to the docs directory for the feature?
diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml
new file mode 100644
index 00000000..21433702
--- /dev/null
+++ b/.github/workflows/releases.yml
@@ -0,0 +1,32 @@
+name: Release
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ build:
+ permissions:
+ contents: write
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - name: Use Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: '18.x'
+ - name: Build
+ run: |
+ yarn
+ yarn build
+ yarn zip
+ - name: Release with Notes
+ uses: softprops/action-gh-release@v2
+ with:
+ files: |
+ main.js
+ manifest.json
+ styles.css
+ obsidian-tracker-*.zip
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index bb7f74e7..1c6405ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,6 @@
# npm
node_modules
-package-lock.json
# build
main.js
@@ -16,4 +15,4 @@ examples/*.js
.obsidian
# examples for users
-examples/case
\ No newline at end of file
+examples/case
diff --git a/LICENSE b/LICENSE
index 6098b167..27adb17c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2021 Hsuan Hao Chang
+Copyright (c) 2021-2024 Hsuan Hao Chang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 9bf79c7b..734a6cda 100644
--- a/README.md
+++ b/README.md
@@ -1,52 +1,191 @@
-# Obsidian Tracker Plugin
-
-
-
-
-This is an [Obsidian](https://obsidian.md/) plugin that helps you do tracking in notes and represent the collected data comprehensively.
-
-[Here](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Examples.md) is a simplified table of examples showing what you can track.
-
-## What's New
-Version 1.8.0
-- Add a new `searchType` `task`, retrieving data from tasks ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTask.md))
-- Enhancement
- - Month view ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestCalendar.md))
- - Add parameter `circleColorByValue` to show color based on the value
- - Support multiple targets (dataset), change the dataset by clicking the header
- - Add a button to show current month
- - Accept ISO-8601 date as `dateFormat` ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#iso-8601-date-format))
- - Relative date input for `startDate` and `endDate` ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#relative-date-input-for-startdate-and-enddate))
-- Fixed missing dvField values at the last line of files
-
-Version 1.8.1
-- Fixed bugs while using month view with parameter `xDataset`
-
-Version 1.8.2
-- Fixed tasks searching not working for multiple targets
-
-## Usage
-1. Have some targets you want to track in daily notes.
-2. Add a new note for displaying the tracker.
-3. Add tracker code blocks manually or using [commands](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Commands.md).
-4. Switch the document view mode to 'Preview', then the code block will get rendered.
-
-
-
-For more use cases, please download and open the [examples](https://github.com/pyrochlore/obsidian-tracker/tree/master/examples) folder in obsidian with this plugin installed and enabled.
-
-## More Details You May Want to Know
-- [Installation](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Installation.md): Install the plugin from Obsidian or install it manually
-- [Concepts](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Concepts.md): Explain how this plugin works and what to setup
- - [Target Evaluation](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TargetEvaluation.md)
- - [Input Parameters](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/InputParameters.md)
- - [Template Variables](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TemplateVariables.md)
-- [Examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Examples.md)
-- [Plugin Settings](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Settings.md)
-- [Release Notes](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/ReleaseNotes.md)
-- [Road Map](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/RoadMap.md)
-- [Frequently Asked Questions](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Questions.md)
-
-## Support
-- If you like this plugin or want to support further development, you can [Buy Me a Coffee](https://www.buymeacoffee.com/pyrochlore).
-- Please report bugs and request features in [GitHub Issues](https://github.com/pyrochlore/obsidian-tracker/issues)
+# Obsidian Tracker Plugin
+
+
+
+
+
+This is an [Obsidian](https://obsidian.md/) plugin that helps you collect data from notes and represent it comprehensively.
+
+[Here](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Examples.md) is a table containing simplified examples showing what you can track.
+
+## What's New
+
+Version 1.19.0
+
+- Add `searchType: frontmatter.exists` for tracking field existence
+
+Version 1.18.0
+
+- Support all days for startWeekOn parameter
+- Handle numeric YAML parsing for startDate/endDate with YYYYMMDD format
+- Add searchType for frontmatter keys containing lists
+- Update dependencies
+
+Version 1.17.0
+
+- Adding colorByStreak parameter to Calendars allowing for increase color intensity based on streak
+- Update dependencies
+
+Version 1.16.0
+
+- Adding thresholdType parameter to Calendars
+- Enable bar chart full-bar display at beginning and end
+- Update dependencies
+
+Version 1.15.1
+
+- Fix: Added DatasetName as title for calendar in annotation mode
+- Fix: Resolve scaling issue when navigating between monthly views
+- Update dependencies
+
+Version 1.15.0
+
+- Fix: Minor spelling error in Examples.md
+- Added new expression functions - First and Last
+- Updated dates in examples to fix plots
+- Respect thresholds in scaled months
+- Update dependencies
+
+Version 1.14.0
+
+- Update dependencies
+- Prevent overlapping external labels in pie chart
+- Enable Stacked Bar Chart
+
+Version 1.13.3
+
+- Update dependencies
+- Fix typo in FinanceTracker.md
+- Fix typo in TestExpression.md
+- Update main.ts - use new format for {{average}}
+
+Version 1.13.2
+
+- Update dependencies
+- Fix typo in README.md
+- Change streak counts to terminate on falsey values rather than null
+
+Version 1.13.1
+
+- Fix packaging script
+
+Version 1.13.0
+
+- Add support for inline dataview fields (including emoji support for values)
+- Update dependencies
+
+Version 1.12.0
+
+- Add aspect ratio parameter for graphs
+- Reorganize release notes in readme to be in descending order (latest release first)
+
+Version 1.11.0
+
+- Add support for checkboxes in new properties added in Obsidian 1.4
+- Fix typos in documentation and examples
+
+Version 1.10.9
+
+- Replace tab characters by spaces
+- Accept more unicode characters in dvField
+- Allow emojis in the folder path
+- Fixed bugs
+
+Version 1.10.8
+
+- Fixed startDate/endDate misread as a relative date
+
+Version 1.10.7
+
+- Allow using html image tags as emoji inputs
+
+Version 1.10.6
+
+- Fixed the coloring for missing data in the month view
+
+Version 1.10.5
+
+- Allow using a relative date value in `initMonth` in the month view
+
+Version 1.10.4
+
+- Allow using a regular expression as a key of the parameter `textValueMap`
+- Add a parameter `shiftOnlyValueLargerThan` to determine when to do `valueShift`
+- Fixed bugs reported by users
+- Fixed typo in plugin settings
+
+Version 1.10.3
+
+- Allow using the parameter `fitPanelWidth` with the output type `month` and `pie`
+- Fixed the resizing and positioning of the chart tooltip
+
+Version 1.10.2
+
+- Fixed plugin not rendering on some macOS machines
+
+Version 1.10.1
+
+- Fixed 'failed to load plugin' on iOS
+
+Version 1.10.0
+
+- Add annotation mode for month view ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestCalendar.md))
+- Add parameters `xAxisTickInterval`, `yAxisTickInterval`, `xAxisTickLabelFormat` and `yAxisTickLabelFormat` for the line and bar chart ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestAxisIntervalAndFormat.md))
+- Allow using regular expression in parameter `dateFormatPrefix` and `dateFormatSuffix` ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md))
+- Add parameters `file`, `specifiedFilesOnly`, `fileContainsLinkedFiles`, and `fileMultiplierAfterLink` to retrieve data from specified files ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestSpecifiedFiles.md))
+- Add a parameter `textValueMap` to convert texts or emojis to specified values ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTextValueMap.md))
+- Fixed bugs
+- Enhanced error messages
+
+## !!! Breaking Changes !!!
+
+From version 1.9.0, template variables, e.g. '{{sum}}', are deprecated. Instead, Tracker provide operators (+, -, *, /, %) and functions (dataset(), sum(), maxStreak(), ......etc) to help us do data processing. For users having code blocks from previous version, please replace '{{sum}}' by '{{sum()}}' or '{{sum(1)}}' by '{{sum(dataset(1))}}'. More information about the new expressions could be found [here](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Expressions.md).
+
+## Usage
+
+1. Have some targets you want to track in daily notes.
+2. Add a new note for displaying the tracker.
+3. Add tracker code blocks manually ([examples](https://github.com/pyrochlore/obsidian-tracker/tree/master/examples)) or using [commands](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Commands.md).
+4. Switch the document view mode to 'Preview', then the code block will get rendered.
+
+For more use cases, please download and open the [examples](https://github.com/pyrochlore/obsidian-tracker/tree/master/examples) folder in obsidian with this plugin installed and enabled.
+
+## Development
+
+### Running Tests
+
+The plugin includes automated unit tests. To run them:
+
+```bash
+npm test # Run all tests
+npm run test:watch # Watch mode (re-runs on changes)
+npm run test:coverage # Generate coverage report
+```
+
+See [TESTING.md](TESTING.md) for detailed testing documentation.
+
+### Building
+
+```bash
+npm run build # Build for production
+npm run dev # Build in watch mode for development
+```
+
+## More Details You May Want to Know
+
+- [Installation](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Installation.md): Install the plugin from Obsidian or install it manually
+- [Concepts](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Concepts.md): Explain how this plugin works and what to setup
+ - [Target Evaluation](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TargetEvaluation.md)
+ - [Input Parameters](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/InputParameters.md)
+ - [Expressions](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Expressions.md)
+- [Examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Examples.md)
+- [Plugin Settings](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Settings.md)
+- [Release Notes](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/ReleaseNotes.md)
+- [Road Map](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/RoadMap.md)
+- [Frequently Asked Questions](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Questions.md)
+- [Testing](TESTING.md): How to run and write tests
+
+## Support
+
+- If you like this plugin or want to support further development, you can [Buy Me a Coffee](https://www.buymeacoffee.com/pyrochlore).
+- Please report bugs and request features in [GitHub Issues](https://github.com/pyrochlore/obsidian-tracker/issues)
diff --git a/TESTING.md b/TESTING.md
new file mode 100644
index 00000000..3aacda15
--- /dev/null
+++ b/TESTING.md
@@ -0,0 +1,187 @@
+# Testing Guide
+
+This document explains how to run tests for the obsidian-tracker plugin.
+
+## Quick Start
+
+```bash
+npm test
+```
+
+That's it! The tests will run automatically.
+
+## Test Commands
+
+```bash
+npm test # Run all tests once
+npm run test:watch # Run tests in watch mode (re-runs on file changes)
+npm run test:coverage # Run tests with coverage report
+```
+
+## What Gets Tested
+
+### Unit Tests
+
+Unit tests validate core logic without requiring Obsidian to be running. They test:
+
+- **Data collection functions** - How data is extracted from frontmatter, tags, etc.
+- **Parsing functions** - YAML configuration parsing and validation
+- **Edge cases** - Empty values, null, undefined, arrays, booleans, etc.
+
+### Current Test Coverage
+
+- `test/frontmatter-exists.test.ts` - Tests for the `frontmatter.exists` searchType
+ - 12 test cases covering all edge cases
+ - Validates non-empty strings, arrays, booleans, numbers
+ - Validates empty strings, arrays, null, undefined are rejected
+
+## Test Structure
+
+```
+test/
+├── frontmatter-exists.test.ts # Unit tests for frontmatter.exists
+├── mocks/
+│ ├── obsidian.ts # Mock Obsidian API
+│ └── d3.ts # Mock d3 library
+└── setup.ts # Jest setup file
+```
+
+## Prerequisites
+
+1. **Node.js** - Version 18+ recommended
+2. **npm** - Comes with Node.js
+3. **Dependencies** - Run `npm install` first
+
+## First Time Setup
+
+```bash
+# Install dependencies (including Jest)
+npm install
+
+# Verify tests work
+npm test
+```
+
+If you encounter issues with npm install (e.g., obsidian package integrity errors), see [Troubleshooting](#troubleshooting) below.
+
+## Writing New Tests
+
+### Example Test Structure
+
+```typescript
+import { describe, it, expect, beforeEach } from '@jest/globals';
+import { SearchType, Query } from '../src/data';
+import { yourFunction } from '../src/your-module';
+
+describe('Your Feature', () => {
+ let query: Query;
+
+ beforeEach(() => {
+ // Set up test data
+ query = new Query(0, SearchType.YourType, 'target');
+ });
+
+ it('should do something', () => {
+ const result = yourFunction(query);
+ expect(result).toBe(true);
+ });
+});
+```
+
+### Test File Naming
+
+- Test files should be named `*.test.ts` or `*.spec.ts`
+- Place them in the `test/` directory
+- Jest will automatically find and run them
+
+## Mocking Obsidian API
+
+Since tests run outside Obsidian, we mock the Obsidian API. See `test/mocks/obsidian.ts` for examples.
+
+To use mocks:
+
+```typescript
+import type { CachedMetadata } from 'obsidian';
+
+const fileCache: CachedMetadata = {
+ frontmatter: {
+ field: 'value'
+ }
+};
+```
+
+## Troubleshooting
+
+### npm install fails with obsidian package error
+
+If you see integrity check errors for the obsidian package:
+
+```bash
+# Clear npm cache
+npm cache clean --force
+
+# Remove node_modules and reinstall
+rm -rf node_modules package-lock.json
+npm install
+```
+
+### Tests fail with "Module not found"
+
+Make sure all dependencies are installed:
+
+```bash
+npm install
+```
+
+### TypeScript errors in tests
+
+Check that `ts-jest` is installed:
+
+```bash
+npm install --save-dev ts-jest@29 @types/jest@29
+```
+
+## Continuous Integration
+
+These tests can be run in CI/CD pipelines:
+
+```yaml
+# Example GitHub Actions
+- name: Run tests
+ run: npm test
+```
+
+## Manual Testing (Obsidian)
+
+For visual/end-to-end testing, you still need to test in Obsidian:
+
+1. Build the plugin: `npm run build`
+2. Copy to test vault: See `test-deploy.sh` or `TEST_SETUP.md`
+3. Enable plugin in Obsidian
+4. Test with real notes
+
+See `docs/dev/issue-497/TESTING_GUIDE.md` for detailed manual testing instructions.
+
+## Coverage Reports
+
+Generate coverage reports:
+
+```bash
+npm run test:coverage
+```
+
+This creates a `coverage/` directory with HTML reports. Open `coverage/lcov-report/index.html` in a browser to view.
+
+## Best Practices
+
+1. **Write tests for new features** - Add tests when implementing new searchTypes or features
+2. **Test edge cases** - Empty values, null, undefined, arrays, etc.
+3. **Keep tests fast** - Unit tests should run in seconds
+4. **Mock external dependencies** - Don't require Obsidian or real files
+5. **Use descriptive test names** - "should count non-empty strings" not "test1"
+
+## Questions?
+
+- See `TEST_SETUP.md` for setup troubleshooting
+- Check existing test files for examples
+- Review Jest documentation: https://jestjs.io/docs/getting-started
diff --git a/TEST_SETUP.md b/TEST_SETUP.md
new file mode 100644
index 00000000..f11d6308
--- /dev/null
+++ b/TEST_SETUP.md
@@ -0,0 +1,67 @@
+# Test Setup Guide
+
+## Quick Start
+
+Tests are now set up and working! Just run:
+
+```bash
+npm test
+```
+
+## First Time Setup
+
+If you're setting up the project for the first time:
+
+```bash
+# Install all dependencies (including Jest)
+npm install
+
+# Run tests to verify everything works
+npm test
+```
+
+## If npm install fails
+
+If you encounter integrity check errors with the `obsidian` package:
+
+```bash
+# Clear npm cache and reinstall
+npm cache clean --force
+rm -rf node_modules package-lock.json
+npm install
+```
+
+This should resolve the issue. The npm cache fix worked for us!
+
+## Test Files Created
+
+- `test/frontmatter-exists.test.ts` - Comprehensive unit tests for the new feature
+- `test/mocks/obsidian.ts` - Mock Obsidian API
+- `test/setup.ts` - Jest setup file
+- `jest.config.js` - Jest configuration
+
+## Running Tests
+
+Once Jest is installed:
+
+```bash
+npm test # Run all tests
+npm run test:watch # Watch mode
+npm run test:coverage # With coverage
+```
+
+## What the Tests Cover
+
+The `frontmatter-exists.test.ts` file tests:
+- ✅ Non-empty strings
+- ❌ Empty strings
+- ❌ Whitespace-only strings
+- ✅ Non-empty arrays
+- ❌ Empty arrays
+- ✅ Boolean true
+- ✅ Boolean false
+- ✅ Number zero
+- ❌ Null values
+- ❌ Missing/undefined fields
+- ❌ Missing frontmatter
+- ✅ Nested fields
diff --git a/docs/Commands.md b/docs/Commands.md
index b40905b8..94d5f5b1 100644
--- a/docs/Commands.md
+++ b/docs/Commands.md
@@ -1,5 +1,5 @@
# Commands
-To smooth the process of making trackers, obsidian-tracker provides three commands (There will be more in the future release), "Add Line Chart Tracker", "Add Bar Chart Tracker", and "Add Summary Tracker; Just type Ctrl/Cmd+P to activate the command palette, then type "Tracker" to search these commands.
+To smooth the process of making trackers, obsidian-tracker provides three commands (There will be more in the future release), "Add Line Chart Tracker", "Add Bar Chart Tracker", and "Add Summary Tracker". Just type Ctrl/Cmd+P to activate the command palette, then type "Tracker" to search these commands.
After a command is executed, a code block will be added to the next line below your cursor position for you. The added code block will contain the most frequently used keys. To see the complete list of input parameters and description, please check [this document](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/InputParameters.md).
\ No newline at end of file
diff --git a/docs/Concepts.md b/docs/Concepts.md
index 18b62ff0..bc554e0b 100644
--- a/docs/Concepts.md
+++ b/docs/Concepts.md
@@ -6,24 +6,26 @@ This plugin was designed to read code blocks in [YAML format](https://en.wikiped
### Collecting Data
-Providing parameters `searchType` and `searchTarget` is the minimum requirement for a successful data collection. `searchType` can be `tag`, `frontmatter`, `wiki`, `dvField`, `table`, `fileMeta`, `task`, or `text`. Then the cooresponding `searchTarget` should be provided according to the specified type.
+Providing parameters `searchType` and `searchTarget` is the minimum requirement for a successful data collection. `searchType` can be `tag`, `frontmatter`, `frontmatter.exists`, `frontmatterlist`, `wiki`, `dvField`, `table`, `fileMeta`, `task`, or `text`. Then the cooresponding `searchTarget` should be provided according to the specified type.
### Target Evaluation
-Depends on the `searchType` and the `searchTarget` you provided, the evaluation of a target would be different. Simply speaking, you can track the occurrences of a target or the value attached/embedded in it.
+Depending on the `searchType` and the `searchTarget` you provided, the evaluation of a target would be different. Simply speaking, you can track the occurrences of a target or the value attached/embedded in it.
To see the detail about the target evaluation, please check the document [Target Evaluation](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TargetEvaluation.md).
### Rendering Output
-Currently, obsidian-tracker provides five kinds of rendering output: `line`, `bar`, `summary`, `bullet`, and `month`. You have to provide at least one output parameter in a code block.
+Currently, obsidian-tracker provides five kinds of rendering output: `line`, `bar`, `summary`, `bullet`, `month` and `pie`. You have to provide at least one output parameter in a code block.
-With output set to `line` or `bar`, Tracker plugin will generate a customizable chart. These charts are very good at seeing the variation of collected number in the notes.
+With output type set to `line` or `bar`, Tracker plugin will generate a customizable chart. These charts are very good at seeing the variation of collected number in the notes.
-With the `summary` output, a text block based on your '**template**' parameter will be created. You can use [pre-defined template variables](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TemplateVariables.md) like '{{sum}}' or '{{maxStreak}}' in the template parameter, to get a statistical summary of collected data.
+With the output type `summary`, a text block based on your '**template**' parameter will be created. You can use [expressions](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Expressions.md) like '{{sum()}}' or '{{maxStreak()}}' in the template parameter, to get a statistical summary of collected data.
-`bullet` output creates a [bullet chart](https://en.wikipedia.org/wiki/Bullet_graph) and could serve as a gauge showing the status (level, performance, progress) of a dataset.
+Output type `bullet` creates a [bullet chart](https://en.wikipedia.org/wiki/Bullet_graph) and could serve as a gauge showing the status (level, performance, progress) of a dataset.
-`month` output creates a month view with circled dates exceeding the given threshold and streaks showing how long it persisted.
+Output type `month` creates a month view with circled dates exceeding the given threshold and streaks showing how long it persisted.
+
+Output type `pie` creates a pie chart. The `data` parameter should be applied for circular sectors you want to add. Parameter `label` and `extLabel` are used for displaying labels and `dataName` is used for the diplay names on legend.
Detailed description for all parameters of the output types can be found [here](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/InputParameters.md).
\ No newline at end of file
diff --git a/docs/Examples.md b/docs/Examples.md
index 30f832a1..efd0593d 100644
--- a/docs/Examples.md
+++ b/docs/Examples.md
@@ -1,10 +1,12 @@
# Examples
-The following table lists use cases with information of target location (Location), target to track (Target to Track), simplified tracker block (Tracker), and what kind of data you will get ((((O)ccurence/(V)alue). Various kinds of full examples can also be found in the following section after the table.
+We provide a simplified table of use cases and full examples including data.
## Table of Use Cases
-| Location | Target to Track | Tracker | Get (O)ccurrence/(V)alue |
+Check where (Location) and what (Target to Track) is your target and find the settings (Tracker) you need.
+
+| Location | Target to Track | Tracker | Get (O)ccurrences/(V)alues |
|:--------|:-------|:---------|:--:|
| content | #meditation | searchType: tag
searchTarget: meditation | O |
| frontmatter | ---
tags: meditation
--- | searchType: tag
searchTarget: meditation | O |
@@ -23,44 +25,50 @@ The following table lists use cases with information of target location (Locatio
| content | [[journal]] | searchType: wiki
searchTarget: journal | O |
| content | ⭐ | searchType: text
searchTarget: ⭐ | O |
| content | love | searchType: text
searchTarget: love | O |
-| content | test@gmail.com
test@hotmail.com | searchType: text
serchTarget: '.+\\@.+\\..+' | O |
+| content | test@gmail.com
test@hotmail.com | searchType: text
searchTarget: '.+\\@.+\\..+' | O |
| content | #weightlifting: 50 | searchType: text
searchTarget: 'weightlifting: (?\[\\-]?[0-9]+[\\.][0-9]+\|[\\-]?[0-9]+)' | V |
| content | I walked 10000 steps today. | searchType: text
searchTarget: 'walked\\s+(?\[0-9]+)\\s+steps' | V |
| content | myvalues 1/2/3 | searchType: text
searchTarget: 'myvalues\\s+(?\[0-9]+)/([0-9]+)/([0-9]+), myvalues\\s+([0-9]+)/(?\[0-9]+)/([0-9]+), myvalues\\s+([0-9]+)/([0-9]+)/(?\[0-9]+)' | V |
| table content | { a table filled with dates and values }
[example table](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/data/Tables.md) | searchType: table
searchTarget: filePath[0][0], filePath[0][1] | V |
-| talbe content | { a table filled with dates and values }
[example table](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/data/Tables.md) | searchType: table
searchTarget: filePath[1][0], filePath[1][1][0], filePath[1][1][1] | V |
+| table content | { a table filled with dates and values }
[example table](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/data/Tables.md) | searchType: table
searchTarget: filePath[1][0], filePath[1][1][0], filePath[1][1][1] | V |
| file meta | meta data from files
(size, cDate, mDate, numWords, numChars, numSentences) | searchType: fileMeta
searchTarget: size | V |
| content | - [x] Say love
- [ ] Say love | searchType:task
searchTarget: Say love | O |
| content | - [x] Say love | searchType:task.done
searchTarget: Say love | O |
| content | - [ ] Say love | searchType: task.notdone
searchTarget: Say love | O |
+| frontmatter | habits: spanish, piano, yoga
([more examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TargetEvaluation.md#Formatting-property-values-in-frontmatter))| searchType: frontmatterlist
searchTarget: habits[spanish] | O |
## Full examples
-Full tracker code blocks can be found in [this folder](https://github.com/pyrochlore/obsidian-tracker/tree/master/examples) and the corresponding notes can be found under folder 'diary' and 'data'.
+Full tracker code blocks can be found in folder [examples](https://github.com/pyrochlore/obsidian-tracker/tree/master/examples) and the corresponding notes (data) can be found under folder '[diary](https://github.com/pyrochlore/obsidian-tracker/tree/master/examples/diary)' and '[data](https://github.com/pyrochlore/obsidian-tracker/tree/master/examples/data)'.
List of all examples
- [Bloodpressure Tracker](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/BloodPressureTracker.md)
- [Error Messages](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/ErrorMessages.md)
- [Finance Tracker](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/FinanceTracker.md)
-- [Habit Trakcer](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/HabitTracker.md)
-- [Mood Tracker](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/MoodTracker.md)
+- [Habit Tracker](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/HabitTracker.md)
- [Star Tracker](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/StarTracker.md)
- [Bar Chart](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestBarChart.md)
+- [Axis Interval and Format](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestAxisIntervalAndFormat.md)
- [Bullet Chart](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestBullet.md)
- [Calendar](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestCalendar.md)
- [Date Formats](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md)
- [Dataview Inline Field](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDvField.md)
+- [Expression](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestExpression.md)
- [File Meta](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestFileMeta.md)
+- [Frontmatter Lists](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestFrontmatterList.md) (New!)
- [Legends](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestLegends.md)
- [Multiple Targets / Multiple Values](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestMultipleTargetsMultipleValues.md)
+- [Pie Chart](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestPieChart.md)
- [Scaling and Positioning](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestScalingAndPositioning.md)
+- [Specified Files](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestSpecifiedFiles.md)
+- [Summary](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestSummary.md)
- [Table](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTable.md)
- [Task](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTask.md)
-- [Template Variables](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTemplateVariables.md)
+- [Text-value Map/Mood Tracker](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTextValueMap.md)
- [Time Values](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTimeValues.md)
- [Word Counting](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestWordCounting.md)
- [X Dataset](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestXDataset.md)
-- [Regular Expression](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TrackUsingRegex.md)
+- [Regular Expression](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestRegex.md)
- [Weight Tracker](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/WeightTracker.md)
- [Wiki](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/WikiTracker.md)
diff --git a/docs/Expressions.md b/docs/Expressions.md
new file mode 100644
index 00000000..f366067b
--- /dev/null
+++ b/docs/Expressions.md
@@ -0,0 +1,99 @@
+# Expressions
+
+Expressions could help us create new and meaningful data from the original collected data by using operators and functions.
+
+## !!! Breaking Changes !!!
+
+From version 1.9.0, template variables, e.g. '{{sum}}', are deprecated. Instead, Tracker provide operators (+, -, *, /, %) and functions (dataset(), sum(), maxStreak(), ......etc) to help us do data processing. For users having code blocks from previous version, please replace '{{sum}}' by '{{sum()}}' or '{{sum(1)}}' by '{{sum(dataset(1))}}'.
+
+## Where to Use
+
+Currently, we can only use expressions in some parameters. These includes `template` in `summary` output, `value` in `bullet` output, and `data` `label` `extLabel` in `pie` output. In future release, there will be more parameters using expressiones as input.
+
+## How to Use
+
+Expressions should be be wrapped in curly brackets. By using the combination of operators and funtions, Tracker can resolve the whole expression in brackets and then generate a number or a string according to what was requested.
+
+If the resolved output of an expression is a string, we can assign a format string to it. The format string should be placed after the expression in curly brackets following by two colons. For example, The expression '{{sum()::i}}' will force the output number represented as an integer (i for integer).
+
+For the number output, use '[Printf Format String](https://en.wikipedia.org/wiki/Printf_format_string)' for the format string. For the date output, use the date format string defined in [Moment.js](https://momentjscom.readthedocs.io/en/latest/moment/04-displaying/01-format/).
+
+The following tables show all the operators and functions available for now. Please make sure the input type and output type when you are combining them together. Examples could be found [here](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestExpression.md). Requests for operators or functions are welcome.
+
+## List of Operators
+
+### Uniry Operators
+
+| Function | Description | Operant | Output |
+|:---------|:------------|:--------|:------|
+| + | positive | number or dataset | number or dataset |
+| - | negative | number or dataset | number or dataset |
+
+### Binary Operators
+
+| Function | Description | Left Operant | Right Operant | Output |
+|:---------|:------------|:-----|:------|:-------|
+| + | plus | number or dataset | number or dataset | number or dataset |
+| - | minus | number or dataset | number or dataset | number or dataset |
+| * | multiply | number or dataset | number or dataset | number or dataset |
+| / | divide | number or dataset | number or dataset | number or dataset |
+| % | modulo | number or dataset | number or dataset | number or dataset |
+
+e.g.
+- number + number --> number
+- dataset + number --> dataset
+- number + dataset -> dataset
+- dataset + dataset --> dataset
+
+## List of Functions
+
+### Get Dataset by Index
+
+| Function(InputType): OutputType | Description |
+|:------------------|:-----------|
+| dataset(number): Dataset | Get dataset from dataset id (the order in `searchTarget`) |
+
+### Functions Accept Dataset and return a value
+
+**If the input dataset is missing, it will use the first available Y dataset found.**
+
+| Function(InputType): OutputType | Description |
+|:------------------|:-----------|
+| first(Dataset): number | First value of the dataset |
+| last(Dataset): number | Last value of the dataset |
+| min(Dataset): number | Minimum value of the dataset |
+| minDate(Dataset): Date | Latest date of minimum value |
+| max(Dataset): number | Maximum value of the dataset |
+| maxDate(Dataset): Date | Latest date of maximum value |
+| startDate(Dataset): Date | Start date of the dataset |
+| endDate(Dataset): Date | End date of the dataset |
+| sum(Dataset): number | Summation of values of the dataset |
+| numTargets(Dataset): number | Total counts of targets |
+| numDays(Dataset): number | Days from startDate to endDate |
+| numDaysHavingData(Dataset): number | Number of days having data |
+| maxStreak(Dataset): number | Maximum continuous days without breaks |
+| maxStreakStart(Dataset): Date | Start date of the max streak |
+| maxStreakEnd(Dataset): Date | End date of the max streak |
+| maxBreaks(Dataset): number | Maximum break days |
+| maxBreaksStart(Dataset): Date | Start date of the maximum break days |
+| maxBreaksEnd(Dataset): Date | End date of the maximum break days |
+| currentStreak(Dataset): number | Current continuous days |
+| currentStreakStart(Dataset): Date | Start date of current streak |
+| currentStreakEnd(Dataset): Date | End date of current streak |
+| currentBreaks(Dataset): number | Current break days |
+| currentBreaksStart(Dataset): Date | Start date of current breaks |
+| currentBreaksEnd(Dataset): Date | End date of current breaks |
+| average(Dataset): number | Average value of the dataset |
+| median(Dataset): number | Median value of the dataset |
+| variance(Dataset): number | Variance value of the dataset |
+
+### Functions Accept Dataset and Return Dataset
+
+| Function(InputType): OutputType | Description |
+|:---------|:-----------|
+| normalize(Dataset): Dataset | rescale the Y values to [0, 1] |
+| setMissingValues(Dataset, number): Dataset | set the missing values |
+
+## Missing Values
+
+Notice that the missing values (null values) are ignored in function like sum or average. Moreover, a value plus a missing value will leads to null value (missing value). To avoid these, you can set those missing values to a value by using parameter `penalty` or use expression function `setMissingValues`.
diff --git a/docs/InputParameters.md b/docs/InputParameters.md
index 018d3be6..d174590b 100644
--- a/docs/InputParameters.md
+++ b/docs/InputParameters.md
@@ -1,142 +1,185 @@
# Input Parameters
-Obsidian-tracker parses key-value pairs in your code block in YAML format and uses them as input parameters. The minimum requirements for parameters are `searchType`, `searchTarget` and one output parameter (`line`, `bar`, `frontmatter`, `fileMeta`, `wiki`, `table`, `task`, or `text`).
-## Array Input
+Obsidian-tracker parses key-value pairs in YAML format in your code block and uses them as input parameters. The minimum requirements for parameters are `searchType`, `searchTarget` and at least one output parameter (`line`, `bar`, `summary`, `bullet`, `month`, or `pie`).
-Some of the parameters can accept more than one value for each target, thus the maximum number of values of the parameter equals the number of targets (NT). If the number of values is less than the number of targets, the plugin will use the previously provided value or use the default value if nothing is provided.
+## Array Input for a Parameter
-Also, some y-axis related parameters for chart (`line` or `bar`), like `yMin`, `yMax`, and `yAxisLabel` accept one value for each y-axis (`left` and `right`). If you only use one axis, or the values for the two axes are the same, only one value is required. If you need the two axes to have different values, assign two values to them. The first one will be used for the left axis and the second one for the right axis.
+Some of the parameters can accept more than one value for each target. For those parameters accept different value for each given search target, the maximum number of values should equal to the number of search target (NT). If the number of values are less than the number of targets, Tracker will use the previously provided one in sequence or use the default value if nothing is provided.
-To enter array of values, Use YAML array (e.g. ['value1', 'value2', 'value3']) or simply values separated by comma (e.g. value1, value2, value3). The second method is a syntax surgar of Tracker to simplify the inputs. If YAML special characters are included, be sure to wrap the whole values by single quotes (e.g. 'value1, value2, value3'). Please also check [this](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TargetEvaluation.md) for more information about YAML in Tracker.
+For Y axis related parameters, like `yMin`, `yMax`, or `yAxisLabel`, they accept one value for each Y axis (`left` and `right`). If you only use one axis, or the values for the two axes are the same, only one value is required. If you need the two axes to have different values, provide two values to do the work. The first one will be used for the left Y axis and the second one for the right Y axis.
+
+To enter array of values, we can use YAML array (e.g. ['value1', 'value2', 'value3']) or simply values separated by comma (e.g. value1, value2, value3). The second method is a syntax surgar of Tracker to simplify input process. If YAML special characters are required in the inputs, be sure to wrap the whole values by single quotes (e.g. 'value1, value2, value3'). Please also check [this](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/YAML.md) for more information about YAML in Tracker.
## List of Parameters
### Root Parameters
-These key-value pairs are placed under the code block root.
+These key-value pairs are placed under the root of the code block.
| Key | Description | Number of Values | Default |
|:--------|:-------|:-----------:|:------|
-| `searchType` | The type of `searchTarget` (tag\|frontmatter\|wiki\|text\|dvField\|table\|filemeta\|task) | 1~NT | Must be provided |
-| `searchTarget` | The target to search
[[detail](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TargetEvaluation.md)] | NT (Number of Targets) | Must be provided |
-| `folder` | The root path of notes to search | 1 | Root of this vault |
-| `dateFormat` | The date format you are using
or use [iso-8601](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#iso-8601-date-format) | 1 | 'YYYY-MM-DD' |
-| `dateFormatPrefix` | The prefix before your dateFormat | 1 | '' |
-| `dateFormatSuffix` | The suffix after your dateFormat | 1 | '' |
-| `startDate` | The start date to collect data
accept [relative date](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#relative-date-input-for-startdate-and-enddate) | 1 | Min date found |
-| `endDate` | The end date of to collect data
accept [relative date](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#relative-date-input-for-startdate-and-enddate) | 1 | Max date found |
-| `datasetName` | The name of the dataset for a search target` | 1~NT | untitled |
-| `separator` | The character to separate multiple values appearing in the search target | 1~NT | '/' |
-| `xDataset` | The `searchTarget` of this index will be used as xDataset | 1~NT | -1 (use filename as xDataset) |
-| `constValue` | The constant value of a target if no value attached | 1~NT | 1.0 |
-| `ignoreAttachedValue` | Use a constant value even if the target has a value attached on (true\|false) | 1~NT | false |
+| `searchType` | Type of `searchTarget` (tag\|frontmatter\|frontmatter.exists\|wiki\|text\|dvField\|table\|fileMeta\|task) | 1~NT | Must be provided |
+| `searchTarget` | Target to search
[[detail](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TargetEvaluation.md)] | NT (Number of Targets) | Must be provided |
+| `folder` | Root path containing notes to search | 1 | Root of this vault |
+| `file` | Files to include for searching | N | null |
+| `specifiedFilesOnly` | Ignore files found in `folder` | 1 | false |
+| `fileContainsLinkedFiles` | Include the linked files in the specified files here | N | null |
+| `fileMultiplierAfterLink` | Regex string include named group 'value'
to search the multiplier after link | 1 | '' |
+| `dateFormat` | Date format
Use [Moment.js](https://momentjscom.readthedocs.io/en/latest/moment/04-displaying/01-format/) format or use [iso-8601](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#iso-8601-date-format) | 1 | 'YYYY-MM-DD' |
+| `dateFormatPrefix` | Prefix before your dateFormat (accept regex) | 1 | '' |
+| `dateFormatSuffix` | Suffix after your dateFormat (accept regex) | 1 | '' |
+| `startDate` | Start date to collect data from
accept [relative date](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#relative-date-input-for-startdate-and-enddate) | 1 | Min date found |
+| `endDate` | End date of to collect data
accept [relative date](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#relative-date-input-for-startdate-and-enddate) | 1 | Max date found |
+| `datasetName` | Name of the dataset for a search target` | 1~NT | untitled |
+| `separator` | Character used to separate multiple values appearing in the search target | 1~NT | '/'
',' in front matter tags |
+| `xDataset` | Index of `searchTarget` used as xDataset | 1~NT | -1 (use filename as xDataset) |
+| `constValue` | Constant value of a target if no value attached | 1~NT | 1.0 |
+| `ignoreAttachedValue` | Use `constValue` even if the target has a value attached on (true\|false) | 1~NT | false |
| `ignoreZeroValue` | Treat zero value as missing (true\|false) | 1~NT | false |
| `accum` | Accumulatively sum the values over time (true\|false) | 1~NT | false |
-| `penalty` | The value to use if the search target is missing on the day | 1~NT | |
-| `valueShift` | The amount to shift the collected values | 1~NT | 0 |
+| `stack` | Support stacked charts (true\|false) | 1 | false |
+| `penalty` | Value to use if the search target is missing on the day | 1~NT | |
+| `valueShift` | Amount to shift for each collected value | 1~NT | 0 |
+| `shiftOnlyValueLargerThan` | Do `valueShift` only if the value is larger then the specifed one | 1~NT | null |
| `valueType` | Not implemented yet | 1~NT | |
-| `fixedScale` | The scaling factor apply to the chart | 1 | 1.0 |
+| `textValueMap` | A container key for multiple text-value mapping | | |
+| `fixedScale` | Uniform scaling factor to the graph dimensions | 1 | 1.0 |
| `fitPanelWidth` | Auto-fit the width of the chart to the container | 1 | false |
-| `margin` | The four margin (top|right|bottom|left) of the graph | 1~4 | 10 |
+| `aspectRatio` | Change the 1:1 aspect ratio of the graph | number:number | 1:1 |
+| `margin` | Four margins (top\|right\|bottom\|left) of the graph | 1~4 | 10 |
| `line` | A container key for parameters related to the line chart | | |
| `bar` | A container key for parameters related to the bar chart | | |
| `summary` | A container key for parameters related to the summary output | | |
-| `bullet` | A container key for parameters related to the bullet chart |
+| `bullet` | A container key for parameters related to the bullet chart | | |
+| `month` | A container key for parameters related to the month view | | |
+| `pie` | A container key for parameters related to the pie chart | | |
-### Parameters for Common Chart
+### Parameters for Common Charts
These key-value pairs should be placed under the key `line` or `bar`.
| Key | Description | Number of Values | Default |
|:--------|:-------|:-----------:|:------|
-| `title` | The title of the chart | 1 | '' |
-| `xAxisLabel` | The label of X axis | 1 | 'Date' |
-| `xAxisColor` | The color of X axis | 1 | 'white'('black'*) |
-| `xAxisLabelColor` | The color of X axis label | 1 | 'white'('black'*) |
-| `yAxisLabel` | The label of Y axis | 1~2 | 'Value' |
-| `yAxisColor` | The color of Y axis | 1~2 | 'white'('black'*) |
-| `yAxisLabelColor` | The color of Y axis label | 1~2 | 'white'('black'*) |
-| `yAxisUnit` | The unit displayed aside Y axis label | 1~2 | '' |
-| `yMin` | The minimum value on Y axis | 1~2 |Minimum Y value found |
-| `yMax` | The maximum value on Y axis | 1~2 | Maximum Y value found |
-| `reverseYAxis` | Flip the Y Axis or not (true\|false) | 1~2 | false |
+| `title` | Title of this chart | 1 | '' |
+| `xAxisLabel` | Label of X axis | 1 | 'Date' |
+| `xAxisColor` | Color of X axis | 1 | 'white'('black'*) |
+| `xAxisLabelColor` | Color of X axis label | 1 | 'white'('black'*) |
+| `yAxisLabel` | Label of Y axis | 1~2 | 'Value' |
+| `yAxisColor` | Color of Y axis | 1~2 | 'white'('black'*) |
+| `yAxisLabelColor` | Color of Y axis label | 1~2 | 'white'('black'*) |
+| `yAxisUnit` | Unit displayed aside Y axis label | 1~2 | '' |
+| `xAxisTickInterval` | X axis interval between ticks | 1~2 | null |
+| `xAxisTickLabelFormat` | Format of tick label on X axis
| 1~2 | null |
+| `yAxisTickInterval` | Y axis interval between ticks | 1~2 | null |
+| `yAxisTickLabelFormat` | Format of tick label on Y axis
| 1~2 | null |
+| `yMin` | Minimum value on Y axis | 1~2 |Minimum Y value found |
+| `yMax` | Maximum value on Y axis | 1~2 | Maximum Y value found |
+| `reverseYAxis` | Flip (upside down) the Y Axis or not (true\|false) | 1~2 | false |
| `allowInspectData` | Show data value when mouse hovered (true\|false) | 1 | true |
-| `showLegend` | Show legend (true\|false) | 1 | false |
+| `showLegend` | Show/Hide legend (true\|false) | 1 | false |
| `legendPosition` | Legend position (top\|bottom\|left\|right) | 1 | bottom |
| `legendOrientation` | Legend orientation (vertical\|horizontal) | 1 | horizontal for bottom and top
vertical for left and right |
| `legendBgColor` | Legend background color | 1 | none |
| `legendBorderColor` | Legend border color | 1 | white |
-### Parameters for Line Chart
+### Parameters for a Line Chart
These key-value pairs should be placed under the key `line`.
| Key | Description | Number of Values | Default |
|:--------|:-------|:-----------:|:------|
-| `lineColor` | The color of the lines in chart | 1~NT | 'white'('black'*) |
-| `lineWidth` | The width of the lines in chart | 1~NT | 1.5 |
-| `showLine` | Show lines (true\|false) | 1~NT | true |
-| `showPoint` | Show data points (true\|false) | 1~NT | true |
-| `pointColor` | The color of data points | 1~NT | #69b3a2 |
-| `pointBorderColor` | The border color of data points | 1~NT | #69b3a2 |
-| `pointBorderWidth` | The border width of data points | 1~NT | 0 |
-| `pointSize` | The size of data points | 1~NT | 3 |
+| `lineColor` | Color of the lines in the chart | 1~NT | 'white'('black'*) |
+| `lineWidth` | Width of the lines in the chart | 1~NT | 1.5 |
+| `showLine` | Show/hide lines (true\|false) | 1~NT | true |
+| `showPoint` | Show/hide data points (true\|false) | 1~NT | true |
+| `pointColor` | Color of data points | 1~NT | #69b3a2 |
+| `pointBorderColor` | Border color of data points | 1~NT | #69b3a2 |
+| `pointBorderWidth` | Border width of data points | 1~NT | 0 |
+| `pointSize` | Radius of data points | 1~NT | 3 |
| `fillGap` | Connect points over missing data (true\|false) | 1~NT | false |
-| `yAxisLocation` | The corresponding Y axis for a specific dataset (left\|right) | 1~NT | left |
+| `yAxisLocation` | Corresponding Y axis for the dataset (left\|right) | 1~NT | left |
-### Parameters for Bar Chart
+### Parameters for a Bar Chart
These key-value pairs should be placed under the key `bar`.
| Key | Description | Number of Values | Default |
|:--------|:-------|:-----------:|:------|
-| `barColor` | The color of bars in chart | 1~NT | #69b3a2 |
-| `yAxisLocation` | The corresponding y-axis for a specific dataset (left\|right) | 1~NT | left |
+| `barColor` | Color of bars in the chart | 1~NT | #69b3a2 |
+| `xAxisPadding` | Padding to ensure bars are fully displayed | 1~2 | null |
+| `yAxisLocation` | Corresponding y-axis for the dataset (left\|right) | 1~NT | left |
-### Parameters for Summary
+### Parameters for a Summary
These key-value pairs should be placed under the key `summary`.
| Key | Description | Number of Values | Default |
|:--------|:-------|:-----------:|:------|
-| `template` | Text template (you may embed [template variables](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TemplateVariables.md) | 1 | '' |
-| `style` | CSS style applied the rendered text block | 1 | '' |
+| `template` | Text template (you may embed [expressions](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Expressions.md) | 1 | '' |
+| `style` | CSS style applied to the rendered text block | 1 | '' |
-### Parameters for Bullet Chart
+### Parameters for a Bullet Chart
These key-value pairs should be placed under the key `bullet`.
| Key | Description | Number of Values | Default |
|:--------|:-------|:-----------:|:------|
-| `title` | The title of the chart | 1 | '' |
-| `dataset` | The index of the dataset of your interest | 1 | 0 |
+| `title` | Title of this chart | 1 | '' |
+| `dataset` | Index of the dataset of your interest | 1 | 0 |
| `orientation` | Bar orientation (horizontal\|vertical) | 1 | 'horizontal' |
-| `value` | The actual value of interest
(you may embed [template variables](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TemplateVariables.md) | 1 | '' |
-| `valueUnit` | The unit of value displayed aside | 1 | '' |
-| `valueColor` | The color of the value bar | 1 | '#69b3a2' |
-| `range` | The ranges of color bands in background | N | [] |
-| `rangeColor` | The color of range bands | N | [] |
-| `showMarker` | Show marker or not (true\|false) | 1 | true |
-| `markerValue` | The value of the markder | 1 | 0 |
-| `markerColor` | The color of the marker | 1 | 'black' |
-
-### Parameters for Month View
+| `value` | Actual value of interest
(you may embed [expressions](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Expressions.md) | 1 | '' |
+| `valueUnit` | Unit of the Y value displayed aside | 1 | '' |
+| `valueColor` | Color of the value bar | 1 | '#69b3a2' |
+| `range` | Data anges of defined by series of numbers | N | [] |
+| `rangeColor` | Color of the range bands | N | [] |
+| `showMarker` | Show/hide the marker line (true\|false) | 1 | true |
+| `markerValue` | Value of the markder | 1 | 0 |
+| `markerColor` | Color of the marker | 1 | 'black' |
+
+### Parameters for a Month View
These key-value pairs should be placed under the key `month`.
| Key | Description | Number of Values | Default |
|:--------|:-------|:-----------:|:------|
-| `dataset` | The index of the dataset of your interest | 1~NT | all indices of non-x searchTarget |
-| `startWeekOn` | First day of a week ('Sun'\|'Mon') | 1 | 'Sun' |
-| `threshold` | The threshold to determine showing a circle on a day or not | 1~NT | 0 |
+| `mode` | Pick one mode of the two(circle\|annotation) | 1 |
+| `dataset` | Index of the dataset of your interest | 1~NT | all indices of non-x searchTarget |
+| `startWeekOn` | First day of a week ('Sun'\|'Mon'\|'Tue'\|'Wed'\|'Thu'\|'Fri'\|'Sat' or full names like 'Sunday', 'Monday', etc.) | 1 | 'Sun' |
+| `threshold` | Threshold to determine showing a circle on a day or not | 1~NT | 0 |
+| `thresholdType` | Pick one of the two (GreaterThan\|LessThan) | 1 | GreaterThan
| `yMin` | Minimum value | 1~NT | Minimum value of the dataset |
| `yMax` | Maximum value | 1~NT | Maximum value of the dataset |
-| `showCircle` | Circle the day label if the collected value reach the threshold (value > threshold) | 1 | true |
+| `showCircle` | Circle the day label if the collected value reach the threshold (value > `threshold`) | 1 | true |
| `color` | Main color (can be override by other color parameters) | 1 | null |
| `dimNotInMonth` | Dim the color for days not in current month | 1 | true |
-| `showStreak` | Show streaks between circles | 1 | true |
-| `showTodayRing` | Show a ring on the label today | 1 | true |
-| `showSelectedValue` | Show the value on the selected day | 1 | true |
-| `showSelectedRing` | Show a ring on the label of the selected day | 1 | true |
-| `circleColor` | The color of circles | 1 | '#69b3a2' |
-| `circleColorByValue` | Display colors based on the value | 1 | 1 |
-| `headerYearColor` | The color of year text in header | 1 | 'white' |
-| `headerMonthColor` | The color of the month text in header | 1 | 'white' |
-| `dividingLineColor` | The color of the dividing line | 1 | '#69b3a2' |
-| `todayRingColor` | The color of the ring on today | 1 | 'white' |
-| `selectedRingColor` | The color of the ring on the selected day | 1 | 'firebrick' |
-| `initMonth` | Initial month to show (YYYY-MM) | 1 | last month found |
\ No newline at end of file
+| `showStreak` | Show/hide streaks between circles | 1 | true |
+| `showTodayRing` | Show/hide the ring on the label today | 1 | true |
+| `showSelectedValue` | Show/hide the value on the selected day | 1 | true |
+| `showSelectedRing` | Show/hide a ring on the label of the selected day | 1 | true |
+| `circleColor` | Color of circles | 1 | '#69b3a2' |
+| `circleColorByValue` | Display circle colors based on the value | 1 | false |
+| `circleColorByStreak` | Display circle colors based on the streak length | 1 | false |
+| `headerYearColor` | Color of the year text in header | 1 | 'white' |
+| `headerMonthColor` | Color of the month text in header | 1 | 'white' |
+| `dividingLineColor` | Color of the dividing line | 1 | '#69b3a2' |
+| `todayRingColor` | Color of the ring on today | 1 | 'white' |
+| `selectedRingColor` | Color of the ring on the selected day | 1 | 'firebrick' |
+| `initMonth` | Initial month to show (YYYY-MM) | 1 | last month found |
+| `showAnnotation` | Show/hide annotation | 1 | false |
+| `annotation` | Annotation for each piece of data | NT | '' |
+| `showAnnotationOfAllTargets` | Show annotation of all targets at the same time | 1 | false |
+
+
+### Parameters for Pie Chart
+These key-value pairs should be placed under the key `pie`.
+
+| Key | Description | Number of Values | Default |
+|:--------|:-------|:-----------:|:------|
+| `title` | Title of this chart | 1 | '' |
+| `data` | Array of values, each represents the number or fraction of a circular sector | N | '' |
+| `dataColor` | Color of each circular sector | N | '' |
+| `dataName` | Name of each data shown on legend | N | |
+| `label` | Labels for each data shown on circular sector | N | |
+| `hideLabelLessThan` | Hide the label with its fraction number lower than | 1 | 0.03 |
+| `showExtLabelOnlyIfNoLabel` | Show/hide the external label only if the correstponding label is missing or empty (true\|false) | 1 | false |
+| `extLabel` | Labels for each data shown aside out of circular sector | N | |
+| `ratioInnerRadius` | Ratio of donut inner radius to pie radius | 1 | 0 |
+| `showLegend` | Show/hide legend (true\|false) | 1 | false |
+| `legendPosition` | Legend position (top\|bottom\|left\|right) | 1 | right |
+| `legendOrientation` | Legend orientation (vertical\|horizontal) | 1 | horizontal for bottom and top
vertical for left and right |
+| `legendBgColor` | Legend background color | 1 | none |
+| `legendBorderColor` | Legend border color | 1 | white |
diff --git a/docs/Questions.md b/docs/Questions.md
index 285c8e5b..94cafd37 100644
--- a/docs/Questions.md
+++ b/docs/Questions.md
@@ -1,47 +1,32 @@
# Frequently Asked Questions
-- Does Tracker only track data in daily notes (notes named by dates)?
+- Does Tracker only track data in daily notes (file names contain dates)?
- No. From version 1.6.0, you can use collect date data from file meta data with search type `fileMeta` and use that dataset as `xDataset`. So any notes can be included.
+ No. The file name of your notes could be any string. But we do need a date from each file. If it is not from the file name, we should add one more `searchTarget` and use that target as the source of X values by setting parameter `xDataset` to its index. The searchType `fileMeta` with `searchTarget` cDate (creation date) and mDate (modification date) are always accessible as date sources if you don't have any. Examples of these use cases could be found [here](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestXDataset.md).
-- Does Tracker only track data over dates?
+- Why my line chart looks broken (not connected) at some points?
- Yes. The data type of x values should be in the form of date for now. This constrain might be relaxed in future.
+ Tracker only connects adjacent points (neighbor points by date) by default. To force it connecting points over missing data, set the parameter `fillGap` under `line` or `bar` to true.
-- Why my line chart looks broken at some points?
+- Why does the plugin show: error 'No valid date as X value found in notes'?
- The plugin only connects adjacent points (by date) by default. To force it connecting points over missing data, set the parameter `fillGap` under `line` to true.
+ First we have to confirm where is the source of your X values. Tracker always needs X values in dates. The default source of X values are the file names of your notes. As long as a proper `dateFormat` was assigned, and combine with `dateFormatPrefix` and `dateFormatSuffix`, the dates in file names could be extracted from your file names successfully.
-- Why the plugin shows 'No notes found under the given search condition'?
+ If the date values are from front matter, dataview inline field, or other places, choose the right `searchType` and `searchTarget` and mark them as `xDataset`, Tracker will collect X values for you.
- There are few possibilities for this error messages.
- 1. No files in the given folder
- 2. No files or x data values matched the dateFormat you gave in the given folder
- 3. No files in the date range you gave (from startDate to endDate)
+ If you don't have any date values, and you just want to count the number of occurrences of a target. As a trick, you can use the creation date (cDate) or modification date (mDate) of the file as X data source.
-- Why the plugin shows 'Error parsing YAML'?
+ Examples of these use cases could be found [here](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestXDataset.md).
- There are syntax errors in your code block. Please check [this document](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/YAML.md) for common mistakes.
+- Why does the plugin show: error 'No valid Y value found in notes'?
-- Why no data (points or lines) is shown in my chart?
+ That means no matched data found in your notes. Please check the document for the detail of [target evaluation](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TargetEvaluation.md).
- That means no matched data found in your notes. Please check `searchType` and `searchTarget` in your notes and the document for [target evaluation](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TargetEvaluation.md).
+- Why does the plugin show: 'Error parsing YAML'?
- For example, if you were doing tag search (`searchType` input is `tag`), an additional space between the colon and the value will leads to data missing. If the space in between is a must, you can use `text` search with regular expression instead of `tag`. Here is an example.
-
- To Track '#tagName: 10' in daily notes. Use
-```
-searchType: text
-searchTarget: 'tagName:\s(?[0-9]+)'
-......
-```
- More cases can be found [here](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TrackUsingRegex.md)
-
----
-
-Please also check [these trackers](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/ErrorMessages.md) for more cases leading to error messages.
+ There are syntax errors in your code block. Please check [this document](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/YAML.md) for common issues.
---
-Still have problems?? You might encounter bugs.
-Welcome to leave an issue [here](https://github.com/pyrochlore/obsidian-tracker/issues).
\ No newline at end of file
+Still have problems?? You might encounter a bug.
+Welcome to leave an issue [here](https://github.com/pyrochlore/obsidian-tracker/issues).
diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md
index ca3ecea7..7a1dec8c 100644
--- a/docs/ReleaseNotes.md
+++ b/docs/ReleaseNotes.md
@@ -1,59 +1,212 @@
# Release Notes
+## 1.19.0
+
+- Add `searchType: frontmatter.exists` for tracking field existence
+
+## 1.18.0
+
+- Support all days for startWeekOn parameter
+- Handle numeric YAML parsing for startDate/endDate with YYYYMMDD format
+- Add searchType for frontmatter keys containing lists
+- Update dependencies
+
+## 1.17.0
+
+- Adding colorByStreak parameter to Calendars allowing for increase color intensity based on streak
+- Update dependencies
+
+## 1.16.0
+
+- Adding thresholdType parameter to Calendars
+- Enable bar chart full-bar display at beginning and end
+- Update dependencies
+
+## 1.15.1
+
+- Fix: Added DatasetName as title for calendar in annotation mode
+- Fix: Resolve scaling issue when navigating between monthly views
+- Update dependencies
+
+## 1.15.0
+
+- Fix: Minor spelling error in Examples.md
+- Added new expression functions - First and Last
+- Updated dates in examples to fix plots
+- Respect thresholds in scaled months
+- Update dependencies
+
+## 1.14.0
+
+- Update dependencies
+- Prevent overlapping external labels in pie chart
+- Enable Stacked Bar Chart
+
+## 1.13.3
+
+- Update dependencies
+- Fix typo in FinanceTracker.md
+- Fix typo in TestExpression.md
+- Update main.ts - use new format for {{average}}
+
+## 1.13.2
+
+- Update dependencies
+- Fix typo in README.md
+- Change streak counts to terminate on falsey values rather than null
+
+## 1.13.1
+
+- Fix packaging script
+
+## 1.13.0
+
+- Add support for inline dataview fields (including emoji support for values)
+- Update dependencies
+
+## 1.12.0
+
+- Add aspect ratio parameter for graphs
+- Reorganize release notes in readme to be in descending order (latest release first)
+
+## 1.11.0
+
+- Add support for checkboxes in new properties added in Obsidian 1.4
+- Fix typos in documentation and examples
+
+## v1.10.9
+
+- Replace tab characters by spaces
+- Accept more unicode characters in dvField
+- Allow emojis in the folder path
+- Fixed bugs
+
+## v1.10.8
+
+- Fixed startDat/endDate misread as a relative date
+
+## v1.10.7
+
+- Allow using html image tags as emoji inputs
+
+## v1.10.6
+
+- Fixed the coloring for missing data in the month view
+
+## v1.10.5
+
+- Allow using a relative date value in `initMonth` in the month view
+
+## v1.10.4
+
+- Allow using a regular expression as a key of the parameter `textValueMap`
+- Add a parameter `shiftOnlyValueLargerThan` to determine when to do `valueShift`
+- Fixed bugs reported by users
+- Fixed typo in plugin settings
+
+## v1.10.3
+
+- Allow using the parameter `fitPanelWidth` with the output type `month` and `pie`
+- Fixed the resizing and positioning of the chart tooltip
+
+## v1.10.2
+
+- Fixed plugin not rendering on some macOS machines
+
+## v1.10.1
+
+- Fixed 'failed to load plugin' on iOS
+
+## v1.10.0
+
+- Add annotation mode for month view ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestCalendar.md))
+- Add parameters `xAxisTickInterval`, `yAxisTickInterval`, `xAxisTickLabelFormat` and `yAxisTickLabelFormat` for the line and bar chart ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestAxisIntervalAndFormat.md))
+- Allow using regular expression in parameter `dateFormatPrefix` and `dateFormatSuffix` ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md))
+- Add parameters `file`, `specifiedFilesOnly`, `fileContainsLinkedFiles`, and `fileMultiplierAfterLink` to retrieve data from specified files ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestSpecifiedFiles.md))
+- Add a parameter `textValueMap` to convert texts or emojis to specified values ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTextValueMap.md))
+- Fixed bugs
+- Enhanced error messages
+
+## v1.9.2
+
+- Allow using seconds in time values
+- Fixed error parsing `dvField`
+
+## v1.9.1
+
+- Fixed errors on collecting time values from `dvField`
+- Fixed errors on collecting wiki while fileCache.links is undefined
+
+## v1.9.0
+
+- Add a new output type `pie`, rendering a pie chart ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestPieChart.md))
+- Allow expressions (operators and functions) as data inputs for output type `summary`, `bullet`, and `pie` (examples: [expression](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestExpression.md), [summary](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestSummary.md), [bullet](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestBullet.md), [pie](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestPieChart.md))
+- Allow formatting evaluated expressions by a follwing format string ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestExpression.md))
+
## v1.8.2
+
- Fixed tasks searching not working for multiple targets
## v1.8.1
+
- Fixed bugs while using month view with parameter `xDataset`
## v1.8.0
+
- Add a new `searchType` `task`, retrieving data from tasks ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTask.md))
- Enhancement
- - Month view ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestCalendar.md))
- - Add parameter `circleColorByValue` to show color based on the value
- - Support multiple targets (dataset), change the dataset by clicking the header
- - Add a button (◦) to show current month
- - Accept ISO-8601 date as `dateFormat` ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#iso-8601-date-format))
- - Relative date input for `startDate` and `endDate` ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#relative-date-input-for-startdate-and-enddate))
+ - Month view ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestCalendar.md))
+ - Add parameter `circleColorByValue` to show color based on the value
+ - Support multiple targets (dataset), change the dataset by clicking the header
+ - Add a button (◦) to show current month
+ - Accept ISO-8601 date as `dateFormat` ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#iso-8601-date-format))
+ - Relative date input for `startDate` and `endDate` ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestDateFormats.md#relative-date-input-for-startdate-and-enddate))
- Fixed missing dvField values at the last line of files
## v1.7.0
+
- Add a new output type 'month', rendering a month view for a given dataset ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestCalendar.md))
## v1.6.1
+
- Add new targets 'numWords', 'numChars', and 'numSentences' for input type 'fileMeta' ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestWordCounting.md))
## v1.6.0
+
- Add a new input type 'fileMeta', getting meta data from a file ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestFileMeta.md))
- Add a new output type 'bullet', rendering a bullet chart ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestBullet.md))
- Enhancement
- - Accept tracking time values ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTimeValues.md))
- - Allow tracking nested values from front matter
- - Allow using dataset with date values as xDataset ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestXDataset.md))
- - Add more template variables ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTemplateVariables.md))
- - Allow parsing date in wiki brackets
+ - Accept tracking time values ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTimeValues.md))
+ - Allow tracking nested values from front matter
+ - Allow using dataset with date values as xDataset ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestXDataset.md))
+ - Add more template variables ([examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestTemplateVariables.md))
+ - Allow parsing date in wiki brackets
- Fixed bugs
## v1.5.1
+
- Fixed labels not shown in light theme
- Enhanced error handling for searchType 'table'
## v1.5.0
+
- New searchType 'table', searching records from a given table
- New searchType 'dvField', searching the inline fields used with Dataview plugin
- Enhance multiple values extraction
- - Allow using multiple values in searchType 'text'
- - Allow using array values in searchType 'frontmatter'
- - Allow using multiple values in searchType 'dvField'
- - Allow using multiple values in searchType 'table'
- - Allow using custom separator for multiple values extraction
+ - Allow using multiple values in searchType 'text'
+ - Allow using array values in searchType 'frontmatter'
+ - Allow using multiple values in searchType 'dvField'
+ - Allow using multiple values in searchType 'table'
+ - Allow using custom separator for multiple values extraction
- Improved performance
- Reduced package size
## v1.4.1
+
- Enhanced error handling
## v1.4.0
+
- Add a new parameter (fixedScale) for the scaling of the output chart
- Add a new parameter (fitPanelWidth) to enable/disable the auto-scaling of the output chart
- Add a new parameter (margin) to help to position the chart
@@ -61,6 +214,7 @@
- Fixed bugs
## v1.3.0
+
- Support reading and rendering multiple targets
- Support reading and rendering multiple values (a tuple of values) under a target
- New output type 'bar', rendering a bar chart
@@ -68,33 +222,39 @@
- Fixed bugs
## v1.2.1
+
- Fixed files with the specified dateFormat are not recognized
- Restored the plugin's settings panel for dateFormat and folder
## v1.2.0
+
- Enable using regular expression in text searching
- New search type 'frontmatter', searching for key-value pairs in the front matter
- New search type 'wiki', searching for wiki links
- Reduced package size
## v1.1.0
+
- New output type 'summary'
- Add commands help create Tracker code blocks
- Relaxed the regex for searching tags, allowing tags embedded in sentences
- Fixed issues
## v1.0.2
+
- Fixed the searching of nested tag in frontmatter
- Reduced the package size by using the module from Obsidian
## v1.0.1
+
- Remove dependencies to Node.js modules
- Add example markdown files
## v1.0.0
+
First version released at 2021-03-23
- Track simple tags, value-attached tags, and texts using code blocks
- Represent the tracked data in a customizable line chart
- Allow tracking in-line tags and tags in frontmatter
-- Allow tracking nested tags
\ No newline at end of file
+- Allow tracking nested tags
diff --git a/docs/RoadMap.md b/docs/RoadMap.md
index 9b219566..861a915c 100644
--- a/docs/RoadMap.md
+++ b/docs/RoadMap.md
@@ -6,34 +6,38 @@
- [x] Support multiple targets and multiple values
- [x] Add a parameter xDataset to identify targets to be used as x values
- [x] Allow tracking time values
+ - [ ] Allow tracking date values
- [x] Get data from a table
- [x] Collect data from dataview plugin's inline fields
- [x] Collect meta information from file
- [x] Support tracking tasks
- - [ ] Allow manual data input (x and y values)
+ - [ ] Allow manual data input (x and y values) in custom datasets
- [ ] Allow forced value types
- [ ] Allow using non-date x values
+ - [ ] Allow multiple points (different time stamp) from a single file
- Output Type and Graph
- - [x] New output type 'summary', analyzes the input data and represents it using a user-defined text template
- - [x] New output type 'bar', rendering a bar chart
- - [x] New output type 'bullet', rendering
- - [x] New output type 'month', rendering a month view
- - [ ] New output type 'heatmap', rendering a heatmap like Github activity chart
+ - [x] Implement output type 'summary', analyzes the input data and represents it using a user-defined text template
+ - [x] Implement output type 'bar', rendering a bar chart
+ - [x] Implement output type 'bullet', rendering
+ - [x] Implement output type 'month', rendering a month view
+ - [ ] Implement output type 'heatmap', rendering a heatmap like Github activity chart
+ - [x] Implement output type 'pie', rendering a pie chart
- [x] Add parameters for adjusting the size of the graph
- [ ] Multiple outputs from one code block
- - [ ] Support graphs showing the correlation between sets of data.
+ - [ ] Support graphs showing the correlation between sets of data
- [ ] Allow a graph drawing selected dataset.
- - [ ] Evaluate template variables by arithmetics and predefined functions.
+ - [x] Allow expressions evaluating operators and functions
+ - [x] Allow format string for evaluated expressions
- Helper
- [x] Add Commands help create Tracker blocks.
- - [ ] Add an 'Explode' button to the rendered blocks, it will replace the code block with the rendered result.
+ - [ ] Add an 'Explode' button to the rendered blocks, it will replace the code block with the rendered result
- [ ] Add a helper panel for adding frequently used tracking targets to article.
- Data Processing
- - [ ] Allow arithmetics operation on dataset and generate new datasets
+ - [ ] Allow arithmetics operation on dataset and generate custom datasets
- [ ] Add data post-process function, e.g. 'moving average'
- Performance
- [ ] Use PixiJS to do rendering
-And more ...
+And more ... Feature requests are welcome.
P.S. Features may not be implemented in the order above.
\ No newline at end of file
diff --git a/docs/TargetEvaluation.md b/docs/TargetEvaluation.md
index a771ecb3..f59d9ff3 100644
--- a/docs/TargetEvaluation.md
+++ b/docs/TargetEvaluation.md
@@ -1,6 +1,6 @@
# Target Evaluation
-From the [input parameters](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/InputParameters.md) you provided, the search targets dispersed in the notes will be counted or evaluated as a value. Tracker plugin supports eight kinds of `searchType`: `tag`, `frontmatter`, `wiki`, `text`, `table`, `dvField`, `task`, and `fileMeta`, dealing with different types of searching condition.
+From the [input parameters](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/InputParameters.md) you provided, the search targets dispersed in the notes will be counted or evaluated as a value. Tracker plugin supports multiple kinds of `searchType`: `tag`, `frontmatter`, `frontmatter.exists`, `frontmatterlist`, `wiki`, `text`, `table`, `dvField`, `task`, and `fileMeta`, dealing with different types of searching condition.
## Multiple Targets
You can provide multiple search targets in code block by entering an array of targets separated by a comma under parameter `searchType` and `searchTarget`. Each of the targets will be identified in order and then the values in notes will be evaluated and form a dataset indexed by that order in the array (zero-based indexing).
@@ -21,6 +21,8 @@ Many other parameters that accept multiple values (e.g. lineColor) can also be p
Multiple values under a target (value tuple) separated by a slash, e.g. #bloodpressure:180/120mmHg, are supported after version 1.3.0. To identify a specific value as a target, use an accessor with bracket notation where the value in the bracket is the index by the order of values. In this case, they are bloodpressure[0] and bloodpressure[1]. You can find the example of this [here](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/BloodPressureTracker.md). You can also use a custom separator by using the parameter `separator`.
+**Note**: the bracket notation used with searchType: frontmatterlist (e.g. habits[yoga]) uses the same bracket syntax but serves a different purpose. It specifies a string value to match against a list, rather than a numeric index into a tuple. The two are not interchangeable.
+
## Search Target in Detail
### searchType: tag
@@ -51,13 +53,59 @@ mood: 10
......
\-\-\-
+### searchType: frontmatter.exists
+
+This search type tracks days when a frontmatter field exists and is non-empty. Unlike `frontmatter`, which tries to parse the value as a number, `frontmatter.exists` simply checks if the field has any value (text, number, boolean, etc.) and counts it as 1 (or the value specified by `constValue`). This is useful for tracking habits or events where you just need to know if something happened, regardless of the value.
+
+For example, if you have a frontmatter field `meditation: "yes"` or `meditation: "completed"`, using `searchType: frontmatter.exists` with `searchTarget: meditation` will count each day where the field exists and is not empty.
+
+\-\-\-
+meditation: yes
+......
+\-\-\-
+
+### searchType: frontmatterlist
+This option is for vaultkeepers who want to use the same custom YAML property to track multiple targets. It is useful for tracking habits or categories recorded as a list in front matter fields *other* than tags.
+
+This search type checks whether a specific value is present in a frontmatter key that holds a list. Ex:
+```
+habits: [habitA, habitB, habitC]
+```
+
+When specifying a searchTarget, use the key name followed by the member value in bracket notation:
+```
+searchType: frontmatterlist
+searchTarget: habits[spanish]
+```
+When the member value is present in the list, it will be evaluated as a constant value (default 1.0). When it is absent, the day will have no value. This is ideal for counting occurances.
+
+#### Formatting property values in frontmatter
+
+Like tags, values stored in frontmatter lists are **case insensitive**. (ex: `piano` and `Piano` should be treated the same.)
+
+**Single-line lists** can optionally be surrounded by square brackets, but it's not required. Any of the following will work in frontmatter and evaluate without issue:
+- `habits: `
+- `habits: []`
+- `habits: spanish`
+- `habits: [spanish]`
+- `habits: yoga, spanish, piano`
+- `habits: [yoga, spanish, piano]`
+
+**Multi-line lists** are also allowed, ex:
+```
+habits:
+ - yoga
+ - spanish
+ - piano
+```
+
### searchType: wiki
This search type helps you count wiki links in articles. For example,
[[A]]
[[B|Link to B]]
### searchType: text
-searchType `text` is the most powerful one among all. If you simply provide text like 'love', the number of occurrences of tags will be counted. You can provide a regular expression to search for a very complicated target by wrapping it in single quotes. If you want to retrieve a value from it, use the group name in the expression. To see more detail, see [this case](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TrackUsingRegex.md).
+searchType `text` is the most powerful one among all. If you simply provide text like 'love', the number of occurrences of tags will be counted. You can provide a regular expression to search for a very complicated target by wrapping it in single quotes. If you want to retrieve a value from it, use the group name in the expression. To see more detail, see [this case](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestRegex.md).
Multiple values in text search can be achieved by separate regex by comma and wrap them all in single quotes as follows:
@@ -120,8 +168,8 @@ With this search type, you can retrieve infomation of files. Currently, three ki
### searchType: task
-You can retrieve infomation from tasks by using `searchType` `tasks`.
+You can retrieve infomation from tasks by using `searchType` `task`.
The provided `searchTarget` will limit the result with task's contents match the input.
Using type `task` or `task.all` will get you all tasks no matter it is done or not.
-To get task done, use `task.done`. By contrast, use `task.notdone`.
\ No newline at end of file
+To get task done, use `task.done`. By contrast, use `task.notdone`.
diff --git a/docs/TemplateVariables.md b/docs/TemplateVariables.md
deleted file mode 100644
index ec91c016..00000000
--- a/docs/TemplateVariables.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# Template Variables
-
-Currently, there are two places you can use template variables. First is the `template` parameter under output type `summary`. The second one is the `value` parameter under output type `bullet`. For each template variable, the plugin will do calculations based on the collected dataset refereed, and render the output with variables replaced by calculated results.
-
-The following table shows all the template variables available for now. The "N" in the table is the ID (the order of a target in `searchTarget` parameter starting from zero) or the name (specified by the parameter `datasetName`) of a dataset.
-
-Examples using template variables could be found [here](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/BloodPressureTracker.md).
-
-## List of Template Variables
-
-| Template variable | Description |
-|:------------------|:-----------|
-| {{min}}
{{min(Dataset(N))}} | Minimum value of the dataset |
-| {{minDate}}
{{minDate(Dataset(N))}} | Latest date of minimum value |
-| {{max}}
{{max(Dataset(N))}} | Maximum value of the dataset |
-| {{maxDate}}
{{maxDate(Dataset(N))}} | Latest date of maximum value |
-| {{startDate}}
{{startDate(Dataset(N))}} | Start date of the dataset |
-| {{endDate}}
{{endDate(Dataset(N))}} | End date of the dataset |
-| {{sum}}
{{sum(Dataset(N))}} | Summation of values of the dataset |
-| {{numTargets}}
{{numTargets(Dataset(N))}} | Total counts of targets |
-| {{numDays}}
{{numDays(Dataset(N))}} | Days from startDate to endDate |
-| {{numDaysHavingData}}
{{numDaysHavingData(Dataset(N))}} | Number of days having data |
-| {{maxStreak}}
{{maxStreak(Dataset(N))}} | Maximum continuous days without breaks |
-| {{maxStreakStart}}
{{maxStreakStart(Dataset(N))}} | Start date of the max streak |
-| {{maxStreakEnd}}
{{maxStreakEnd(Dataset(N))}} | End date of the max streak |
-| {{maxBreaks}}
{{maxBreaks(Dataset(N))}} | Maximum break days |
-| {{maxBreaksStart}}
{{maxBreaksStart(Dataset(N))}} | Start date of the maximum break days |
-| {{maxBreaksEnd}}
{{maxBreaksEnd(Dataset(N))}} | End date of the maximum break days |
-| {{currentStreak}}
{{currentStreak(Dataset(N))}} | Current continuous days |
-| {{currentStreakStart}}
{{currentStreakStart(Dataset(N))}} | Start date of current streak |
-| {{currentStreakEnd}}
{{currentStreakEnd(Dataset(N))}} | End date of current streak |
-| {{currentBreaks}}
{{currentBreaks(Dataset(N))}} | Current break days |
-| {{currentBreaksStart}}
{{currentBreaksStart(Dataset(N))}} | Start date of current breaks |
-| {{currentBreaksEnd}}
{{currentBreaksEnd(Dataset(N))}} | End date of current breaks |
-| {{average}}
{{average(Dataset(N))}} | Average value of the dataset |
-| {{median}}
{{median(Dataset(N))}} | Median value of the dataset |
-| {{variance}}
{{variance(Dataset(N))}} | Variance value of the dataset |
-
-## List of Deprecated Template Variables
-| Template variable | Description |
-|:------------------|:-----------|
-| {{count}}
{{count(Dataset(N))}} | Use numTargets instead |
-| {{days}}
{{days(Dataset(N))}} | Use numDays instead |
-| {{lastStreak}}
{{lastStreak(Dataset(N))}} | Use currentSteak instead |
-
-Notice when doing calculations like sum or average, the missing values are ignored. You can set those missing values to zero by setting the value of key `penalty` to zero. Moreover, if the key `ignoreZeroValue` is assigned true, zero value will be ignored too. You can check these parameters [here](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/InputParameters.md)
\ No newline at end of file
diff --git a/docs/images/screenshot_v1.9.png b/docs/images/screenshot_v1.9.png
new file mode 100644
index 00000000..4a86a4f7
Binary files /dev/null and b/docs/images/screenshot_v1.9.png differ
diff --git a/examples/.obsidian/plugins/obsidian-tracker/.hotreload b/examples/.obsidian/plugins/obsidian-tracker/.hotreload
new file mode 100644
index 00000000..e69de29b
diff --git a/examples/BloodPressureTracker.md b/examples/BloodPressureTracker.md
index d198b77b..bcfceed9 100644
--- a/examples/BloodPressureTracker.md
+++ b/examples/BloodPressureTracker.md
@@ -26,10 +26,9 @@ folder: diary
startDate: 2021-01-01
endDate: 2021-01-31
summary:
- template: "Average: {{average(Dataset(0))}}/{{average(Dataset(1))}}\nAverage: {{average(Dataset(systolic))}}/{{average(Dataset(diastolic))}}"
+ template: "Average: {{average(dataset(0))}}/{{average(dataset(1))}}"
```
-
``` tracker
searchType: frontmatter
searchTarget: bloodpressure[0], bloodpressure[1]
diff --git a/examples/ErrorMessages.md b/examples/ErrorMessages.md
index 29afea7d..12742195 100644
--- a/examples/ErrorMessages.md
+++ b/examples/ErrorMessages.md
@@ -1,27 +1,31 @@
# Error Messages
+
## YAML
-Error parsing caused by the escaping character --> YAMLParsError: Missing closing "quote"
+Error parsing caused by the escaping character YAMLParsError: Missing closing "quote"
``` tracker
searchType: tag
searchTarget: "\"
line:
```
-'searchTypes' wrong, 'searchType' right
+'searchTypes' --> typo
+'searchType' --> correct
``` tracker
searchTypes: tag
searchTarget: weight
line:
```
-'searchTargets' wrong, searchTarget right
+'searchTargets' --> typo
+'searchTarget' --> correct
``` tracker
searchType: tag
searchTargets: weight
line:
```
-'lines' wrong, 'line' right
+'lines' --> typo
+'line' --> correct
``` tracker
searchType: tag
searchTarget: weight
@@ -35,15 +39,15 @@ searchTarget: weight
line:
```
-## Target
-Missing search target
+## searchTarget
+Missing searchTarget
``` tracker
searchType: tag
searchTarget:
line:
```
-Invalid search target, '#' is not allowed
+Invalid searchTarget, '#' is a special character to YAML, use single quotes to wrap it
``` tracker
searchType: tag
searchTarget: #weight
@@ -59,22 +63,38 @@ folder: abc
line:
```
-## Date
-The format of startDate or endDate does not match dateFormat in the plugin settings. Change the settings or Add a dateFormat parameter into YAML.
+## Files
+No file in folder
``` tracker
searchType: tag
searchTarget: weight
-startDate: 2020-01-01_Fri
-endDate: 2020-01-31_Mon
+folder: empty
line:
```
-No note found in the given date range
+## Number of parameters
+Two search targets provided, the number of search types shouldn't be more than two.
+``` tracker
+searchType: frontmatter, frontmatter, frontmatter
+searchTarget: bloodpressure[0], bloodpressure[1]
+line:
+```
+
+yAxisLabel allows only two inputs
+``` tracker
+searchType: frontmatter, frontmatter
+searchTarget: bloodpressure[0], bloodpressure[1]
+line:
+ yAxisLabel: BP1, BP2, BP3
+```
+
+## startDate & endDate
+The format of startDate or endDate does not match dateFormat in the plugin settings. Change the settings or Add a dateFormat parameter into YAML.
``` tracker
searchType: tag
searchTarget: weight
-startDate: 2020-01-01
-endDate: 2020-01-31
+startDate: 2020-01-01_Fri
+endDate: 2020-01-31_Mon
line:
```
@@ -87,20 +107,34 @@ endDate: 2021-02-30
line:
```
-## Number of parameters
-Two search targets provided, the number of search types shouldn't be more than two.
+## X Values (Dates)
+No note found in the given date range
``` tracker
-searchType: frontmatter, frontmatter, frontmatter
-searchTarget: bloodpressure[0], bloodpressure[1]
+searchType: tag
+searchTarget: weight
+startDate: 2020-01-01
+endDate: 2020-01-31
line:
```
-yAxisLabel allows only two inputs
+No valid X values, add "xDataset: 0" to fix it
``` tracker
-searchType: frontmatter, frontmatter
-searchTarget: bloodpressure[0], bloodpressure[1]
+searchType: fileMeta, dvField
+searchTarget: cDate, dataviewTarget
+folder: data
+line:
+ fillGap: true
+```
+
+## Y Values
+No valid Y values!!!!!
+Use parameter `textValueMap` to map a text to a value.
+``` tracker
+searchType: frontmatter
+searchTarget: randchar
+folder: diary
line:
- yAxisLabel: BP1, BP2, BP3
+ fillGap: true
```
## Output
@@ -116,8 +150,8 @@ The Parameter 'lineColor' allows only one input for the single target
searchType: tag
searchTarget: weight
line:
- title: Line
- lineColor: red, yellow
+ title: Line
+ lineColor: red, yellow
```
The parameter name should be 'title', not 'titles'
@@ -135,8 +169,62 @@ searchType: table
searchTarget: data/Tables[4][0], data/Tables[4][1]
xDataset: 0
line:
- lineColor: none, yellow
+ lineColor: none, yellow
+```
+
+
+Please also check those search targets in markdown files under folder 'diary' and 'data'.
+
+## Expression
+Unknown function
+``` tracker
+searchType: task
+searchTarget: Say I love you
+summary:
+ template: '{{unknown()}}'
```
+Incomplete expression
+``` tracker
+searchType: task
+searchTarget: Say I love you
+summary:
+ template: '{{1+}}'
+```
+
+No dataset found for id
+``` tracker
+searchType: task
+searchTarget: Say I love you
+summary:
+ template: '{{sum(dataset(1))}}'
+```
+
+Divide by zero
+``` tracker
+searchType: task
+searchTarget: Say I love you
+summary:
+ template: '{{sum()/0}}'
+```
+
+Invalid data range (data only contains 1 and null)
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: diary
+summary:
+ template: '{{sum(normalize(dataset(0)))}}'
+```
+
+## Deprecated
+### Deprecated template variables
+
+Deprecated template variable
+``` tracker
+searchType: task
+searchTarget: Say I love you
+summary:
+ template: '{{sum}}'
+```
-Please also check those search targets in markdown files under folder 'diary' and 'data'.
\ No newline at end of file
diff --git a/examples/FinanceTracker.md b/examples/FinanceTracker.md
index 212ec16d..f63a19f2 100644
--- a/examples/FinanceTracker.md
+++ b/examples/FinanceTracker.md
@@ -5,8 +5,9 @@ searchType: tag
searchTarget: finance
folder: diary
accum: true
+endDate: 2021-01-31
line:
- title: Finace
+ title: Finance
yAxisLabel: USD
lineWidth: 4
```
@@ -17,6 +18,7 @@ searchType: tag
searchTarget: finance/bank1
folder: diary
accum: true
+endDate: 2021-01-31
line:
title: Bank1
yAxisLabel: USD
@@ -28,10 +30,11 @@ searchType: tag
searchTarget: finance/bank2
folder: diary
accum: true
+endDate: 2021-03-15
line:
title: Bank2
yAxisLabel: USD
fillGap: true
```
-Please also check those search targets in markdown files under folder 'diary'.
\ No newline at end of file
+Please also check those search targets in markdown files under folder 'diary'.
diff --git a/examples/HabitTracker.md b/examples/HabitTracker.md
index b517eede..71b0f302 100644
--- a/examples/HabitTracker.md
+++ b/examples/HabitTracker.md
@@ -15,6 +15,7 @@ line:
searchType: tag
searchTarget: exercise-plank
folder: diary
+endDate: 2021-01-31
line:
title: Plank
yAxisLabel: Hold
@@ -29,6 +30,7 @@ searchTarget: meditation
folder: diary
accum: true
penalty: -1
+endDate: 2021-01-31
line:
title: Meditation
yAxisLabel: Count
@@ -41,7 +43,7 @@ searchType: tag
searchTarget: meditation
folder: diary
summary:
- template: "Longest Streak: {{maxStreak}} day(s)\nLongest Breaks: {{maxBreaks}} day(s)\nLast streak: {{currentStreak}} day(s)"
+ template: "Longest Streak: {{maxStreak()}} day(s)\nLongest Breaks: {{maxBreaks()}} day(s)\nLast streak: {{currentStreak()}} day(s)"
```
### CleanUp
@@ -51,7 +53,7 @@ searchTarget: clean-up
folder: diary
endDate: 2021-01-31
summary:
- template: "Last Break: {{currentBreaks}} day(s)"
+ template: "Last Break: {{currentBreaks()}} day(s)"
```
### Work log
@@ -61,6 +63,7 @@ searchTarget: work_log
folder: diary
accum: true
startDate: 2021-01-01
+endDate: 2021-01-31
line:
title: Work Log
yAxisLabel: Count
diff --git a/examples/MoodTracker.md b/examples/MoodTracker.md
deleted file mode 100644
index 48c83c82..00000000
--- a/examples/MoodTracker.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# Mood Tracker
-
-``` tracker
-searchType: frontmatter
-searchTarget: "mood"
-folder: diary
-endDate: 2021-01-31
-line:
- title: "Mood"
- yAxisLabel: Mood
- lineColor: "#d65d0e"
-```
-
-Please also check those search targets in markdown files under folder 'diary'.
\ No newline at end of file
diff --git a/examples/StarTracker.md b/examples/StarTracker.md
index 994ce450..9bdd37a8 100644
--- a/examples/StarTracker.md
+++ b/examples/StarTracker.md
@@ -5,8 +5,9 @@
searchType: text
searchTarget: ⭐
folder: diary
+endDate: 2021-01-31
summary:
- template: "I have {{sum}} stars in total."
+ template: "I have {{sum()}} stars in total."
style: "font-size:20px;color:yellow;margin-left: 50px;margin-top:00px;"
```
@@ -14,6 +15,7 @@ summary:
searchType: text
searchTarget: ⭐
folder: diary
+endDate: 2021-01-31
accum: true
line:
title: Count the Given Stars
diff --git a/examples/TestAxisIntervalAndFormat.md b/examples/TestAxisIntervalAndFormat.md
new file mode 100644
index 00000000..35f16899
--- /dev/null
+++ b/examples/TestAxisIntervalAndFormat.md
@@ -0,0 +1,105 @@
+# Test Axis Interval and Tick Label Format
+
+## Y Axis Interval
+
+Numeric Y values
+``` tracker
+searchType: tag
+searchTarget: weight
+folder: diary
+startDate: 2021-01-01
+endDate: 2021-01-31
+line:
+ title: Weight Log
+ yAxisLabel: Weight
+ yAxisUnit: kg
+ lineColor: yellow
+ yAxisTickInterval: 5
+ yMin: 55
+```
+
+Y values in time
+``` tracker
+searchType: frontmatter
+searchTarget: clock-in, clock-out
+endDate: 2021-01-15
+folder: diary
+datasetName: Clock-In, Clock-Out
+line:
+ title: "Working Hours"
+ yAxisLabel: "Time (24h)"
+ reverseYAxis: true
+ lineColor: yellow, red
+ showPoint: true
+ yAxisTickInterval: 1h
+ yMin: 06:00
+ yMax: 23:00
+```
+
+## Y Axis Tick Label Format
+Float numbers with precision of 1 decimal digits
+``` tracker
+searchType: tag
+searchTarget: weight
+folder: diary
+startDate: 2021-01-01
+endDate: 2021-01-31
+line:
+ title: Weight Log
+ yAxisLabel: Weight
+ yAxisUnit: kg
+ lineColor: yellow
+ yAxisTickInterval: 5
+ yAxisTickLabelFormat: .2f
+ yMin: 55
+```
+
+Y values in time
+``` tracker
+searchType: frontmatter
+searchTarget: clock-in, clock-out
+endDate: 2021-01-15
+folder: diary
+datasetName: Clock-In, Clock-Out
+line:
+ title: "Working Hours"
+ yAxisLabel: "Time (24h)"
+ reverseYAxis: true
+ lineColor: yellow, red
+ showPoint: true
+ yMin: 05:00
+ yMax: 22:00
+ yAxisTickInterval: 50m
+ yAxisTickLabelFormat: H---m
+```
+
+## X Axis Interval
+``` tracker
+searchType: tag
+searchTarget: weight
+folder: diary
+startDate: 2021-01-01
+endDate: 2021-01-31
+line:
+ title: Weight Log
+ yAxisLabel: Weight
+ yAxisUnit: kg
+ lineColor: yellow
+ xAxisTickInterval: 1w
+```
+
+## X Axis Tick Label Format
+``` tracker
+searchType: tag
+searchTarget: weight
+folder: diary
+startDate: 2021-01-01
+endDate: 2021-01-31
+line:
+ title: Weight Log
+ yAxisLabel: Weight
+ yAxisUnit: kg
+ lineColor: yellow
+ xAxisTickInterval: 7d
+ xAxisTickLabelFormat: M-DD
+```
\ No newline at end of file
diff --git a/examples/TestBarChart.md b/examples/TestBarChart.md
index 3fb24784..118a7547 100644
--- a/examples/TestBarChart.md
+++ b/examples/TestBarChart.md
@@ -9,6 +9,7 @@ endDate: 2021-01-05
bar:
title: Weight Log
yAxisLabel: Weight
+ xAxisPadding: 12h
yAxisUnit: kg
yMin: 0
barColor: darkolivegreen
@@ -23,6 +24,7 @@ endDate: 2021-01-31
bar:
title: Weight Log
yAxisLabel: Weight
+ xAxisPadding: 12h
yAxisUnit: kg
yMin: 0
barColor: brown
@@ -37,6 +39,7 @@ endDate: 2021-01-21
bar:
title: Sin Wave
yAxisLabel: Value
+ xAxisPadding: 12h
barColor: yellow, red, green
```
@@ -49,8 +52,24 @@ endDate: 2021-01-05
bar:
title: Sin Square Wave
yAxisLabel: Value
+ xAxisPadding: 12h
yMin: 0
barColor: yellow, red, green, blue, orange, white
```
+
+``` tracker
+searchType: tag
+searchTarget: sinsquare[0], sinsquare[1], sinsquare[2], sinsquare[3], sinsquare[4], sinsquare[5]
+folder: diary
+startDate: 2021-01-01
+endDate: 2021-01-05
+stack: true
+bar:
+ title: Sin Square Wave (Stacked)
+ yAxisLabel: Value
+ xAxisPadding: 12h
+ yMin: 0
+ barColor: yellow, red, green, blue, orange, black
+```
Please also check those search targets in markdown files under folder 'diary'.
\ No newline at end of file
diff --git a/examples/TestBullet.md b/examples/TestBullet.md
index 733e1f6c..2db36010 100644
--- a/examples/TestBullet.md
+++ b/examples/TestBullet.md
@@ -1,5 +1,32 @@
# Test Bullet
+
+## Manual Input Data
+
+Manual input `value` as 12\.5
+``` tracker
+searchType: tag
+searchTarget: clean-up
+folder: diary
+endDate: 2021-01-31
+fixedScale: 1.1
+bullet:
+ title: "Clean Up"
+ dataset: 0
+ orientation: horizontal
+ range: 10, 20, 40
+ rangeColor: darkgray, silver, lightgray
+ value: 12.5
+ valueUnit: times
+ valueColor: '#69b3a2'
+ showMarker: true
+ markerValue: 30
+ markerColor: black
+```
+
+## Data from Notes
+
Horizontal bullet chart
+value from expression function currentBreaks()
``` tracker
searchType: tag
searchTarget: clean-up
@@ -8,19 +35,20 @@ endDate: 2021-01-31
fixedScale: 1.1
bullet:
title: "Clean Up"
- dataset: 0
+ dataset: 0
orientation: horizontal
- range: 10, 20, 40
- rangeColor: darkgray, silver, lightgray
- value: "{{currentBreaks}}"
- valueUnit: times
- valueColor: '#69b3a2'
- showMarker: true
- markerValue: 24
- markerColor: black
+ range: 10, 20, 40
+ rangeColor: darkgray, silver, lightgray
+ value: "{{currentBreaks()}}"
+ valueUnit: times
+ valueColor: '#69b3a2'
+ showMarker: true
+ markerValue: 24
+ markerColor: black
```
Vertical bullet chart
+value from expression function sum()
``` tracker
searchType: tag
searchTarget: meditation
@@ -28,16 +56,16 @@ folder: diary
endDate: 2021-01-31
bullet:
title: "Meditation"
- dataset: 0
+ dataset: 0
orientation: vertical
- range: 30, 60, 100
- rangeColor: darkgray, silver, lightgray
- value: "{{sum}}"
- valueUnit: times
- valueColor: steelblue
- showMarker: true
- markerValue: 80
- markerColor: red
+ range: 30, 60, 100
+ rangeColor: darkgray, silver, lightgray
+ value: "{{sum()}}"
+ valueUnit: times
+ valueColor: steelblue
+ showMarker: true
+ markerValue: 80
+ markerColor: red
```
Please also check those search targets in markdown files under folder 'diary'.
\ No newline at end of file
diff --git a/examples/TestCalendar.md b/examples/TestCalendar.md
index 43883051..68705a78 100644
--- a/examples/TestCalendar.md
+++ b/examples/TestCalendar.md
@@ -9,6 +9,7 @@ searchType: tag
searchTarget: meditation
datasetName: Meditation
folder: diary
+endDate: 2021-01-31
month:
```
@@ -21,6 +22,7 @@ searchType: tag
searchTarget: exercise-pushup
datasetName: PushUp
folder: diary
+endDate: 2021-01-31
month:
startWeekOn: 'Sun'
threshold: 40
@@ -38,6 +40,7 @@ searchType: tag
searchTarget: meditation
datasetName: Meditation
folder: diary
+endDate: 2021-01-31
month:
startWeekOn: 'Sun'
color: steelblue
@@ -46,21 +49,59 @@ month:
```
### Colored by Values
-Use parameter `circleColorByValue`, color the circles based on the values
+Use parameters `circleColorByValue`, `yMin`, and `yMax`, to color the circles based on the values
``` tracker
searchType: tag
searchTarget: exercise-pushup
datasetName: PushUp
folder: diary
+endDate: 2021-01-31
month:
startWeekOn:
- threshold: 40
+ threshold: 10
color: green
headerMonthColor: orange
dimNotInMonth: false
todayRingColor: orange
selectedRingColor: steelblue
circleColorByValue: true
+ yMin: 0
+ yMax: 50
+ showSelectedValue: true
+```
+### Colored by Streak
+Use parameter circleColorByStreak to increase color intesity with streaklength. This can also be used along with thresholdtype parameter.
+``` tracker
+searchType: tag
+searchTarget: exercise-pushup
+datasetName: PushUp
+folder: diary
+endDate: 2021-01-31
+month:
+ startWeekOn:
+ threshold: 30
+ color: red
+ dimNotInMonth: false
+ circleColorByStreak: true
+```
+
+### Colored by Threshold and thresholdType
+Use parameters threshold and thresholdType - "LessThan" to color the circles
+``` tracker
+searchType: tag
+searchTarget: exercise-pushup
+datasetName: PushUp
+folder: diary
+endDate: 2021-01-31
+month:
+ startWeekOn:
+ threshold: 40
+ thresholdType: LessThan
+ color: green
+ headerMonthColor: orange
+ dimNotInMonth: false
+ todayRingColor: orange
+ selectedRingColor: steelblue
showSelectedValue: true
```
@@ -69,7 +110,47 @@ month:
searchType: tag
searchTarget: exercise-pushup
summary:
- template: "minDate: {{minDate}}\nminValue: {{min}}\nmaxDate: {{maxDate}}\nmaxValue: {{max}}"
+ template: "minDate: {{minDate()}}\nminValue: {{min()}}\nmaxDate: {{maxDate()}}\nmaxValue: {{max()}}"
+```
+
+### initMonth
+
+Specify the initial month in YYYY-MM format
+``` tracker
+searchType: tag
+searchTarget: exercise-pushup
+datasetName: PushUp
+folder: diary
+month:
+ startWeekOn:
+ threshold: 40
+ color: green
+ headerMonthColor: orange
+ dimNotInMonth: false
+ todayRingColor: orange
+ selectedRingColor: steelblue
+ circleColorByValue: true
+ showSelectedValue: true
+ initMonth: 2021-01
+```
+
+Specify the initial month by relative date
+``` tracker
+searchType: tag
+searchTarget: exercise-pushup
+datasetName: PushUp
+folder: diary
+month:
+ startWeekOn:
+ threshold: 40
+ color: green
+ headerMonthColor: orange
+ dimNotInMonth: false
+ todayRingColor: orange
+ selectedRingColor: steelblue
+ circleColorByValue: true
+ showSelectedValue: true
+ initMonth: -47M
```
## Multiple targets
@@ -82,6 +163,7 @@ searchType: tag
searchTarget: exercise-pushup, meditation
datasetName: PushUp, Meditation
folder: diary
+endDate: 2021-01-31
month:
dataset: 0, 1
startWeekOn: 'Sun'
@@ -95,5 +177,54 @@ month:
showSelectedValue: true
```
+## Annotations
+One target at a time
+``` tracker
+searchType: tag
+searchTarget: exercise-pushup, meditation
+datasetName: PushUp, Meditation
+folder: diary
+endDate: 2021-01-31
+month:
+ mode: annotation
+ startWeekOn: 'Sun'
+ threshold: 40, 0
+ color: green
+ headerMonthColor: orange
+ dimNotInMonth: false
+ annotation: 💪,🧘♂️
+ showAnnotationOfAllTargets: false
+```
+
+All targets
+``` tracker
+searchType: tag
+searchTarget: exercise-pushup, meditation
+datasetName: PushUp, Meditation
+folder: diary
+endDate: 2021-01-31
+month:
+ mode: annotation
+ startWeekOn: 'Sun'
+ threshold: 40, 0
+ color: green
+ headerMonthColor: orange
+ dimNotInMonth: false
+ annotation: 💪,🧘♂️
+ showAnnotationOfAllTargets: true
+```
+
Please also check those search targets in markdown files under folder 'diary'.
+## Scaling
+fitPanelWidth: true
+Click forward backward months and verify it stays scaled
+``` tracker
+searchType: tag
+searchTarget: meditation
+datasetName: Meditation
+fitPanelWidth: true
+folder: diary
+endDate: 2021-01-31
+month:
+```
diff --git a/examples/TestCommands.md b/examples/TestCommands.md
index 38c81e1e..88caaf74 100644
--- a/examples/TestCommands.md
+++ b/examples/TestCommands.md
@@ -5,7 +5,7 @@ searchType: tag
searchTarget: weight
folder: /
startDate:
-endDate:
+endDate: 2021-01-31
line:
title: "Line Chart"
xAxisLabel: Date
@@ -18,7 +18,7 @@ searchType: tag
searchTarget: weight
folder: /
startDate:
-endDate:
+endDate: 2021-01-31
bar:
title: "Bar Chart"
xAxisLabel: Date
@@ -31,8 +31,8 @@ searchType: tag
searchTarget: weight
folder: /
startDate:
-endDate:
+endDate: 2021-01-31
summary:
- template: "Average value of tagName is {{average}}"
+ template: "Average value of tagName is {{average()}}"
style: "color:white;"
```
\ No newline at end of file
diff --git a/examples/TestDateFormats.md b/examples/TestDateFormats.md
index a98b9468..ff6fc791 100644
--- a/examples/TestDateFormats.md
+++ b/examples/TestDateFormats.md
@@ -119,6 +119,7 @@ The string provided in dateFormatPrefix and dateFormatSuffix will be removed bef
searchType: tag
searchTarget: weight
folder: diary
+dateFormat: YYYYMMDD
dateFormatPrefix: D-
startDate: D-20210101
endDate: D-20210105
@@ -135,6 +136,7 @@ line:
searchType: tag
searchTarget: weight
folder: diary
+dateFormat: YYYYMMDD
dateFormatSuffix: -D
startDate: 20210101-D
endDate: 20210105-D
@@ -145,20 +147,49 @@ line:
lineColor: yellow
```
+### Using prefix and suffix with regular expression
+
+Examples of file name
+- Jeffrey-20210101-Journal
+- Jeffrey-20210102-Diary
+- Lucas-2021-0103-Journal
+- Lucas-2021-0104-Diary
+
+Data from the same days will be summed up.
+``` tracker
+searchType: tag
+searchTarget: exercise-pushup
+folder: diary
+dateFormat: YYYYMMDD
+dateFormatPrefix: '(Jeffrey-|Lucas-)'
+dateFormatSuffix: '(-Journal|-Diary)'
+startDate: 20210101
+endDate: 20210105
+line:
+ title: PushUp
+ yAxisLabel: Count
+ yAxisUnit: times
+ lineColor: yellow
+```
+
## Relative Date Input for startDate and endDate
The reference date of the relative date input is 'today' (The current date of your computer), So
- 0d ==> today
- -1d ==> yesterday
- -1w ==> last week
-- -1m ==> last month
+- -1M ==> last month
- -1y ==> last year
+Notice!!
+- small 'm' represent 'minute'
+- If the date range is less than 1 day, you will get the error message 'No valid date as X value found in notes'.
+
``` tracker
searchType: tag
searchTarget: weight
folder: diary
-startDate: -1m
+startDate: -1M
endDate: 0d
line:
title: Weight Log
diff --git a/examples/TestDvField.md b/examples/TestDvField.md
index 7fb7877d..34b56809 100644
--- a/examples/TestDvField.md
+++ b/examples/TestDvField.md
@@ -9,7 +9,7 @@ startDate: 2021-01-01
endDate: 2021-01-31
line:
title: dvField
- lineColor: green
+ lineColor: green
```
Field with a space
@@ -21,7 +21,7 @@ startDate: 2021-01-01
endDate: 2021-01-31
line:
title: dvField
- lineColor: yellow
+ lineColor: yellow
```
Field with a dash line
@@ -33,7 +33,7 @@ startDate: 2021-01-01
endDate: 2021-01-31
line:
title: dvField
- lineColor: red
+ lineColor: red
```
Extract the first value from multiple values
@@ -45,7 +45,7 @@ startDate: 2021-01-01
endDate: 2021-01-31
line:
title: dvField
- lineColor: blue
+ lineColor: blue
```
Multiple values separated by '/' (default)
@@ -73,6 +73,19 @@ line:
lineColor: green, red
```
+Multiple values seprated by '\,'
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget3[0], dataviewTarget3[1]
+folder: diary
+startDate: 2021-01-01
+endDate: 2021-01-31
+separator: '\,'
+line:
+ title: dvField
+ lineColor: green, red
+```
+
Use custom multiple value separator
``` tracker
searchType: dvField
diff --git a/examples/TestEmoji.md b/examples/TestEmoji.md
new file mode 100644
index 00000000..a5c84b6c
--- /dev/null
+++ b/examples/TestEmoji.md
@@ -0,0 +1,20 @@
+
+```tracker
+searchType: dvfield
+searchTarget: Physical
+folder: diary
+startDate: 2023-06-04
+endDate: 2023-06-11
+textValueMap:
+ 😀: 5
+ 🙂: 4
+ 😐: 3
+ 🙁: 2
+ 😞: 1
+datasetName: 🚹 Physical
+line:
+ lineColor: orange
+ lineWidth: 3
+ showLegend: true
+ legendPosition: right
+```
\ No newline at end of file
diff --git a/examples/TestExpression.md b/examples/TestExpression.md
new file mode 100644
index 00000000..5a86464d
--- /dev/null
+++ b/examples/TestExpression.md
@@ -0,0 +1,371 @@
+# Test Expression
+
+All examples here using the output type `summary`.
+To see examples of `bullet` and `pie`, please check [bullet examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestBullet.md) and [pie examples](https://github.com/pyrochlore/obsidian-tracker/blob/master/examples/TestPieChart.md).
+
+## Operators
+
+### number and number
+
+number \+ number --> number
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+startDate: 2021-01-01
+endDate: 2021-01-03
+summary:
+ template: 'Maximum value: {{10 + 10::i}} <-- should be 20'
+```
+
+### Dataset and number
+
+Dataset \+ number --> Dataset
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+startDate: 2021-01-01
+endDate: 2021-01-03
+summary:
+ template: 'Maximum value: {{max() + 10::i}} <-- should be 48 + 10'
+```
+
+Dataset \- number --> Dataset
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+startDate: 2021-01-01
+endDate: 2021-01-03
+summary:
+ template: 'Maximum value: {{max() - 2::i}} <-- should be 48 - 2'
+```
+
+Dataset \* number --> Dataset
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+startDate: 2021-01-01
+endDate: 2021-01-03
+summary:
+ template: 'Maximum value: {{max() * 2::i}} <-- should be 48 * 2'
+```
+
+Dataset / number --> Dataset
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+startDate: 2021-01-01
+endDate: 2021-01-03
+summary:
+ template: 'Maximum value: {{max() / 2::i}} <-- should be 48 / 2'
+```
+
+Dataset % number --> Dataset
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+startDate: 2021-01-01
+endDate: 2021-01-03
+summary:
+ template: 'Maximum value: {{max() % 5::i}} <-- should be 48 % 5'
+```
+
+### Dataset and Dataset
+
+Dataset1 \+ Dataset2 --> Dataset
+==> Dataset[i] = Dataset1[i] + Dataset2[i]
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+startDate: 2021-01-01
+endDate: 2021-01-03
+summary:
+ template: 'Maximum value: {{max(dataset(0) + dataset(0))::i}} <-- should be 48 + 48'
+```
+
+## Functions
+
+**If the input dataset is missing, it will use the first available Y dataset found.**
+
+### Functions Accept Dataset and Return a Value
+
+min(Dataset): number
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+endDate: 2021-01-03
+summary:
+ template: 'Minimum value: {{min()::i}} <-- should be 12'
+```
+
+minDate(Dataset): Date
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+endDate: 2021-01-03
+summary:
+ template: 'Latest date of minimum value: {{minDate()}} <-- should be 2021-01-03'
+```
+
+max(Dataset): number
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+endDate: 2021-01-03
+summary:
+ template: 'Maximum value: {{max()::i}} <-- should be 48'
+```
+
+maxDate(Dataset): Date
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+endDate: 2021-01-03
+summary:
+ template: 'Latest date of maximum value: {{maxDate()}} <-- should be 2021-01-01'
+```
+
+startDate(Dataset): Date
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+endDate: 2021-01-03
+summary:
+ template: 'Start date: {{startDate()}} <-- should be 2021-01-01'
+```
+
+endDate(Dataset): Date
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+endDate: 2021-01-03
+summary:
+ template: 'End date: {{endDate()}} <-- should be 2021-01-03'
+```
+
+sum(Dataset): number
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-04
+summary:
+ template: 'Sum: {{sum()::i}} <-- should be 3'
+```
+
+numTargets(Dataset): number
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-04
+summary:
+ template: 'Number of targets: {{numTargets()::i}} <-- should be 3'
+```
+
+numDays(Dataset): number
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-04
+summary:
+ template: 'Number of days: {{numDays()::i}} <-- should be 4'
+```
+
+numDaysHavingData(Dataset): number
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-04
+summary:
+ template: 'Number of days having data: {{numDaysHavingData()::i}} <-- should be 3'
+```
+
+maxStreak(Dataset): number
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-09
+summary:
+ template: 'Maximum streak: {{maxStreak()::i}} <-- should be 5'
+```
+
+maxStreakStart(Dataset): Date
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-09
+summary:
+ template: 'The start date of maximum streak: {{maxStreakStart()}} <-- should be 2021-01-02'
+```
+
+maxStreakEnd(Dataset): Date
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-09
+summary:
+ template: 'The end date of maximum streak: {{maxStreakEnd()}} <-- should be 2021-01-06'
+```
+
+maxBreaks(Dataset): number
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-09
+summary:
+ template: 'Maximum breaks: {{maxBreaks()::i}} <-- should be 2'
+```
+
+maxBreaksStart(Dataset): Date
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-09
+summary:
+ template: 'The start date of maximum breaks: {{maxBreaksStart()}} <-- should be 2021-01-07'
+```
+
+maxBreaksEnd(Dataset): Date
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-09
+summary:
+ template: 'The end date of maximum breaks: {{maxBreaksEnd()}} <-- should be 2021-01-08'
+```
+
+currentStreak(Dataset): number
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-24
+summary:
+ template: 'Latest streak: {{currentStreak()::i}} <-- should be 1'
+```
+
+currentStreakStart(Dataset): Date
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-24
+summary:
+ template: 'The start date of current streak: {{currentStreakStart()}} <-- should be 2021-01-24'
+```
+
+currentStreakEnd(Dataset): Date
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-24
+summary:
+ template: 'The end date of current streak: {{currentStreakEnd()}} <-- should be 2021-01-24'
+```
+
+currentBreaks(Dataset): number
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-22
+summary:
+ template: 'Current breaks: {{currentBreaks()::i}} <-- should be 1'
+```
+
+currentBreaksStart(Dataset): number
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-22
+summary:
+ template: 'The start date of current breaks: {{currentBreaksStart()}} <-- should be 2021-01-22'
+```
+
+currentBreaksEnd(Dataset): Date
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-22
+summary:
+ template: 'The end date of current breaks: {{currentBreaksEnd()}} <-- should be 2021-01-22'
+```
+
+average(Dataset): number
+(48+25+12)/3 = 28.33
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+endDate: 2021-01-03
+summary:
+ template: 'Average value: {{average()::.2f}} <-- should be 28.33'
+```
+
+median(Dataset): number
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+endDate: 2021-01-03
+summary:
+ template: 'Median value: {{median()::i}} <-- should be 25'
+```
+
+variance(Dataset): number
+https://mathworld.wolfram.com/SampleVariance.html
+``` tracker
+searchType: dvField
+searchTarget: dataviewTarget
+folder: /diary
+endDate: 2021-01-03
+summary:
+ template: 'Variance value: {{variance()::.2f}} <-- should be 332.33'
+```
+
+### Functions Accept Dataset and Return a Dataset
+
+normalize(Dataset): Dataset
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-04
+summary:
+ template: 'Set missing values to -1, do normalization then do summation: {{sum( normalize( setMissingValues(dataset(0), -1) ) )::i}} <-- should be 3'
+```
+
+setMissingValues(Dataset): Dataset
+``` tracker
+searchType: tag
+searchTarget: meditation
+folder: /diary
+endDate: 2021-01-04
+summary:
+ template: 'Set missing values to -1 then do summation: {{sum( setMissingValues( dataset(0), -1 ) )::i}} <-- should be 2'
+```
+
diff --git a/examples/TestFileMeta.md b/examples/TestFileMeta.md
index d1436a93..da7634ba 100644
--- a/examples/TestFileMeta.md
+++ b/examples/TestFileMeta.md
@@ -7,8 +7,8 @@ folder: diary
endDate: 2021-01-31
line:
title: File Size Variation
- yAxisLabel: Size
- yAxisUnit: bytes
+ yAxisLabel: Size
+ yAxisUnit: bytes
```
Use file created dates (cDate) as x values
diff --git a/examples/TestFrontmatter.md b/examples/TestFrontmatter.md
index c8f547b1..e42bd775 100644
--- a/examples/TestFrontmatter.md
+++ b/examples/TestFrontmatter.md
@@ -1,6 +1,13 @@
# Test Frontmatter
-
+## Deep Values
+deepValue:
+ very:
+ very:
+ very:
+ very:
+ very:
+ deep: 27.4
``` tracker
searchType: frontmatter
searchTarget: deepValue.very.very.very.very.very.deep
@@ -10,4 +17,72 @@ line:
title: Deep Values
```
-Please also check those search targets in markdown files under folder 'diary'.
\ No newline at end of file
+## Multiple Values
+bloodpressure: 184.4/118.8
+``` tracker
+searchType: frontmatter
+searchTarget: bloodpressure[0], bloodpressure[1]
+datasetName: systolic, diastolic
+folder: diary
+startDate: 2021-01-01
+endDate: 2021-01-31
+line:
+ title: Blood Pressures
+ yAxisLabel: BP
+ yAxisUnit: mmHg
+ lineColor: yellow, red
+ showLegend: true
+ legendPosition: bottom
+```
+
+## Multiple Tags in Front Matter
+Extract data of one tag from multiple tags.
+The default separator in front matter tags is comma (,)
+``` tracker
+searchType: tag
+searchTarget: work_log
+folder: diary
+accum: true
+startDate: 2021-01-01
+endDate: 2021-01-31
+line:
+ title: Work Log
+ yAxisLabel: Count
+ pointSize: 5
+ pointColor: white
+ pointBorderWidth: 2
+ pointBorderColor: "#d65d0e"
+```
+
+Use the data of two tags.
+The default separator in front matter tags is comma (,)
+``` tracker
+searchType: tag
+searchTarget: work_log, work_log2
+folder: diary
+datasetName: Work1, Work2
+month:
+ initMonth: 2021-01
+```
+
+## Track Field Existence (frontmatter.exists)
+
+Track days when a frontmatter field exists and is non-empty, regardless of the value. This is useful for habit tracking where you just need to know if something happened.
+
+``` tracker
+searchType: frontmatter.exists
+searchTarget: meditation
+datasetName: Meditation Days
+folder: diary
+startDate: 2021-01-01
+endDate: 2021-01-31
+month:
+ mode: circle
+```
+
+This will count each day where the `meditation` field exists in the frontmatter, whether it's `meditation: yes`, `meditation: completed`, `meditation: true`, or any other non-empty value.
+
+Please also check those search targets in markdown files under folder 'diary'.
+
+
+Note about multiple values: When extracting one value from a list of multiple values in frontmatter fields besides tags, please refer to TestFrontmatterList.md.
diff --git a/examples/TestFrontmatterList.md b/examples/TestFrontmatterList.md
new file mode 100644
index 00000000..f3ecaf9d
--- /dev/null
+++ b/examples/TestFrontmatterList.md
@@ -0,0 +1,76 @@
+# Frontmatter Lists
+This option is helpful for vaultkeepers who want to track multiple habits (targets) using the same custom YAML property.
+
+## Example outputs
+Each of the outputs below is generated by searching frontmatter for `habits: ` , then checking the list of values for the presence of a single habit, ex: `yoga`, `piano`, or `spanish`.
+
+```tracker
+searchType: frontmatterlist
+searchTarget: habits[yoga]
+folder: diary
+startDate: 2026-02-01
+endDate: 2026-02-28
+datasetName: Yoga
+month:
+```
+
+
+
+```tracker
+searchType: frontmatterlist
+searchTarget: habits[spanish]
+folder: diary
+startDate: 2026-02-01
+endDate: 2026-02-28
+datasetName: Spanish
+month:
+ color: orange
+```
+```tracker
+searchType: frontmatterlist
+searchTarget: habits[piano]
+folder: diary
+startDate: 2026-02-01
+endDate: 2026-02-28
+datasetName: Piano
+month:
+ color: lightblue
+```
+
+```tracker
+searchType: frontmatterlist
+searchTarget: habits[piano]
+folder: diary
+startDate: 2026-02-01
+endDate: 2026-02-28
+summary:
+ template: "Total count: {{sum()}}"
+```
+
+
+
+### Testing
+
+**Bracket test**: Single-line lists can be surrounded by square brackets, but it's not required. (Trailing commas won't break anything either.)
+- 2/9: `habits: []`
+- 2/10: `habits: [piano]`
+- 2/12: `habits: [yoga, spanish]`
+- 2/16: `habits: `
+- 2/8: `habits: spanish`
+- 2/15: `habits: yoga, spanish, piano`
+
+.
+
+**Case test**: Values stored in frontmatter lists are **case insensitive** for this plugin, mirroring the behavior of tags. In the examples above, `yoga` and `Yoga` should be treated the same.
+
+If search targets are case insensitive, then both 2/14 and 2/17 will be highlighted on the `month:` output for `habit[yoga]` when the examples vault is viewed in Obsidian.
+
+.
+
+**Multi-line list test**: If multi-line lists are working, then 2/14 will be highlighted on each of the calendars above when the examples vault is viewed in Obsidian.
+```
+habits:
+ - yoga
+ - spanish
+ - piano
+```
diff --git a/examples/TestHeatmap.md b/examples/TestHeatmap.md
deleted file mode 100644
index 5bc362c5..00000000
--- a/examples/TestHeatmap.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Test Heat Map
-
-``` tracker
-searchType: tag
-searchTarget: exercise-pushup
-folder: diary
-heatmap:
-```
-
-
-Please also check those search targets in markdown files under folder 'diary'.
-
diff --git a/examples/TestInSentenceFields.md b/examples/TestInSentenceFields.md
new file mode 100644
index 00000000..92d460b7
--- /dev/null
+++ b/examples/TestInSentenceFields.md
@@ -0,0 +1,15 @@
+
+```tracker
+searchType: frontmatter, dvField
+searchTarget: date, count
+xDataset: 0
+folder: "data"
+bullet:
+ title: Total Counts
+ value: "{{sum()}}"
+ range: 1,5,10,20
+ rangeColor: darkRed, lightBlue, lightGreen, blue
+ showMarker: true
+ markerValue: 2
+ markerColor: red
+```
diff --git a/examples/TestMultipleGraphs.md b/examples/TestMultipleGraphs.md
deleted file mode 100644
index d2262dd7..00000000
--- a/examples/TestMultipleGraphs.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Test Multiple Graphs
-
-``` tracker
-searchType: tag
-searchTarget: weight
-endDate: 2021-01-31
-fixedScale: 0.5
-line:
-
-line1:
-
-```
-
- Try parse the display text first, if it does not exist, parse the link text
+wiki.link --> Parse the link text
+wiki.display --> Parse the display text
+
+In the example notes, we have
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+## wiki.link
``` tracker
-searchType: wiki
+searchType: wiki.link
searchTarget: todo_work
folder: diary
startDate: 2021-01-01
endDate: 2021-01-31
-line:
- title: Wiki Links
- yAxisLabel: Count
- lineColor: yellow
+summary:
+ template: '{{sum()::i}}'
```
-Please also check those search targets in markdown files under folder 'diary'.
\ No newline at end of file
+## wiki.display
+``` tracker
+searchType: wiki.display
+searchTarget: To
+folder: diary
+startDate: 2021-01-01
+endDate: 2021-01-31
+summary:
+ template: '{{sum()::i}}'
+```
+
+
+Please also check those search targets in markdown files under folder 'diary'.
diff --git a/examples/data/In-Sentence-Fields.md b/examples/data/In-Sentence-Fields.md
new file mode 100644
index 00000000..ca1556ec
--- /dev/null
+++ b/examples/data/In-Sentence-Fields.md
@@ -0,0 +1,9 @@
+---
+date: 2021-01-05
+---
+
+I am [count:: 1] inline
+So am (count::2) I
+count:: 3
+| count:: 4 |
+|count:: 5|
\ No newline at end of file
diff --git a/examples/data/MTG-Card-1.md b/examples/data/MTG-Card-1.md
new file mode 100644
index 00000000..fdeb29ce
--- /dev/null
+++ b/examples/data/MTG-Card-1.md
@@ -0,0 +1,3 @@
+
+
+{R}{R}
\ No newline at end of file
diff --git a/examples/data/MTG-Card-2.md b/examples/data/MTG-Card-2.md
new file mode 100644
index 00000000..b2d5956c
--- /dev/null
+++ b/examples/data/MTG-Card-2.md
@@ -0,0 +1,3 @@
+
+
+{B}{B}{R}{2}
\ No newline at end of file
diff --git a/examples/data/MTG-Card-3.md b/examples/data/MTG-Card-3.md
new file mode 100644
index 00000000..b5918374
--- /dev/null
+++ b/examples/data/MTG-Card-3.md
@@ -0,0 +1,3 @@
+
+
+{G}{G}{G}{G}{G}{0}
\ No newline at end of file
diff --git a/examples/data/MTG-Card-4.md b/examples/data/MTG-Card-4.md
new file mode 100644
index 00000000..fa7a98cb
--- /dev/null
+++ b/examples/data/MTG-Card-4.md
@@ -0,0 +1,3 @@
+
+
+{W}
\ No newline at end of file
diff --git a/examples/data/MTG-Deck-1.md b/examples/data/MTG-Deck-1.md
new file mode 100644
index 00000000..6194350d
--- /dev/null
+++ b/examples/data/MTG-Deck-1.md
@@ -0,0 +1,6 @@
+
+
+[[MTG-Card-1]] x1
+[[MTG-Card-2]] x2
+[[MTG-Card-3]] x2
+[[MTG-Card-4]] x5
\ No newline at end of file
diff --git a/examples/diary/05-01-2021.md b/examples/diary/05-01-2021.md
index 892a3e00..f9285eb3 100644
--- a/examples/diary/05-01-2021.md
+++ b/examples/diary/05-01-2021.md
@@ -1,6 +1,7 @@
---
tags: work_log
mood: 8
+meditation: true
---
#weight:60.2kg
diff --git a/examples/diary/05.01.2021.md b/examples/diary/05.01.2021.md
index 892a3e00..f9285eb3 100644
--- a/examples/diary/05.01.2021.md
+++ b/examples/diary/05.01.2021.md
@@ -1,6 +1,7 @@
---
tags: work_log
mood: 8
+meditation: true
---
#weight:60.2kg
diff --git a/examples/diary/2021-01-01.md b/examples/diary/2021-01-01.md
index 010e31d1..9539ed22 100644
--- a/examples/diary/2021-01-01.md
+++ b/examples/diary/2021-01-01.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 8
+tags: work_log, work_log2
+mood: 🙁
bloodpressure: 184.4/118.8
bloodpressure1: 184.4, 118.8
bloodpressure2: [184.4, 118.8]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 27.4
+randchar: D
---
#weight:74.1kg
@@ -44,6 +45,11 @@ dataviewTarget1:: 48/29
dataviewTarget2:: 29 @ 88
dataviewTarget3:: 29, 88
+clock-in:: 10:45:29
+clock-out:: 20:51:29
+
+sleep:: 10:11 pm/7:5 am
+
#sin:0.00000/0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452
#sinsquare:0.00000/0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907
diff --git a/examples/diary/2021-01-02.md b/examples/diary/2021-01-02.md
index b8e826f8..efa7db36 100644
--- a/examples/diary/2021-01-02.md
+++ b/examples/diary/2021-01-02.md
@@ -1,6 +1,6 @@
---
tags:
-mood: 7
+mood: 🙁
bloodpressure: 180.8/120.6
bloodpressure1: 180.8, 120.6
bloodpressure2: [180.8, 120.6]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 13.3
+randchar: D
+meditation: true
---
#weight:76.2kg
@@ -44,6 +46,11 @@ dataviewTarget1:: 25/5
dataviewTarget2:: 5 @ 72
dataviewTarget3:: 5, 72
+clock-in:: 8:7:5
+clock-out:: 20:10:5
+
+sleep:: 9:37 pm/7:57 am
+
#sin:0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106
#sinsquare:0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451
diff --git a/examples/diary/2021-01-03.md b/examples/diary/2021-01-03.md
index ff62c5a6..c932cbc2 100644
--- a/examples/diary/2021-01-03.md
+++ b/examples/diary/2021-01-03.md
@@ -1,6 +1,6 @@
---
tags:
-mood: 7
+mood: 🙁
bloodpressure: 180.2/118.4
bloodpressure1: 180.2, 118.4
bloodpressure2: [180.2, 118.4]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 73.0
+randchar: D
+meditation: true
---
#weight:63.2kg
@@ -44,6 +46,11 @@ dataviewTarget1:: 12/14
dataviewTarget2:: 14 @ 82
dataviewTarget3:: 14, 82
+clock-in:: 10:49:14
+clock-out:: 16:33:14
+
+sleep:: 9:17 pm/6:48 am
+
#sin:0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603
#sinsquare:0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000
diff --git a/examples/diary/2021-01-04.md b/examples/diary/2021-01-04.md
index 19d43a89..20e97e97 100644
--- a/examples/diary/2021-01-04.md
+++ b/examples/diary/2021-01-04.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 3
+tags: work_log, work_log2
+mood: 🙂
bloodpressure: 176.6/121.2
bloodpressure1: 176.6, 121.2
bloodpressure2: [176.6, 121.2]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 88.6
+randchar: B
+meditation: true
---
#weight:61.6kg
@@ -28,6 +30,7 @@ deepValue:
⭐
+#clean-up
#finance/bank1:-3.8USD
@@ -47,6 +50,11 @@ dataviewTarget1:: 43/29
dataviewTarget2:: 29 @ 74
dataviewTarget3:: 29, 74
+clock-in:: 10:41:29
+clock-out:: 20:52:29
+
+sleep:: 10:10 pm/6:37 am
+
#sin:0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603/0.74314
#sinsquare:0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226
diff --git a/examples/diary/2021-01-05-Tuesday.md b/examples/diary/2021-01-05-Tuesday.md
index 892a3e00..f9285eb3 100644
--- a/examples/diary/2021-01-05-Tuesday.md
+++ b/examples/diary/2021-01-05-Tuesday.md
@@ -1,6 +1,7 @@
---
tags: work_log
mood: 8
+meditation: true
---
#weight:60.2kg
diff --git a/examples/diary/2021-01-05.md b/examples/diary/2021-01-05.md
index 3265595d..da13f775 100644
--- a/examples/diary/2021-01-05.md
+++ b/examples/diary/2021-01-05.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 2
+tags: work_log, work_log2
+mood: 😀
bloodpressure: 180/118
bloodpressure1: 180, 118
bloodpressure2: [180, 118]
@@ -17,11 +17,13 @@ deepValue:
very:
very:
deep: 13.1
+randchar: A
+meditation: true
---
#weight:72.5kg
-#exercise-pushup:49
+#exercise-pushup:5
#exercise-plank:57sec
#meditation
@@ -46,6 +48,11 @@ dataviewTarget1:: 86/24
dataviewTarget2:: 24 @ 90
dataviewTarget3:: 24, 90
+clock-in:: 9:1:24
+clock-out:: 18:57:24
+
+sleep:: 9:9 pm/5:57 am
+
#sin:0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603/0.74314/0.58779
#sinsquare:0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226/0.34549
diff --git a/examples/diary/2021-01-06.md b/examples/diary/2021-01-06.md
index 3a092b02..21cbfbae 100644
--- a/examples/diary/2021-01-06.md
+++ b/examples/diary/2021-01-06.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 8
+tags: work_log, work_log2
+mood: 🙁
bloodpressure: 171.4/118.8
bloodpressure1: 171.4, 118.8
bloodpressure2: [171.4, 118.8]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 20.7
+randchar: D
+meditation: true
---
#weight:60.1kg
@@ -28,6 +30,7 @@ deepValue:
⭐⭐⭐
+#clean-up
#finance/bank1:-2.4USD
@@ -45,6 +48,11 @@ dataviewTarget1:: 98/46
dataviewTarget2:: 46 @ 75
dataviewTarget3:: 46, 75
+clock-in:: 10:27:46
+clock-out:: 19:26:46
+
+sleep:: 10:30 pm/5:29 am
+
#sin:0.86603/0.95106/0.99452/0.99452/0.95106/0.86603/0.74314/0.58779/0.40674
#sinsquare:0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226/0.34549/0.16543
diff --git a/examples/diary/2021-01-07.md b/examples/diary/2021-01-07.md
index 84988986..b99af2e5 100644
--- a/examples/diary/2021-01-07.md
+++ b/examples/diary/2021-01-07.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 1
+tags: work_log, work_log2
+mood: 😀
bloodpressure: 175.8/120.6
bloodpressure1: 175.8, 120.6
bloodpressure2: [175.8, 120.6]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 47.8
+randchar: A
---
#weight:70.2kg
@@ -45,6 +46,11 @@ dataviewTarget1:: 43/6
dataviewTarget2:: 6 @ 77
dataviewTarget3:: 6, 77
+clock-in:: 9:53:6
+clock-out:: 17:39:6
+
+sleep:: 10:53 pm/7:13 am
+
#sin:0.95106/0.99452/0.99452/0.95106/0.86603/0.74314/0.58779/0.40674/0.20791
#sinsquare:0.90451/0.98907/0.98907/0.90451/0.75000/0.55226/0.34549/0.16543/0.04323
diff --git a/examples/diary/2021-01-08.md b/examples/diary/2021-01-08.md
index bd277b90..ca1e5596 100644
--- a/examples/diary/2021-01-08.md
+++ b/examples/diary/2021-01-08.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 1
+tags: work_log, work_log2
+mood: 😀
bloodpressure: 172.2/116.4
bloodpressure1: 172.2, 116.4
bloodpressure2: [172.2, 116.4]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 68.6
+randchar: A
---
#weight:74.0kg
@@ -45,6 +46,11 @@ dataviewTarget1:: 39/25
dataviewTarget2:: 25 @ 77
dataviewTarget3:: 25, 77
+clock-in:: 8:5:25
+clock-out:: 16:5:25
+
+sleep:: 9:1 pm/6:56 am
+
#sin:0.99452/0.99452/0.95106/0.86603/0.74314/0.58779/0.40674/0.20791/0.00000
#sinsquare:0.98907/0.98907/0.90451/0.75000/0.55226/0.34549/0.16543/0.04323/0.00000
diff --git a/examples/diary/2021-01-09.md b/examples/diary/2021-01-09.md
index 1d4f3943..b3573e8e 100644
--- a/examples/diary/2021-01-09.md
+++ b/examples/diary/2021-01-09.md
@@ -1,6 +1,6 @@
---
tags:
-mood: 1
+mood: 😀
bloodpressure: 175.6/118.2
bloodpressure1: 175.6, 118.2
bloodpressure2: [175.6, 118.2]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 68.6
+randchar: A
+meditation: true
---
#weight:68.8kg
@@ -47,6 +49,11 @@ dataviewTarget1:: 37/36
dataviewTarget2:: 36 @ 53
dataviewTarget3:: 36, 53
+clock-in:: 8:37:36
+clock-out:: 17:9:36
+
+sleep:: 9:23 pm/5:51 am
+
#sin:0.99452/0.95106/0.86603/0.74314/0.58779/0.40674/0.20791/0.00000/-0.20791
#sinsquare:0.98907/0.90451/0.75000/0.55226/0.34549/0.16543/0.04323/0.00000/0.04323
diff --git a/examples/diary/2021-01-10.md b/examples/diary/2021-01-10.md
index 2d9ecc98..fda03e00 100644
--- a/examples/diary/2021-01-10.md
+++ b/examples/diary/2021-01-10.md
@@ -1,6 +1,6 @@
---
tags:
-mood: 3
+mood: 🙂
bloodpressure: 179/116
bloodpressure1: 179, 116
bloodpressure2: [179, 116]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 17.7
+randchar: B
---
#weight:62.1kg
@@ -44,6 +45,11 @@ dataviewTarget1:: 4/10
dataviewTarget2:: 10 @ 92
dataviewTarget3:: 10, 92
+clock-in:: 10:25:10
+clock-out:: 17:55:10
+
+sleep:: 10:11 pm/5:31 am
+
#sin:0.95106/0.86603/0.74314/0.58779/0.40674/0.20791/0.00000/-0.20791/-0.40674
#sinsquare:0.90451/0.75000/0.55226/0.34549/0.16543/0.04323/0.00000/0.04323/0.16543
diff --git a/examples/diary/2021-01-11.md b/examples/diary/2021-01-11.md
index e8fe2dd4..3a751fbc 100644
--- a/examples/diary/2021-01-11.md
+++ b/examples/diary/2021-01-11.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 5
+tags: work_log, work_log2
+mood: 😐
bloodpressure: 173.4/117.8
bloodpressure1: 173.4, 117.8
bloodpressure2: [173.4, 117.8]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 26.2
+randchar: C
---
#weight:80.8kg
@@ -43,6 +44,11 @@ dataviewTarget1:: 97/19
dataviewTarget2:: 19 @ 82
dataviewTarget3:: 19, 82
+clock-in:: 9:42:19
+clock-out:: 20:35:19
+
+sleep:: 11:47 pm/6:8 am
+
#sin:0.86603/0.74314/0.58779/0.40674/0.20791/0.00000/-0.20791/-0.40674/-0.58779
#sinsquare:0.75000/0.55226/0.34549/0.16543/0.04323/0.00000/0.04323/0.16543/0.34549
diff --git a/examples/diary/2021-01-12.md b/examples/diary/2021-01-12.md
index 406957ab..9bebb984 100644
--- a/examples/diary/2021-01-12.md
+++ b/examples/diary/2021-01-12.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 1
+tags: work_log, work_log2
+mood: 😀
bloodpressure: 176.8/118.6
bloodpressure1: 176.8, 118.6
bloodpressure2: [176.8, 118.6]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 45.0
+randchar: A
+meditation: true
---
#weight:61.7kg
@@ -45,6 +47,11 @@ dataviewTarget1:: 79/36
dataviewTarget2:: 36 @ 66
dataviewTarget3:: 36, 66
+clock-in:: 8:54:36
+clock-out:: 16:19:36
+
+sleep:: 11:23 pm/6:15 am
+
#sin:0.74314/0.58779/0.40674/0.20791/0.00000/-0.20791/-0.40674/-0.58779/-0.74314
#sinsquare:0.55226/0.34549/0.16543/0.04323/0.00000/0.04323/0.16543/0.34549/0.55226
diff --git a/examples/diary/2021-01-13.md b/examples/diary/2021-01-13.md
index 20ffccde..0777c374 100644
--- a/examples/diary/2021-01-13.md
+++ b/examples/diary/2021-01-13.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 10
+tags: work_log, work_log2
+mood: 😞
bloodpressure: 168.2/116.4
bloodpressure1: 168.2, 116.4
bloodpressure2: [168.2, 116.4]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 64.5
+randchar: E
+meditation: true
---
#weight:71.7kg
@@ -43,6 +45,11 @@ dataviewTarget1:: 87/46
dataviewTarget2:: 46 @ 73
dataviewTarget3:: 46, 73
+clock-in:: 8:1:46
+clock-out:: 17:0:46
+
+sleep:: 9:2 pm/6:37 am
+
#sin:0.58779/0.40674/0.20791/0.00000/-0.20791/-0.40674/-0.58779/-0.74314/-0.86603
#sinsquare:0.34549/0.16543/0.04323/0.00000/0.04323/0.16543/0.34549/0.55226/0.75000
diff --git a/examples/diary/2021-01-14.md b/examples/diary/2021-01-14.md
index 110f0a21..13677a8f 100644
--- a/examples/diary/2021-01-14.md
+++ b/examples/diary/2021-01-14.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 5
+tags: work_log, work_log2
+mood: 😐
bloodpressure: 175.6/117.2
bloodpressure1: 175.6, 117.2
bloodpressure2: [175.6, 117.2]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 61.5
+randchar: C
+meditation: true
---
#weight:77.1kg
@@ -28,6 +30,7 @@ deepValue:
⭐
+#clean-up
#finance/bank1:-2.5USD
@@ -45,6 +48,11 @@ dataviewTarget1:: 43/5
dataviewTarget2:: 5 @ 92
dataviewTarget3:: 5, 92
+clock-in:: 8:53:5
+clock-out:: 17:19:5
+
+sleep:: 9:40 pm/7:25 am
+
#sin:0.40674/0.20791/0.00000/-0.20791/-0.40674/-0.58779/-0.74314/-0.86603/-0.95106
#sinsquare:0.16543/0.04323/0.00000/0.04323/0.16543/0.34549/0.55226/0.75000/0.90451
diff --git a/examples/diary/2021-01-15.md b/examples/diary/2021-01-15.md
index 06f6b038..2218e8b4 100644
--- a/examples/diary/2021-01-15.md
+++ b/examples/diary/2021-01-15.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 1
+tags: work_log, work_log2
+mood: 😀
bloodpressure: 176/115
bloodpressure1: 176, 115
bloodpressure2: [176, 115]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 44.0
+randchar: A
+meditation: true
---
#weight:69.6kg
@@ -45,6 +47,11 @@ dataviewTarget1:: 60/7
dataviewTarget2:: 7 @ 81
dataviewTarget3:: 7, 81
+clock-in:: 9:20:7
+clock-out:: 18:40:7
+
+sleep:: 11:14 pm/6:44 am
+
#sin:0.20791/0.00000/-0.20791/-0.40674/-0.58779/-0.74314/-0.86603/-0.95106/-0.99452
#sinsquare:0.04323/0.00000/0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907
diff --git a/examples/diary/2021-01-16.md b/examples/diary/2021-01-16.md
index 0f0b8836..866fb52a 100644
--- a/examples/diary/2021-01-16.md
+++ b/examples/diary/2021-01-16.md
@@ -1,6 +1,6 @@
---
tags:
-mood: 9
+mood: 😞
bloodpressure: 168.4/114.8
bloodpressure1: 168.4, 114.8
bloodpressure2: [168.4, 114.8]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 81.6
+randchar: E
---
#weight:70.0kg
@@ -27,6 +28,7 @@ deepValue:
⭐⭐
+#clean-up
#finance/bank1:-2.6USD
@@ -43,6 +45,11 @@ dataviewTarget1:: 17/21
dataviewTarget2:: 21 @ 93
dataviewTarget3:: 21, 93
+clock-in:: 9:54:21
+clock-out:: 18:3:21
+
+sleep:: 11:24 pm/6:40 am
+
#sin:0.00000/-0.20791/-0.40674/-0.58779/-0.74314/-0.86603/-0.95106/-0.99452/-0.99452
#sinsquare:0.00000/0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907
diff --git a/examples/diary/2021-01-17.md b/examples/diary/2021-01-17.md
index dfa72635..99efd482 100644
--- a/examples/diary/2021-01-17.md
+++ b/examples/diary/2021-01-17.md
@@ -1,6 +1,6 @@
---
tags:
-mood: 3
+mood: 🙂
bloodpressure: 165.8/114.6
bloodpressure1: 165.8, 114.6
bloodpressure2: [165.8, 114.6]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 43.0
+randchar: B
---
#weight:64.6kg
@@ -45,6 +46,11 @@ dataviewTarget1:: 46/38
dataviewTarget2:: 38 @ 82
dataviewTarget3:: 38, 82
+clock-in:: 8:3:38
+clock-out:: 16:23:38
+
+sleep:: 10:42 pm/6:30 am
+
#sin:-0.20791/-0.40674/-0.58779/-0.74314/-0.86603/-0.95106/-0.99452/-0.99452/-0.95106
#sinsquare:0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451
diff --git a/examples/diary/2021-01-18.md b/examples/diary/2021-01-18.md
index 8183400c..780b9609 100644
--- a/examples/diary/2021-01-18.md
+++ b/examples/diary/2021-01-18.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 2
+tags: work_log, work_log2
+mood: 😀
bloodpressure: 167.2/118.4
bloodpressure1: 167.2, 118.4
bloodpressure2: [167.2, 118.4]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 19.5
+randchar: A
+meditation: true
---
#weight:78.7kg
@@ -45,6 +47,11 @@ dataviewTarget1:: 28/40
dataviewTarget2:: 40 @ 81
dataviewTarget3:: 40, 81
+clock-in:: 8:48:40
+clock-out:: 19:49:40
+
+sleep:: 9:10 pm/7:51 am
+
#sin:-0.40674/-0.58779/-0.74314/-0.86603/-0.95106/-0.99452/-0.99452/-0.95106/-0.86603
#sinsquare:0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000
diff --git a/examples/diary/2021-01-19.md b/examples/diary/2021-01-19.md
index 7ff90289..78a1428a 100644
--- a/examples/diary/2021-01-19.md
+++ b/examples/diary/2021-01-19.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 6
+tags: work_log, work_log2
+mood: 😐
bloodpressure: 165.6/117.2
bloodpressure1: 165.6, 117.2
bloodpressure2: [165.6, 117.2]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 29.5
+randchar: C
+meditation: true
---
#weight:69.3kg
@@ -45,6 +47,11 @@ dataviewTarget1:: 73/8
dataviewTarget2:: 8 @ 59
dataviewTarget3:: 8, 59
+clock-in:: 8:30:8
+clock-out:: 17:42:8
+
+sleep:: 9:34 pm/7:2 am
+
#sin:-0.58779/-0.74314/-0.86603/-0.95106/-0.99452/-0.99452/-0.95106/-0.86603/-0.74314
#sinsquare:0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226
diff --git a/examples/diary/2021-01-20.md b/examples/diary/2021-01-20.md
index 9ccb4744..e4a2a0a7 100644
--- a/examples/diary/2021-01-20.md
+++ b/examples/diary/2021-01-20.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 4
+tags: work_log, work_log2
+mood: 🙂
bloodpressure: 163/117
bloodpressure1: 163, 117
bloodpressure2: [163, 117]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 56.2
+randchar: B
---
#weight:80.7kg
@@ -43,6 +44,11 @@ dataviewTarget1:: 64/0
dataviewTarget2:: 0 @ 54
dataviewTarget3:: 0, 54
+clock-in:: 9:30:0
+clock-out:: 20:13:0
+
+sleep:: 9:28 pm/7:58 am
+
#sin:-0.74314/-0.86603/-0.95106/-0.99452/-0.99452/-0.95106/-0.86603/-0.74314/-0.58779
#sinsquare:0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226/0.34549
diff --git a/examples/diary/2021-01-21.md b/examples/diary/2021-01-21.md
index ab257cc7..8b9082a5 100644
--- a/examples/diary/2021-01-21.md
+++ b/examples/diary/2021-01-21.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 4
+tags: work_log, work_log2
+mood: 🙂
bloodpressure: 170.4/113.8
bloodpressure1: 170.4, 113.8
bloodpressure2: [170.4, 113.8]
@@ -17,6 +17,9 @@ deepValue:
very:
very:
deep: 11.1
+randchar: B
+sleptwell: false
+meditation: true
---
#weight:67.3kg
@@ -45,6 +48,11 @@ dataviewTarget1:: 88/41
dataviewTarget2:: 41 @ 75
dataviewTarget3:: 41, 75
+clock-in:: 9:4:41
+clock-out:: 19:29:41
+
+sleep:: 10:43 pm/6:37 am
+
#sin:-0.86603/-0.95106/-0.99452/-0.99452/-0.95106/-0.86603/-0.74314/-0.58779/-0.40674
#sinsquare:0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226/0.34549/0.16543
diff --git a/examples/diary/2021-01-22.md b/examples/diary/2021-01-22.md
index 26f4263e..c2fe7fbd 100644
--- a/examples/diary/2021-01-22.md
+++ b/examples/diary/2021-01-22.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 9
+tags: work_log, work_log2
+mood: 😞
bloodpressure: 165.8/116.6
bloodpressure1: 165.8, 116.6
bloodpressure2: [165.8, 116.6]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 83.3
+randchar: E
+sleptwell: true
---
#weight:64.7kg
@@ -44,6 +46,11 @@ dataviewTarget1:: 36/0
dataviewTarget2:: 0 @ 91
dataviewTarget3:: 0, 91
+clock-in:: 9:32:0
+clock-out:: 20:27:0
+
+sleep:: 10:25 pm/5:12 am
+
#sin:-0.95106/-0.99452/-0.99452/-0.95106/-0.86603/-0.74314/-0.58779/-0.40674/-0.20791
#sinsquare:0.90451/0.98907/0.98907/0.90451/0.75000/0.55226/0.34549/0.16543/0.04323
diff --git a/examples/diary/2021-01-23.md b/examples/diary/2021-01-23.md
index a9fd95dd..8dfa52da 100644
--- a/examples/diary/2021-01-23.md
+++ b/examples/diary/2021-01-23.md
@@ -1,6 +1,6 @@
---
tags:
-mood: 1
+mood: 😀
bloodpressure: 164.2/115.4
bloodpressure1: 164.2, 115.4
bloodpressure2: [164.2, 115.4]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 28.8
+randchar: A
+sleptwell: true
---
#weight:67.3kg
@@ -44,6 +46,11 @@ dataviewTarget1:: 45/27
dataviewTarget2:: 27 @ 95
dataviewTarget3:: 27, 95
+clock-in:: 10:36:27
+clock-out:: 19:31:27
+
+sleep:: 10:9 pm/7:30 am
+
#sin:-0.99452/-0.99452/-0.95106/-0.86603/-0.74314/-0.58779/-0.40674/-0.20791/-0.00000
#sinsquare:0.98907/0.98907/0.90451/0.75000/0.55226/0.34549/0.16543/0.04323/0.00000
diff --git a/examples/diary/2021-01-24.md b/examples/diary/2021-01-24.md
index 40a9ffbd..9deeb9fe 100644
--- a/examples/diary/2021-01-24.md
+++ b/examples/diary/2021-01-24.md
@@ -1,6 +1,6 @@
---
tags:
-mood: 4
+mood: 🙂
bloodpressure: 170.6/113.2
bloodpressure1: 170.6, 113.2
bloodpressure2: [170.6, 113.2]
@@ -17,6 +17,9 @@ deepValue:
very:
very:
deep: 39.4
+randchar: B
+sleptwell: true
+meditation: true
---
#weight:64.1kg
@@ -46,6 +49,11 @@ dataviewTarget1:: 63/23
dataviewTarget2:: 23 @ 60
dataviewTarget3:: 23, 60
+clock-in:: 8:1:23
+clock-out:: 18:9:23
+
+sleep:: 10:53 pm/5:33 am
+
#sin:-0.99452/-0.95106/-0.86603/-0.74314/-0.58779/-0.40674/-0.20791/-0.00000/0.20791
#sinsquare:0.98907/0.90451/0.75000/0.55226/0.34549/0.16543/0.04323/0.00000/0.04323
diff --git a/examples/diary/2021-01-25.md b/examples/diary/2021-01-25.md
index f1c93f4a..ba63688d 100644
--- a/examples/diary/2021-01-25.md
+++ b/examples/diary/2021-01-25.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 9
+tags: work_log, work_log2
+mood: 😞
bloodpressure: 167/114
bloodpressure1: 167, 114
bloodpressure2: [167, 114]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 81.4
+randchar: E
+sleptwell: false
---
#weight:62.4kg
@@ -43,6 +45,11 @@ dataviewTarget1:: 88/12
dataviewTarget2:: 12 @ 96
dataviewTarget3:: 12, 96
+clock-in:: 9:52:12
+clock-out:: 17:55:12
+
+sleep:: 10:53 pm/6:3 am
+
#sin:-0.95106/-0.86603/-0.74314/-0.58779/-0.40674/-0.20791/-0.00000/0.20791/0.40674
#sinsquare:0.90451/0.75000/0.55226/0.34549/0.16543/0.04323/0.00000/0.04323/0.16543
diff --git a/examples/diary/2021-01-26.md b/examples/diary/2021-01-26.md
index f99c94c8..70415052 100644
--- a/examples/diary/2021-01-26.md
+++ b/examples/diary/2021-01-26.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 3
+tags: work_log, work_log2
+mood: 🙂
bloodpressure: 162.4/114.8
bloodpressure1: 162.4, 114.8
bloodpressure2: [162.4, 114.8]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 73.1
+randchar: B
+meditation: true
---
#weight:72.7kg
@@ -44,6 +46,11 @@ dataviewTarget1:: 42/19
dataviewTarget2:: 19 @ 93
dataviewTarget3:: 19, 93
+clock-in:: 10:24:19
+clock-out:: 19:12:19
+
+sleep:: 11:36 pm/6:2 am
+
#sin:-0.86603/-0.74314/-0.58779/-0.40674/-0.20791/-0.00000/0.20791/0.40674/0.58779
#sinsquare:0.75000/0.55226/0.34549/0.16543/0.04323/0.00000/0.04323/0.16543/0.34549
diff --git a/examples/diary/2021-01-27.md b/examples/diary/2021-01-27.md
index 2fbbb88e..89b43c94 100644
--- a/examples/diary/2021-01-27.md
+++ b/examples/diary/2021-01-27.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 4
+tags: work_log, work_log2
+mood: 🙂
bloodpressure: 165.8/113.6
bloodpressure1: 165.8, 113.6
bloodpressure2: [165.8, 113.6]
@@ -17,6 +17,8 @@ deepValue:
very:
very:
deep: 78.5
+randchar: B
+meditation: true
---
#weight:63.7kg
@@ -45,6 +47,11 @@ dataviewTarget1:: 59/31
dataviewTarget2:: 31 @ 67
dataviewTarget3:: 31, 67
+clock-in:: 10:28:31
+clock-out:: 18:4:31
+
+sleep:: 11:34 pm/6:12 am
+
#sin:-0.74314/-0.58779/-0.40674/-0.20791/-0.00000/0.20791/0.40674/0.58779/0.74314
#sinsquare:0.55226/0.34549/0.16543/0.04323/0.00000/0.04323/0.16543/0.34549/0.55226
diff --git a/examples/diary/2021-01-28.md b/examples/diary/2021-01-28.md
index c8b3927b..efc0f2da 100644
--- a/examples/diary/2021-01-28.md
+++ b/examples/diary/2021-01-28.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 2
+tags: work_log, work_log2
+mood: 😀
bloodpressure: 158.2/115.4
bloodpressure1: 158.2, 115.4
bloodpressure2: [158.2, 115.4]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 80.6
+randchar: A
---
#weight:79.2kg
@@ -45,6 +46,11 @@ dataviewTarget1:: 36/33
dataviewTarget2:: 33 @ 92
dataviewTarget3:: 33, 92
+clock-in:: 10:44:33
+clock-out:: 16:41:33
+
+sleep:: 10:27 pm/5:11 am
+
#sin:-0.58779/-0.40674/-0.20791/-0.00000/0.20791/0.40674/0.58779/0.74314/0.86603
#sinsquare:0.34549/0.16543/0.04323/0.00000/0.04323/0.16543/0.34549/0.55226/0.75000
diff --git a/examples/diary/2021-01-29.md b/examples/diary/2021-01-29.md
index 8b74a63d..d2f984ac 100644
--- a/examples/diary/2021-01-29.md
+++ b/examples/diary/2021-01-29.md
@@ -1,6 +1,6 @@
---
-tags: work_log
-mood: 8
+tags: work_log, work_log2
+mood: 🙁
bloodpressure: 163.6/112.2
bloodpressure1: 163.6, 112.2
bloodpressure2: [163.6, 112.2]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 18.6
+randchar: D
---
#weight:63.5kg
@@ -44,6 +45,11 @@ dataviewTarget1:: 96/44
dataviewTarget2:: 44 @ 90
dataviewTarget3:: 44, 90
+clock-in:: 8:19:44
+clock-out:: 16:35:44
+
+sleep:: 11:47 pm/6:39 am
+
#sin:-0.40674/-0.20791/-0.00000/0.20791/0.40674/0.58779/0.74314/0.86603/0.95106
#sinsquare:0.16543/0.04323/0.00000/0.04323/0.16543/0.34549/0.55226/0.75000/0.90451
diff --git a/examples/diary/2021-01-30.md b/examples/diary/2021-01-30.md
index a6196d32..024410ef 100644
--- a/examples/diary/2021-01-30.md
+++ b/examples/diary/2021-01-30.md
@@ -1,6 +1,6 @@
---
tags:
-mood: 5
+mood: 😐
bloodpressure: 159/114
bloodpressure1: 159, 114
bloodpressure2: [159, 114]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 17.9
+randchar: C
---
#weight:63.3kg
@@ -47,6 +48,11 @@ dataviewTarget1:: 44/48
dataviewTarget2:: 48 @ 84
dataviewTarget3:: 48, 84
+clock-in:: 10:24:48
+clock-out:: 18:4:48
+
+sleep:: 11:10 pm/5:46 am
+
#sin:-0.20791/-0.00000/0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452
#sinsquare:0.04323/0.00000/0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907
diff --git a/examples/diary/2021-01-31.md b/examples/diary/2021-01-31.md
index dbc610f5..4e82ad9a 100644
--- a/examples/diary/2021-01-31.md
+++ b/examples/diary/2021-01-31.md
@@ -1,6 +1,6 @@
---
tags:
-mood: 4
+mood: 🙂
bloodpressure: 160.4/115.8
bloodpressure1: 160.4, 115.8
bloodpressure2: [160.4, 115.8]
@@ -17,6 +17,7 @@ deepValue:
very:
very:
deep: 56.8
+randchar: B
---
#weight:72.2kg
@@ -44,6 +45,11 @@ dataviewTarget1:: 59/22
dataviewTarget2:: 22 @ 55
dataviewTarget3:: 22, 55
+clock-in:: 10:5:22
+clock-out:: 17:3:22
+
+sleep:: 9:14 pm/5:9 am
+
#sin:-0.00000/0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452
#sinsquare:0.00000/0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907
diff --git a/examples/diary/20210103-D.md b/examples/diary/20210103-D.md
index 3a89f50d..68128a39 100644
--- a/examples/diary/20210103-D.md
+++ b/examples/diary/20210103-D.md
@@ -2,6 +2,7 @@
tags:
mood: 7
bloodpressure: 178.2/119.4
+meditation: true
---
#weight:74.9kg
diff --git a/examples/diary/20210104-D.md b/examples/diary/20210104-D.md
index 6ee80bef..7b0f4593 100644
--- a/examples/diary/20210104-D.md
+++ b/examples/diary/20210104-D.md
@@ -2,6 +2,7 @@
tags: work_log
mood: 3
bloodpressure: 178.6/119.2
+meditation: true
---
#weight:73.5kg
diff --git a/examples/diary/20210105-D.md b/examples/diary/20210105-D.md
index c836d811..9af72afe 100644
--- a/examples/diary/20210105-D.md
+++ b/examples/diary/20210105-D.md
@@ -2,11 +2,12 @@
tags: work_log
mood: 1
bloodpressure: 177/119
+meditation: true
---
#weight:70.0kg
-#exercise-pushup:35
+#exercise-pushup:5
#exercise-plank:34sec
#meditation
diff --git a/examples/diary/2023-06-05.md b/examples/diary/2023-06-05.md
new file mode 100644
index 00000000..80f84e78
--- /dev/null
+++ b/examples/diary/2023-06-05.md
@@ -0,0 +1 @@
+Physical:: 😐
\ No newline at end of file
diff --git a/examples/diary/2023-06-06.md b/examples/diary/2023-06-06.md
new file mode 100644
index 00000000..80f84e78
--- /dev/null
+++ b/examples/diary/2023-06-06.md
@@ -0,0 +1 @@
+Physical:: 😐
\ No newline at end of file
diff --git a/examples/diary/2023-06-07.md b/examples/diary/2023-06-07.md
new file mode 100644
index 00000000..63787743
--- /dev/null
+++ b/examples/diary/2023-06-07.md
@@ -0,0 +1 @@
+Physical:: 😀
\ No newline at end of file
diff --git a/examples/diary/2023-06-08.md b/examples/diary/2023-06-08.md
new file mode 100644
index 00000000..7074a691
--- /dev/null
+++ b/examples/diary/2023-06-08.md
@@ -0,0 +1 @@
+Physical:: 😞
\ No newline at end of file
diff --git a/examples/diary/2026-02-08.md b/examples/diary/2026-02-08.md
new file mode 100644
index 00000000..2fb751fb
--- /dev/null
+++ b/examples/diary/2026-02-08.md
@@ -0,0 +1,3 @@
+---
+habits: spanish
+---
diff --git a/examples/diary/2026-02-09.md b/examples/diary/2026-02-09.md
new file mode 100644
index 00000000..b71f1331
--- /dev/null
+++ b/examples/diary/2026-02-09.md
@@ -0,0 +1,4 @@
+---
+habits: []
+---
+ Punxsutawney Phil was right :(
\ No newline at end of file
diff --git a/examples/diary/2026-02-10.md b/examples/diary/2026-02-10.md
new file mode 100644
index 00000000..34ea72f9
--- /dev/null
+++ b/examples/diary/2026-02-10.md
@@ -0,0 +1,4 @@
+---
+habits: [piano]
+---
+boop beep
\ No newline at end of file
diff --git a/examples/diary/2026-02-11.md b/examples/diary/2026-02-11.md
new file mode 100644
index 00000000..7e28c16a
--- /dev/null
+++ b/examples/diary/2026-02-11.md
@@ -0,0 +1,4 @@
+---
+habits: [yoga, spanish, piano]
+---
+build all the habits
\ No newline at end of file
diff --git a/examples/diary/2026-02-12.md b/examples/diary/2026-02-12.md
new file mode 100644
index 00000000..94e3ddde
--- /dev/null
+++ b/examples/diary/2026-02-12.md
@@ -0,0 +1,3 @@
+---
+habits: [yoga, spanish]
+---
diff --git a/examples/diary/2026-02-13.md b/examples/diary/2026-02-13.md
new file mode 100644
index 00000000..229d1079
--- /dev/null
+++ b/examples/diary/2026-02-13.md
@@ -0,0 +1,4 @@
+---
+habits: [spanish, piano,]
+---
+a trailing comma snuck inside the brackets, but will it cause problems? tune into [[TestFrontmatterList]] tonight at 8 to find out.
\ No newline at end of file
diff --git a/examples/diary/2026-02-14.md b/examples/diary/2026-02-14.md
new file mode 100644
index 00000000..bf549207
--- /dev/null
+++ b/examples/diary/2026-02-14.md
@@ -0,0 +1,7 @@
+---
+habits:
+ - yoga
+ - spanish
+ - piano
+---
+habits, but as a fancy little bullet list
\ No newline at end of file
diff --git a/examples/diary/2026-02-15.md b/examples/diary/2026-02-15.md
new file mode 100644
index 00000000..a229124d
--- /dev/null
+++ b/examples/diary/2026-02-15.md
@@ -0,0 +1,5 @@
+---
+habits: yoga, spanish, piano
+---
+
+such productivity, wow
diff --git a/examples/diary/2026-02-16.md b/examples/diary/2026-02-16.md
new file mode 100644
index 00000000..e97d2472
--- /dev/null
+++ b/examples/diary/2026-02-16.md
@@ -0,0 +1,6 @@
+---
+habits:
+---
+
+everybody deserve a little lazy day every now and then :)
+
diff --git a/examples/diary/2026-02-17.md b/examples/diary/2026-02-17.md
new file mode 100644
index 00000000..17cf3d26
--- /dev/null
+++ b/examples/diary/2026-02-17.md
@@ -0,0 +1,5 @@
+---
+habits: Yoga, spanish, Piano
+---
+
+let's get wAcKy with these cAsEs
\ No newline at end of file
diff --git a/examples/diary/2026-02-18.md b/examples/diary/2026-02-18.md
new file mode 100644
index 00000000..631a1e3b
--- /dev/null
+++ b/examples/diary/2026-02-18.md
@@ -0,0 +1,4 @@
+---
+habits: Yoga, spanish, Piano,
+---
+a wild trailing comma appeared
\ No newline at end of file
diff --git a/examples/diary/5-20210105.md b/examples/diary/5-20210105.md
index 892a3e00..f9285eb3 100644
--- a/examples/diary/5-20210105.md
+++ b/examples/diary/5-20210105.md
@@ -1,6 +1,7 @@
---
tags: work_log
mood: 8
+meditation: true
---
#weight:60.2kg
diff --git a/examples/diary/D-20210103.md b/examples/diary/D-20210103.md
index 3a89f50d..68128a39 100644
--- a/examples/diary/D-20210103.md
+++ b/examples/diary/D-20210103.md
@@ -2,6 +2,7 @@
tags:
mood: 7
bloodpressure: 178.2/119.4
+meditation: true
---
#weight:74.9kg
diff --git a/examples/diary/D-20210104.md b/examples/diary/D-20210104.md
index 6ee80bef..7b0f4593 100644
--- a/examples/diary/D-20210104.md
+++ b/examples/diary/D-20210104.md
@@ -2,6 +2,7 @@
tags: work_log
mood: 3
bloodpressure: 178.6/119.2
+meditation: true
---
#weight:73.5kg
diff --git a/examples/diary/D-20210105.md b/examples/diary/D-20210105.md
index c836d811..9af72afe 100644
--- a/examples/diary/D-20210105.md
+++ b/examples/diary/D-20210105.md
@@ -2,11 +2,12 @@
tags: work_log
mood: 1
bloodpressure: 177/119
+meditation: true
---
#weight:70.0kg
-#exercise-pushup:35
+#exercise-pushup:5
#exercise-plank:34sec
#meditation
diff --git a/examples/diary/Jeffrey-20210101-Diary.md b/examples/diary/Jeffrey-20210101-Diary.md
new file mode 100644
index 00000000..294ce1a9
--- /dev/null
+++ b/examples/diary/Jeffrey-20210101-Diary.md
@@ -0,0 +1,28 @@
+---
+tags: work_log
+mood: 10
+bloodpressure: 177.4/121.8
+---
+
+#weight:74.0kg
+
+#exercise-pushup:44
+#exercise-plank:37sec
+
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-3.4USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker+1@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 10
+
+#sin:0.00000/0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452
+
+#sinsquare:0.00000/0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907
+
diff --git a/examples/diary/Jeffrey-20210101-Journal.md b/examples/diary/Jeffrey-20210101-Journal.md
new file mode 100644
index 00000000..294ce1a9
--- /dev/null
+++ b/examples/diary/Jeffrey-20210101-Journal.md
@@ -0,0 +1,28 @@
+---
+tags: work_log
+mood: 10
+bloodpressure: 177.4/121.8
+---
+
+#weight:74.0kg
+
+#exercise-pushup:44
+#exercise-plank:37sec
+
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-3.4USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker+1@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 10
+
+#sin:0.00000/0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452
+
+#sinsquare:0.00000/0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907
+
diff --git a/examples/diary/Jeffrey-20210102-Diary.md b/examples/diary/Jeffrey-20210102-Diary.md
new file mode 100644
index 00000000..f9425bd1
--- /dev/null
+++ b/examples/diary/Jeffrey-20210102-Diary.md
@@ -0,0 +1,28 @@
+---
+tags:
+mood: 8
+bloodpressure: 174.8/121.6
+---
+
+#weight:68.8kg
+
+#exercise-pushup:38
+#exercise-plank:112sec
+
+
+⭐⭐⭐
+
+#finance/bank1:-2.7USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 16
+
+#sin:0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106
+
+#sinsquare:0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451
+
diff --git a/examples/diary/Jeffrey-20210102-Journal.md b/examples/diary/Jeffrey-20210102-Journal.md
new file mode 100644
index 00000000..f9425bd1
--- /dev/null
+++ b/examples/diary/Jeffrey-20210102-Journal.md
@@ -0,0 +1,28 @@
+---
+tags:
+mood: 8
+bloodpressure: 174.8/121.6
+---
+
+#weight:68.8kg
+
+#exercise-pushup:38
+#exercise-plank:112sec
+
+
+⭐⭐⭐
+
+#finance/bank1:-2.7USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 16
+
+#sin:0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106
+
+#sinsquare:0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451
+
diff --git a/examples/diary/Jeffrey-20210103-Diary.md b/examples/diary/Jeffrey-20210103-Diary.md
new file mode 100644
index 00000000..68128a39
--- /dev/null
+++ b/examples/diary/Jeffrey-20210103-Diary.md
@@ -0,0 +1,31 @@
+---
+tags:
+mood: 7
+bloodpressure: 178.2/119.4
+meditation: true
+---
+
+#weight:74.9kg
+
+#exercise-pushup:32
+#exercise-plank:56sec
+
+#meditation
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-3.7USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+obsidian-tracker+1@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 16
+
+#sin:0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603
+
+#sinsquare:0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000
+
diff --git a/examples/diary/Jeffrey-20210103-Journal.md b/examples/diary/Jeffrey-20210103-Journal.md
new file mode 100644
index 00000000..68128a39
--- /dev/null
+++ b/examples/diary/Jeffrey-20210103-Journal.md
@@ -0,0 +1,31 @@
+---
+tags:
+mood: 7
+bloodpressure: 178.2/119.4
+meditation: true
+---
+
+#weight:74.9kg
+
+#exercise-pushup:32
+#exercise-plank:56sec
+
+#meditation
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-3.7USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+obsidian-tracker+1@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 16
+
+#sin:0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603
+
+#sinsquare:0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000
+
diff --git a/examples/diary/Jeffrey-20210104-Diary.md b/examples/diary/Jeffrey-20210104-Diary.md
new file mode 100644
index 00000000..7b0f4593
--- /dev/null
+++ b/examples/diary/Jeffrey-20210104-Diary.md
@@ -0,0 +1,28 @@
+---
+tags: work_log
+mood: 3
+bloodpressure: 178.6/119.2
+meditation: true
+---
+
+#weight:73.5kg
+
+#exercise-pushup:40
+#exercise-plank:74sec
+
+#meditation
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-2.5USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+
+weightlifting: 19
+
+#sin:0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603/0.74314
+
+#sinsquare:0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226
+
diff --git a/examples/diary/Jeffrey-20210104-Journal.md b/examples/diary/Jeffrey-20210104-Journal.md
new file mode 100644
index 00000000..7b0f4593
--- /dev/null
+++ b/examples/diary/Jeffrey-20210104-Journal.md
@@ -0,0 +1,28 @@
+---
+tags: work_log
+mood: 3
+bloodpressure: 178.6/119.2
+meditation: true
+---
+
+#weight:73.5kg
+
+#exercise-pushup:40
+#exercise-plank:74sec
+
+#meditation
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-2.5USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+
+weightlifting: 19
+
+#sin:0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603/0.74314
+
+#sinsquare:0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226
+
diff --git a/examples/diary/Jeffrey-20210105-Diary.md b/examples/diary/Jeffrey-20210105-Diary.md
new file mode 100644
index 00000000..9af72afe
--- /dev/null
+++ b/examples/diary/Jeffrey-20210105-Diary.md
@@ -0,0 +1,29 @@
+---
+tags: work_log
+mood: 1
+bloodpressure: 177/119
+meditation: true
+---
+
+#weight:70.0kg
+
+#exercise-pushup:5
+#exercise-plank:34sec
+
+#meditation
+
+⭐⭐
+
+#finance/bank1:-2.6USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+
+weightlifting: 10
+
+#sin:0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603/0.74314/0.58779
+
+#sinsquare:0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226/0.34549
+
diff --git a/examples/diary/Jeffrey-20210105-Journal.md b/examples/diary/Jeffrey-20210105-Journal.md
new file mode 100644
index 00000000..9af72afe
--- /dev/null
+++ b/examples/diary/Jeffrey-20210105-Journal.md
@@ -0,0 +1,29 @@
+---
+tags: work_log
+mood: 1
+bloodpressure: 177/119
+meditation: true
+---
+
+#weight:70.0kg
+
+#exercise-pushup:5
+#exercise-plank:34sec
+
+#meditation
+
+⭐⭐
+
+#finance/bank1:-2.6USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+
+weightlifting: 10
+
+#sin:0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603/0.74314/0.58779
+
+#sinsquare:0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226/0.34549
+
diff --git a/examples/diary/Lucas-20210101-Diary.md b/examples/diary/Lucas-20210101-Diary.md
new file mode 100644
index 00000000..294ce1a9
--- /dev/null
+++ b/examples/diary/Lucas-20210101-Diary.md
@@ -0,0 +1,28 @@
+---
+tags: work_log
+mood: 10
+bloodpressure: 177.4/121.8
+---
+
+#weight:74.0kg
+
+#exercise-pushup:44
+#exercise-plank:37sec
+
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-3.4USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker+1@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 10
+
+#sin:0.00000/0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452
+
+#sinsquare:0.00000/0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907
+
diff --git a/examples/diary/Lucas-20210101-Journal.md b/examples/diary/Lucas-20210101-Journal.md
new file mode 100644
index 00000000..294ce1a9
--- /dev/null
+++ b/examples/diary/Lucas-20210101-Journal.md
@@ -0,0 +1,28 @@
+---
+tags: work_log
+mood: 10
+bloodpressure: 177.4/121.8
+---
+
+#weight:74.0kg
+
+#exercise-pushup:44
+#exercise-plank:37sec
+
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-3.4USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker+1@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 10
+
+#sin:0.00000/0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452
+
+#sinsquare:0.00000/0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907
+
diff --git a/examples/diary/Lucas-20210102-Diary.md b/examples/diary/Lucas-20210102-Diary.md
new file mode 100644
index 00000000..f9425bd1
--- /dev/null
+++ b/examples/diary/Lucas-20210102-Diary.md
@@ -0,0 +1,28 @@
+---
+tags:
+mood: 8
+bloodpressure: 174.8/121.6
+---
+
+#weight:68.8kg
+
+#exercise-pushup:38
+#exercise-plank:112sec
+
+
+⭐⭐⭐
+
+#finance/bank1:-2.7USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 16
+
+#sin:0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106
+
+#sinsquare:0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451
+
diff --git a/examples/diary/Lucas-20210102-Journal.md b/examples/diary/Lucas-20210102-Journal.md
new file mode 100644
index 00000000..f9425bd1
--- /dev/null
+++ b/examples/diary/Lucas-20210102-Journal.md
@@ -0,0 +1,28 @@
+---
+tags:
+mood: 8
+bloodpressure: 174.8/121.6
+---
+
+#weight:68.8kg
+
+#exercise-pushup:38
+#exercise-plank:112sec
+
+
+⭐⭐⭐
+
+#finance/bank1:-2.7USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 16
+
+#sin:0.20791/0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106
+
+#sinsquare:0.04323/0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451
+
diff --git a/examples/diary/Lucas-20210103-Diary.md b/examples/diary/Lucas-20210103-Diary.md
new file mode 100644
index 00000000..68128a39
--- /dev/null
+++ b/examples/diary/Lucas-20210103-Diary.md
@@ -0,0 +1,31 @@
+---
+tags:
+mood: 7
+bloodpressure: 178.2/119.4
+meditation: true
+---
+
+#weight:74.9kg
+
+#exercise-pushup:32
+#exercise-plank:56sec
+
+#meditation
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-3.7USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+obsidian-tracker+1@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 16
+
+#sin:0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603
+
+#sinsquare:0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000
+
diff --git a/examples/diary/Lucas-20210103-Journal.md b/examples/diary/Lucas-20210103-Journal.md
new file mode 100644
index 00000000..68128a39
--- /dev/null
+++ b/examples/diary/Lucas-20210103-Journal.md
@@ -0,0 +1,31 @@
+---
+tags:
+mood: 7
+bloodpressure: 178.2/119.4
+meditation: true
+---
+
+#weight:74.9kg
+
+#exercise-pushup:32
+#exercise-plank:56sec
+
+#meditation
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-3.7USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+obsidian-tracker+1@gmail.com
+obsidian-tracker@yahoo.com
+
+weightlifting: 16
+
+#sin:0.40674/0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603
+
+#sinsquare:0.16543/0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000
+
diff --git a/examples/diary/Lucas-20210104-Diary.md b/examples/diary/Lucas-20210104-Diary.md
new file mode 100644
index 00000000..7b0f4593
--- /dev/null
+++ b/examples/diary/Lucas-20210104-Diary.md
@@ -0,0 +1,28 @@
+---
+tags: work_log
+mood: 3
+bloodpressure: 178.6/119.2
+meditation: true
+---
+
+#weight:73.5kg
+
+#exercise-pushup:40
+#exercise-plank:74sec
+
+#meditation
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-2.5USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+
+weightlifting: 19
+
+#sin:0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603/0.74314
+
+#sinsquare:0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226
+
diff --git a/examples/diary/Lucas-20210104-Journal.md b/examples/diary/Lucas-20210104-Journal.md
new file mode 100644
index 00000000..7b0f4593
--- /dev/null
+++ b/examples/diary/Lucas-20210104-Journal.md
@@ -0,0 +1,28 @@
+---
+tags: work_log
+mood: 3
+bloodpressure: 178.6/119.2
+meditation: true
+---
+
+#weight:73.5kg
+
+#exercise-pushup:40
+#exercise-plank:74sec
+
+#meditation
+
+⭐⭐⭐⭐⭐
+
+#finance/bank1:-2.5USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+
+weightlifting: 19
+
+#sin:0.58779/0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603/0.74314
+
+#sinsquare:0.34549/0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226
+
diff --git a/examples/diary/Lucas-20210105-Diary.md b/examples/diary/Lucas-20210105-Diary.md
new file mode 100644
index 00000000..9af72afe
--- /dev/null
+++ b/examples/diary/Lucas-20210105-Diary.md
@@ -0,0 +1,29 @@
+---
+tags: work_log
+mood: 1
+bloodpressure: 177/119
+meditation: true
+---
+
+#weight:70.0kg
+
+#exercise-pushup:5
+#exercise-plank:34sec
+
+#meditation
+
+⭐⭐
+
+#finance/bank1:-2.6USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+
+weightlifting: 10
+
+#sin:0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603/0.74314/0.58779
+
+#sinsquare:0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226/0.34549
+
diff --git a/examples/diary/Lucas-20210105-Journal.md b/examples/diary/Lucas-20210105-Journal.md
new file mode 100644
index 00000000..9af72afe
--- /dev/null
+++ b/examples/diary/Lucas-20210105-Journal.md
@@ -0,0 +1,29 @@
+---
+tags: work_log
+mood: 1
+bloodpressure: 177/119
+meditation: true
+---
+
+#weight:70.0kg
+
+#exercise-pushup:5
+#exercise-plank:34sec
+
+#meditation
+
+⭐⭐
+
+#finance/bank1:-2.6USD
+
+[[todo_family|To-Do @Family]]
+[[todo_work|To-Do @Work]]
+
+obsidian-tracker@gmail.com
+
+weightlifting: 10
+
+#sin:0.74314/0.86603/0.95106/0.99452/0.99452/0.95106/0.86603/0.74314/0.58779
+
+#sinsquare:0.55226/0.75000/0.90451/0.98907/0.98907/0.90451/0.75000/0.55226/0.34549
+
diff --git a/examples/example_generator.ts b/examples/example_generator.ts
index 43181290..53e67ae6 100644
--- a/examples/example_generator.ts
+++ b/examples/example_generator.ts
@@ -44,16 +44,18 @@ for (
// fontmatter
let frontmatter = "---\n";
- let weekday = curDate.weekday();
+
// front matter tags
+ let weekday = curDate.weekday();
if (weekday == 0 || weekday == 6) {
frontmatter += "tags: " + "\n";
} else {
- frontmatter += "tags: " + "work_log" + "\n";
+ frontmatter += "tags: " + "work_log" + ", " + "work_log2" + "\n";
}
// frontmatter mood
- let mood = randomIntFromInterval(1, 10);
- frontmatter += "mood: " + mood + "\n";
+ let moodSymbols = ["😀", "🙂", "😐", "🙁", "😞"];
+ let indMood = randomIntFromInterval(0, 4);
+ frontmatter += "mood: " + moodSymbols[indMood] + "\n";
// blood pressure
let progress = dayCount;
@@ -118,6 +120,9 @@ for (
}
frontmatter += indent + "deep: " + deepValue.toFixed(1) + "\n";
+ // random character
+ frontmatter += "randchar: " + String.fromCharCode(65+indMood) + "\n";
+
frontmatter += "---\n";
content += frontmatter;
@@ -160,7 +165,7 @@ for (
// clean up
let tagCleanUp = "#clean-up";
- let doCleanUp = randomIntFromInterval(0, 30);
+ let doCleanUp = randomIntFromInterval(0, 5);
if (doCleanUp === 1) {
content += tagCleanUp + "\n";
}
@@ -227,6 +232,18 @@ for (
content += "\n";
+ // clock-in clock-out in dvField
+ let seconds = dataviewValue1;
+ content += "clock-in:: " + time_clock_in + ":" + seconds + "\n";
+ content += "clock-out:: " + time_clock_out + ":" + seconds +"\n";
+
+ content += "\n";
+
+ // sleep in dvField
+ content += "sleep:: " + time_in_bed + "/" + time_out_of_bed + "\n";
+
+ content += "\n";
+
let amplitude = 1.0;
let period = 30; // how many days to complete a sin period
let numSinValues = 9;
diff --git a/gulpfile.js b/gulpfile.js
deleted file mode 100644
index a321f1c8..00000000
--- a/gulpfile.js
+++ /dev/null
@@ -1,13 +0,0 @@
-const { src, dest } = require("gulp");
-const zip = require("gulp-zip");
-const fs = require("fs");
-
-const manifest = fs.readFileSync("./manifest.json");
-var version = JSON.parse(manifest)["version"];
-var zipFileName = "obsidian-tracker-v" + version + ".zip";
-
-exports.default = function () {
- return src(["main.js", "manifest.json", "styles.css"])
- .pipe(zip(zipFileName))
- .pipe(dest("."));
-};
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 00000000..f4a88d88
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,29 @@
+module.exports = {
+ testEnvironment: 'node',
+ roots: ['/test'],
+ testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
+ transform: {
+ '^.+\\.ts$': ['ts-jest', {
+ tsconfig: {
+ esModuleInterop: true,
+ allowSyntheticDefaultImports: true,
+ module: 'commonjs',
+ },
+ }],
+ },
+ moduleNameMapper: {
+ '^obsidian$': '/test/mocks/obsidian.ts',
+ // Mock d3 and other ES module dependencies
+ '^d3$': '/test/mocks/d3.ts',
+ },
+ collectCoverageFrom: [
+ 'src/**/*.ts',
+ '!src/**/*.d.ts',
+ ],
+ setupFilesAfterEnv: ['/test/setup.ts'],
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
+ // Transform ES modules in node_modules
+ transformIgnorePatterns: [
+ 'node_modules/(?!(d3|d3-.*|moment)/)',
+ ],
+};
diff --git a/manifest.json b/manifest.json
index 5ee61fa2..7840aaf6 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,10 +1,10 @@
-{
- "id": "obsidian-tracker",
- "name": "Tracker",
- "version": "1.8.2",
- "minAppVersion": "0.9.12",
- "description": "A plugin tracks occurrences and numbers in your notes",
- "author": "pyrochlore",
- "authorUrl": "",
- "isDesktopOnly": false
-}
+{
+ "id": "obsidian-tracker",
+ "name": "Tracker",
+ "version": "1.19.0",
+ "minAppVersion": "0.9.12",
+ "description": "A plugin tracks occurrences and numbers in your notes",
+ "author": "pyrochlore",
+ "authorUrl": "",
+ "isDesktopOnly": false
+}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 00000000..a8d10a0a
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6578 @@
+{
+ "name": "obsidian-tracker",
+ "version": "1.19.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "obsidian-tracker",
+ "version": "1.19.0",
+ "license": "MIT",
+ "dependencies": {
+ "d3": "^7.9.0",
+ "d3-interpolate": "^3.0.1",
+ "jsep": "^1.4.0",
+ "sprintf-js": "^1.1.3"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^29.0.0",
+ "@rollup/plugin-node-resolve": "^16.0.3",
+ "@rollup/plugin-terser": "^0.4.4",
+ "@rollup/plugin-typescript": "^12.3.0",
+ "@types/d3": "^7.4.3",
+ "@types/jest": "^30.0.0",
+ "@types/node": "^25.3.3",
+ "@types/sprintf-js": "^1.1.4",
+ "jest": "^30.2.0",
+ "obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master",
+ "rollup": "^4.59.0",
+ "rollup-plugin-copy": "^3.5.0",
+ "ts-jest": "^29.4.6",
+ "tslib": "^2.8.1",
+ "typescript": "^5.9.3"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
+ "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
+ "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@codemirror/state": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
+ "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@marijn/find-cluster-break": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/view": {
+ "version": "6.38.6",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz",
+ "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@codemirror/state": "^6.5.0",
+ "crelt": "^1.0.6",
+ "style-mod": "^4.1.0",
+ "w3c-keyname": "^2.2.4"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
+ "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.1.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
+ "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
+ "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@isaacs/cliui/node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jest/console": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz",
+ "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz",
+ "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/reporters": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-changed-files": "30.2.0",
+ "jest-config": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-resolve-dependencies": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/diff-sequences": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz",
+ "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/environment": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz",
+ "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "30.2.0",
+ "jest-snapshot": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz",
+ "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz",
+ "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@sinonjs/fake-timers": "^13.0.0",
+ "@types/node": "*",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/get-type": {
+ "version": "30.1.0",
+ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz",
+ "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz",
+ "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/types": "30.2.0",
+ "jest-mock": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/pattern": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
+ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-regex-util": "30.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz",
+ "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "collect-v8-coverage": "^1.0.2",
+ "exit-x": "^0.2.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^5.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.2",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@jest/reporters/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "30.0.5",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz",
+ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/snapshot-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz",
+ "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "natural-compare": "^1.4.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz",
+ "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "callsites": "^3.1.0",
+ "graceful-fs": "^4.2.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz",
+ "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "collect-v8-coverage": "^1.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz",
+ "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz",
+ "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/types": "30.2.0",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "babel-plugin-istanbul": "^7.0.1",
+ "chalk": "^4.1.2",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "micromatch": "^4.0.8",
+ "pirates": "^4.0.7",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^5.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz",
+ "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/pattern": "30.0.1",
+ "@jest/schemas": "30.0.5",
+ "@types/istanbul-lib-coverage": "^2.0.6",
+ "@types/istanbul-reports": "^3.0.4",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.33",
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
+ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@marijn/find-cluster-break": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
+ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@pkgr/core": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
+ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
+ "node_modules/@rollup/plugin-commonjs": {
+ "version": "29.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.0.tgz",
+ "integrity": "sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "commondir": "^1.0.1",
+ "estree-walker": "^2.0.2",
+ "fdir": "^6.2.0",
+ "is-reference": "1.2.1",
+ "magic-string": "^0.30.3",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=16.0.0 || 14 >= 14.17"
+ },
+ "peerDependencies": {
+ "rollup": "^2.68.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/plugin-node-resolve": {
+ "version": "16.0.3",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz",
+ "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "@types/resolve": "1.20.2",
+ "deepmerge": "^4.2.2",
+ "is-module": "^1.0.0",
+ "resolve": "^1.22.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.78.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/plugin-terser": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
+ "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "serialize-javascript": "^6.0.1",
+ "smob": "^1.0.0",
+ "terser": "^5.17.4"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/plugin-typescript": {
+ "version": "12.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.3.0.tgz",
+ "integrity": "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.1.0",
+ "resolve": "^1.22.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.14.0||^3.0.0||^4.0.0",
+ "tslib": "*",
+ "typescript": ">=3.7.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ },
+ "tslib": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
+ "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.34.48",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz",
+ "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "13.0.5",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz",
+ "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.1"
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/codemirror": {
+ "version": "5.60.8",
+ "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz",
+ "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/tern": "*"
+ }
+ },
+ "node_modules/@types/d3": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
+ "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/d3-axis": "*",
+ "@types/d3-brush": "*",
+ "@types/d3-chord": "*",
+ "@types/d3-color": "*",
+ "@types/d3-contour": "*",
+ "@types/d3-delaunay": "*",
+ "@types/d3-dispatch": "*",
+ "@types/d3-drag": "*",
+ "@types/d3-dsv": "*",
+ "@types/d3-ease": "*",
+ "@types/d3-fetch": "*",
+ "@types/d3-force": "*",
+ "@types/d3-format": "*",
+ "@types/d3-geo": "*",
+ "@types/d3-hierarchy": "*",
+ "@types/d3-interpolate": "*",
+ "@types/d3-path": "*",
+ "@types/d3-polygon": "*",
+ "@types/d3-quadtree": "*",
+ "@types/d3-random": "*",
+ "@types/d3-scale": "*",
+ "@types/d3-scale-chromatic": "*",
+ "@types/d3-selection": "*",
+ "@types/d3-shape": "*",
+ "@types/d3-time": "*",
+ "@types/d3-time-format": "*",
+ "@types/d3-timer": "*",
+ "@types/d3-transition": "*",
+ "@types/d3-zoom": "*"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-axis": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
+ "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-brush": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
+ "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-chord": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
+ "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-contour": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
+ "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-dispatch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
+ "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-drag": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-dsv": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
+ "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-fetch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
+ "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-dsv": "*"
+ }
+ },
+ "node_modules/@types/d3-force": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-format": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
+ "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-geo": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
+ "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-hierarchy": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
+ "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-polygon": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
+ "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-quadtree": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
+ "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-random": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
+ "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-selection": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-time-format": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
+ "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-transition": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+ "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-zoom": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/fs-extra": {
+ "version": "8.1.5",
+ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz",
+ "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/minimatch": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "30.0.0",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
+ "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "^30.0.0",
+ "pretty-format": "^30.0.0"
+ }
+ },
+ "node_modules/@types/minimatch": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
+ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "25.3.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
+ "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.18.0"
+ }
+ },
+ "node_modules/@types/resolve": {
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
+ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/sprintf-js": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@types/sprintf-js/-/sprintf-js-1.1.4.tgz",
+ "integrity": "sha512-aWK1reDYWxcjgcIIPmQi3u+OQDuYa9b+lr6eIsGWrekJ9vr1NSjr4Eab8oQ1iKuH1ltFHpXGyerAv1a3FMKxzQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/tern": {
+ "version": "0.23.9",
+ "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
+ "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.35",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
+ "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
+ "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
+ "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
+ "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
+ "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
+ "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
+ "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
+ "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
+ "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
+ "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
+ "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
+ "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
+ "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
+ "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
+ "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
+ "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
+ "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
+ "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
+ "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
+ "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/anymatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/argparse/node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz",
+ "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/transform": "30.2.0",
+ "@types/babel__core": "^7.20.5",
+ "babel-plugin-istanbul": "^7.0.1",
+ "babel-preset-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz",
+ "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "workspaces": [
+ "test/babel-8"
+ ],
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-instrument": "^6.0.2",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz",
+ "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/babel__core": "^7.20.5"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
+ "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz",
+ "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-jest-hoist": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.11.0 || ^8.0.0-beta.1"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
+ "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001770",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz",
+ "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
+ "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
+ "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz",
+ "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/colorette": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/crelt": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/d3": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
+ "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "3",
+ "d3-axis": "3",
+ "d3-brush": "3",
+ "d3-chord": "3",
+ "d3-color": "3",
+ "d3-contour": "4",
+ "d3-delaunay": "6",
+ "d3-dispatch": "3",
+ "d3-drag": "3",
+ "d3-dsv": "3",
+ "d3-ease": "3",
+ "d3-fetch": "3",
+ "d3-force": "3",
+ "d3-format": "3",
+ "d3-geo": "3",
+ "d3-hierarchy": "3",
+ "d3-interpolate": "3",
+ "d3-path": "3",
+ "d3-polygon": "3",
+ "d3-quadtree": "3",
+ "d3-random": "3",
+ "d3-scale": "4",
+ "d3-scale-chromatic": "3",
+ "d3-selection": "3",
+ "d3-shape": "3",
+ "d3-time": "3",
+ "d3-time-format": "4",
+ "d3-timer": "3",
+ "d3-transition": "3",
+ "d3-zoom": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-axis": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
+ "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-brush": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
+ "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "3",
+ "d3-transition": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-chord": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
+ "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-contour": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
+ "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+ "license": "ISC",
+ "dependencies": {
+ "delaunator": "5"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dispatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+ "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-drag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-selection": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dsv": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
+ "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+ "license": "ISC",
+ "dependencies": {
+ "commander": "7",
+ "iconv-lite": "0.6",
+ "rw": "1"
+ },
+ "bin": {
+ "csv2json": "bin/dsv2json.js",
+ "csv2tsv": "bin/dsv2dsv.js",
+ "dsv2dsv": "bin/dsv2dsv.js",
+ "dsv2json": "bin/dsv2json.js",
+ "json2csv": "bin/json2dsv.js",
+ "json2dsv": "bin/json2dsv.js",
+ "json2tsv": "bin/json2dsv.js",
+ "tsv2csv": "bin/dsv2dsv.js",
+ "tsv2json": "bin/dsv2json.js"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-fetch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
+ "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dsv": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-force": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
+ "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-quadtree": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-geo": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
+ "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.5.0 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-hierarchy": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+ "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-polygon": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
+ "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-quadtree": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
+ "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-random": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
+ "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-interpolate": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-selection": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
+ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-transition": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
+ "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-dispatch": "1 - 3",
+ "d3-ease": "1 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "d3-selection": "2 - 3"
+ }
+ },
+ "node_modules/d3-zoom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "2 - 3",
+ "d3-transition": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/dedent": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz",
+ "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/delaunator": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
+ "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
+ "license": "ISC",
+ "dependencies": {
+ "robust-predicates": "^3.0.2"
+ }
+ },
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.302",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz",
+ "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/exit-x": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz",
+ "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz",
+ "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/foreground-child/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/globby": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz",
+ "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/glob": "^7.1.1",
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.0.3",
+ "glob": "^7.1.3",
+ "ignore": "^5.1.1",
+ "merge2": "^1.2.3",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/handlebars": {
+ "version": "4.7.8",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
+ "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.2",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-object": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz",
+ "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-reference": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+ "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-instrument/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jest": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz",
+ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "30.2.0",
+ "@jest/types": "30.2.0",
+ "import-local": "^3.2.0",
+ "jest-cli": "30.2.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz",
+ "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "execa": "^5.1.1",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz",
+ "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/expect": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "co": "^4.6.0",
+ "dedent": "^1.6.0",
+ "is-generator-fn": "^2.1.0",
+ "jest-each": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "30.2.0",
+ "pure-rand": "^7.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz",
+ "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "exit-x": "^0.2.2",
+ "import-local": "^3.2.0",
+ "jest-config": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz",
+ "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/get-type": "30.1.0",
+ "@jest/pattern": "30.0.1",
+ "@jest/test-sequencer": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-jest": "30.2.0",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "deepmerge": "^4.3.1",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-circus": "30.2.0",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-runner": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "micromatch": "^4.0.8",
+ "parse-json": "^5.2.0",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "esbuild-register": ">=3.4.0",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "esbuild-register": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/jest-config/node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/jest-config/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/jest-diff": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz",
+ "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/diff-sequences": "30.0.1",
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-docblock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz",
+ "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-newline": "^3.1.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz",
+ "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "chalk": "^4.1.2",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz",
+ "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-mock": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz",
+ "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "anymatch": "^3.1.3",
+ "fb-watchman": "^2.0.2",
+ "graceful-fs": "^4.2.11",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.2.0",
+ "jest-worker": "30.2.0",
+ "micromatch": "^4.0.8",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.3"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz",
+ "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz",
+ "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "chalk": "^4.1.2",
+ "jest-diff": "30.2.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz",
+ "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@jest/types": "30.2.0",
+ "@types/stack-utils": "^2.0.3",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "micromatch": "^4.0.8",
+ "pretty-format": "30.2.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.6"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz",
+ "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "jest-util": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
+ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz",
+ "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-pnp-resolver": "^1.2.3",
+ "jest-util": "30.2.0",
+ "jest-validate": "30.2.0",
+ "slash": "^3.0.0",
+ "unrs-resolver": "^1.7.11"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz",
+ "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-regex-util": "30.0.1",
+ "jest-snapshot": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz",
+ "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "30.2.0",
+ "@jest/environment": "30.2.0",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "exit-x": "^0.2.2",
+ "graceful-fs": "^4.2.11",
+ "jest-docblock": "30.2.0",
+ "jest-environment-node": "30.2.0",
+ "jest-haste-map": "30.2.0",
+ "jest-leak-detector": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-resolve": "30.2.0",
+ "jest-runtime": "30.2.0",
+ "jest-util": "30.2.0",
+ "jest-watcher": "30.2.0",
+ "jest-worker": "30.2.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz",
+ "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.2.0",
+ "@jest/fake-timers": "30.2.0",
+ "@jest/globals": "30.2.0",
+ "@jest/source-map": "30.0.1",
+ "@jest/test-result": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "cjs-module-lexer": "^2.1.0",
+ "collect-v8-coverage": "^1.0.2",
+ "glob": "^10.3.10",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-mock": "30.2.0",
+ "jest-regex-util": "30.0.1",
+ "jest-resolve": "30.2.0",
+ "jest-snapshot": "30.2.0",
+ "jest-util": "30.2.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/jest-runtime/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz",
+ "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.27.1",
+ "@babel/types": "^7.27.3",
+ "@jest/expect-utils": "30.2.0",
+ "@jest/get-type": "30.1.0",
+ "@jest/snapshot-utils": "30.2.0",
+ "@jest/transform": "30.2.0",
+ "@jest/types": "30.2.0",
+ "babel-preset-current-node-syntax": "^1.2.0",
+ "chalk": "^4.1.2",
+ "expect": "30.2.0",
+ "graceful-fs": "^4.2.11",
+ "jest-diff": "30.2.0",
+ "jest-matcher-utils": "30.2.0",
+ "jest-message-util": "30.2.0",
+ "jest-util": "30.2.0",
+ "pretty-format": "30.2.0",
+ "semver": "^7.7.2",
+ "synckit": "^0.11.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz",
+ "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "chalk": "^4.1.2",
+ "ci-info": "^4.2.0",
+ "graceful-fs": "^4.2.11",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz",
+ "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/get-type": "30.1.0",
+ "@jest/types": "30.2.0",
+ "camelcase": "^6.3.0",
+ "chalk": "^4.1.2",
+ "leven": "^3.1.0",
+ "pretty-format": "30.2.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz",
+ "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "30.2.0",
+ "@jest/types": "30.2.0",
+ "@types/node": "*",
+ "ansi-escapes": "^4.3.2",
+ "chalk": "^4.1.2",
+ "emittery": "^0.13.1",
+ "jest-util": "30.2.0",
+ "string-length": "^4.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz",
+ "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@ungap/structured-clone": "^1.3.0",
+ "jest-util": "30.2.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsep": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz",
+ "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.16.0"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "license": "MIT",
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/moment": {
+ "version": "2.29.4",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/napi-postinstall": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
+ "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/obsidian": {
+ "version": "1.12.2",
+ "resolved": "https://github.com/obsidianmd/obsidian-api/tarball/master",
+ "integrity": "sha512-okcp2sqVzbOKGtTn+EBjuiq5fX3vfbxWujwC5CSKvUVYajZ7WAC2z9uKBv5Nle100nWyI6wRlA4jSbWjHLmlHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/codemirror": "5.60.8",
+ "moment": "2.29.4"
+ },
+ "peerDependencies": {
+ "@codemirror/state": "6.5.0",
+ "@codemirror/view": "6.38.6"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-locate/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "30.2.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz",
+ "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.5",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/pure-rand": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz",
+ "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/robust-predicates": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
+ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
+ "license": "Unlicense"
+ },
+ "node_modules/rollup": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
+ "@rollup/rollup-android-arm64": "4.59.0",
+ "@rollup/rollup-darwin-arm64": "4.59.0",
+ "@rollup/rollup-darwin-x64": "4.59.0",
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
+ "@rollup/rollup-freebsd-x64": "4.59.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
+ "@rollup/rollup-openbsd-x64": "4.59.0",
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rollup-plugin-copy": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.5.0.tgz",
+ "integrity": "sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/fs-extra": "^8.0.1",
+ "colorette": "^1.1.0",
+ "fs-extra": "^8.1.0",
+ "globby": "10.0.1",
+ "is-plain-object": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8.3"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/rw": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/serialize-javascript": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+ "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/smob": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.1.tgz",
+ "integrity": "sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/style-mod": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
+ "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/synckit": {
+ "version": "0.11.12",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
+ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.9"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
+ "node_modules/terser": {
+ "version": "5.46.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz",
+ "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.15.0",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/terser/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/terser/node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-jest": {
+ "version": "29.4.6",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz",
+ "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bs-logger": "^0.2.6",
+ "fast-json-stable-stringify": "^2.1.0",
+ "handlebars": "^4.7.8",
+ "json5": "^2.2.3",
+ "lodash.memoize": "^4.1.2",
+ "make-error": "^1.3.6",
+ "semver": "^7.7.3",
+ "type-fest": "^4.41.0",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.0.0-beta.0 <8",
+ "@jest/transform": "^29.0.0 || ^30.0.0",
+ "@jest/types": "^29.0.0 || ^30.0.0",
+ "babel-jest": "^29.0.0 || ^30.0.0",
+ "jest": "^29.0.0 || ^30.0.0",
+ "jest-util": "^29.0.0 || ^30.0.0",
+ "typescript": ">=4.3 <6"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "@jest/transform": {
+ "optional": true
+ },
+ "@jest/types": {
+ "optional": true
+ },
+ "babel-jest": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jest-util": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ts-jest/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ts-jest/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uglify-js": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/unrs-resolver": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
+ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.11.1",
+ "@unrs/resolver-binding-android-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-x64": "1.11.1",
+ "@unrs/resolver-binding-freebsd-x64": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-musl": "1.11.1",
+ "@unrs/resolver-binding-wasm32-wasi": "1.11.1",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/v8-to-istanbul": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/w3c-keyname": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/write-file-atomic": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/write-file-atomic/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index 0b7b9c5c..639c7ab0 100644
--- a/package.json
+++ b/package.json
@@ -1,37 +1,40 @@
{
"name": "obsidian-tracker",
- "version": "1.8.2",
+ "version": "1.19.0",
"description": "A plugin tracks occurrences and numbers in your notes",
"main": "main.js",
"scripts": {
- "dev": "rollup --config rollup.config.dev.js -w",
- "build": "rollup --config rollup.config.build.js",
- "zip": "gulp"
+ "dev": "rollup --config rollup.config.dev.mjs -w",
+ "build": "rollup --config rollup.config.build.mjs",
+ "zip": "zip -9 obsidian-tracker-v$npm_package_version.zip main.js manifest.json styles.css",
+ "test": "jest",
+ "test:watch": "jest --watch",
+ "test:coverage": "jest --coverage"
},
"keywords": [],
"author": "pyrochlore",
"license": "MIT",
"devDependencies": {
- "@rollup/plugin-commonjs": "^15.1.0",
- "@rollup/plugin-node-resolve": "^9.0.0",
- "@rollup/plugin-typescript": "^6.0.0",
- "@types/d3": "^6.3.0",
- "@types/moment": "^2.13.0",
- "@types/node": "^14.14.2",
- "@types/yaml": "^1.9.7",
- "gulp": "^4.0.2",
- "gulp-zip": "^5.1.0",
+ "@rollup/plugin-commonjs": "^29.0.0",
+ "@rollup/plugin-node-resolve": "^16.0.3",
+ "@rollup/plugin-terser": "^0.4.4",
+ "@rollup/plugin-typescript": "^12.3.0",
+ "@types/d3": "^7.4.3",
+ "@types/jest": "^30.0.0",
+ "@types/node": "^25.3.3",
+ "@types/sprintf-js": "^1.1.4",
+ "jest": "^30.2.0",
"obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master",
- "rollup": "^2.32.1",
- "rollup-plugin-copy": "^3.4.0",
- "rollup-plugin-terser": "^7.0.2",
- "tslib": "^2.0.3",
- "typescript": "^4.0.3"
+ "rollup": "^4.59.0",
+ "rollup-plugin-copy": "^3.5.0",
+ "ts-jest": "^29.4.6",
+ "tslib": "^2.8.1",
+ "typescript": "^5.9.3"
},
"dependencies": {
- "d3": "^6.5.0",
+ "d3": "^7.9.0",
"d3-interpolate": "^3.0.1",
- "expression-eval": "^5.0.0",
- "obsidian-daily-notes-interface": "^0.9.1"
+ "jsep": "^1.4.0",
+ "sprintf-js": "^1.1.3"
}
}
diff --git a/rollup.config.build.js b/rollup.config.build.js
deleted file mode 100644
index 9436f781..00000000
--- a/rollup.config.build.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import typescript from '@rollup/plugin-typescript';
-import { nodeResolve } from '@rollup/plugin-node-resolve';
-import commonjs from '@rollup/plugin-commonjs';
-import { terser } from "rollup-plugin-terser";
-
-export default {
- input: 'src/main.ts',
- output: {
- dir: '.',
- sourcemap: 'inline',
- format: 'cjs',
- exports: 'default'
- },
- external: ['obsidian'],
- plugins: [
- typescript(),
- nodeResolve({browser: true}),
- commonjs(),
- terser()
- ],
- onwarn: function(warning, warner){
- if (warning.code === 'CIRCULAR_DEPENDENCY'){
- if(warning.importer && warning.importer.startsWith('node_modules')){
- return;
- }
- }
- warner(warning);
- }
-};
\ No newline at end of file
diff --git a/rollup.config.build.mjs b/rollup.config.build.mjs
new file mode 100644
index 00000000..85eae084
--- /dev/null
+++ b/rollup.config.build.mjs
@@ -0,0 +1,38 @@
+import typescript from '@rollup/plugin-typescript';
+import { nodeResolve } from '@rollup/plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
+import terser from "@rollup/plugin-terser";
+
+export default {
+ input: 'src/main.ts',
+ output: {
+ dir: '.',
+ sourcemap: 'inline',
+ format: 'cjs',
+ exports: 'default'
+ },
+ external: ['obsidian'],
+ plugins: [
+ typescript({
+ // Added these lines to fix a rollup error: "Path of Typescript compiler option 'outDir' must be located inside Rollup 'dir' option."
+ tsconfig: 'tsconfig.json',
+ rootDir: 'src',
+ // outDir must be a subdirectory of Rollup's output.dir ('.') to satisfy @rollup/plugin-typescript's validation.
+ // 'dist' is a placeholder, since TypeScript doesn't emit files here. Rollup handles all output.
+ // (there might be more elegant ways to deal with this - just not quite sure how.)
+ outDir: 'dist'
+ }),
+ nodeResolve({browser: true}),
+ commonjs(),
+ terser()
+ ],
+ onwarn: function(warning, warner){
+ if (warning.code === 'CIRCULAR_DEPENDENCY') {
+ if (warning.importer && warning.importer.startsWith('node_modules')) {
+ console.warn(`(!) Circular dependency: ${warning.importer}`);
+ }
+ } else {
+ warner(warning);
+ }
+ }
+};
\ No newline at end of file
diff --git a/rollup.config.dev.js b/rollup.config.dev.mjs
similarity index 63%
rename from rollup.config.dev.js
rename to rollup.config.dev.mjs
index dd2742f5..50363b11 100644
--- a/rollup.config.dev.js
+++ b/rollup.config.dev.mjs
@@ -13,7 +13,11 @@ export default {
},
external: ['obsidian'],
plugins: [
- typescript(),
+ typescript({
+ tsconfig: 'tsconfig.json',
+ rootDir: 'src',
+ outDir: 'examples/.obsidian/plugins/obsidian-tracker'
+ }),
nodeResolve({browser: true}),
commonjs(),
copy({
@@ -23,11 +27,12 @@ export default {
})
],
onwarn: function(warning, warner){
- if (warning.code === 'CIRCULAR_DEPENDENCY'){
- if(warning.importer && warning.importer.startsWith('node_modules')){
- return;
- }
+ if (warning.code === 'CIRCULAR_DEPENDENCY') {
+ if (warning.importer && warning.importer.startsWith('node_modules')) {
+ console.warn(`(!) Circular dependency: ${warning.importer}`);
+ }
+ } else {
+ warner(warning);
}
- warner(warning);
}
-};
\ No newline at end of file
+};
diff --git a/src/bullet.ts b/src/bullet.ts
index 4bbc8a5f..0494dcdb 100644
--- a/src/bullet.ts
+++ b/src/bullet.ts
@@ -7,7 +7,7 @@ import {
Size,
Transform,
ChartElements,
- OutputType,
+ GraphType,
ValueType,
} from "./data";
import * as helper from "./helper";
@@ -450,9 +450,12 @@ function renderBar(
if (!renderInfo || !bulletInfo) return;
- let retActualValue = bulletInfo.value;
- retActualValue = expr.resolveTemplate(retActualValue, renderInfo);
- let actualValue = parseFloat(retActualValue);
+ let retActualValue = expr.resolveValue(bulletInfo.value, renderInfo);
+ // console.log(retActualValue);
+ if (typeof retActualValue === "string") {
+ return retActualValue;
+ }
+ let actualValue = retActualValue;
if (Number.isNaN(actualValue)) {
errorMessage = "Invalid input value: " + retActualValue;
return errorMessage;
diff --git a/src/collecting.ts b/src/collecting.ts
index 080f1b4f..430deaf1 100644
--- a/src/collecting.ts
+++ b/src/collecting.ts
@@ -10,41 +10,53 @@ import {
ValueType,
} from "./data";
import * as helper from "./helper";
+import { Moment } from "moment";
+
+// ref: https://www.rapidtables.com/code/text/unicode-characters.html
+const CurrencyCodes =
+ "\u0024\u20AC\u00A3\u00A5\u00A2\u20B9\u20A8\u20B1\u20A9\u0E3F\u20AB\u20AA";
+const AlphabetCodes = "\u03B1-\u03C9\u0391-\u03A9";
+const IntellectualPropertyCodes = "\u00A9\u00AE\u2117\u2122\u2120";
+const CJKCodes = "\u4E00-\u9FFF\u3400-\u4DBF\u3000\u3001-\u303F";
+const WordCharacters =
+ "\\w" +
+ CurrencyCodes +
+ AlphabetCodes +
+ IntellectualPropertyCodes +
+ CJKCodes;
+
+// fileBaseName is a string contains dateFormat only
+export function getDateFromFilename(
+ file: TFile,
+ renderInfo: RenderInfo
+): Moment {
+ // console.log(`getDateFromFilename: ${file.name}`);
+ // Get date form fileBaseName
-export function getDateFromFilename(file: TFile, renderInfo: RenderInfo) {
let fileBaseName = file.basename;
- if (
- renderInfo.dateFormatPrefix &&
- fileBaseName.startsWith(renderInfo.dateFormatPrefix)
- ) {
- fileBaseName = fileBaseName.slice(renderInfo.dateFormatPrefix.length);
- }
- if (
- renderInfo.dateFormatSuffix &&
- fileBaseName.endsWith(renderInfo.dateFormatSuffix)
- ) {
- fileBaseName = fileBaseName.slice(
- 0,
- fileBaseName.length - renderInfo.dateFormatSuffix.length
- );
- }
- // console.log(fileBaseName);
+ let dateString = helper.getDateStringFromInputString(
+ fileBaseName,
+ renderInfo.dateFormatPrefix,
+ renderInfo.dateFormatSuffix
+ );
+ // console.log(dateString);
- let fileDate = helper.strToDate(fileBaseName, renderInfo.dateFormat);
+ let fileDate = helper.strToDate(dateString, renderInfo.dateFormat);
// console.log(fileDate);
return fileDate;
}
// Not support multiple targets
-// May merge with collectDataFromFrontmatterKey
+// In form 'key: value', target used to identify 'frontmatter key'
export function getDateFromFrontmatter(
fileCache: CachedMetadata,
query: Query,
renderInfo: RenderInfo
-) {
+): Moment {
// console.log("getDateFromFrontmatter");
+ // Get date from 'frontMatterKey: date'
let date = window.moment("");
@@ -53,23 +65,71 @@ export function getDateFromFrontmatter(
if (helper.deepValue(frontMatter, query.getTarget())) {
let strDate = helper.deepValue(frontMatter, query.getTarget());
+ // We only support single value for now
+ if (typeof strDate === "string") {
+ strDate = helper.getDateStringFromInputString(
+ strDate,
+ renderInfo.dateFormatPrefix,
+ renderInfo.dateFormatSuffix
+ );
+
+ date = helper.strToDate(strDate, renderInfo.dateFormat);
+ // console.log(date);
+ }
+ }
+ }
+
+ return date;
+}
+
+// helper function
+// strRegex must have name group 'value'
+// Named group 'value' could be provided from users or plugin
+function extractDateUsingRegexWithValue(
+ text: string,
+ strRegex: string,
+ renderInfo: RenderInfo
+): Moment {
+ let date = window.moment("");
+
+ let regex = new RegExp(strRegex, "gm");
+ let match;
+ while ((match = regex.exec(text))) {
+ // console.log(match);
+ if (
+ typeof match.groups !== "undefined" &&
+ typeof match.groups.value !== "undefined"
+ ) {
+ // must have group name 'value'
+ let strDate = match.groups.value.trim();
+ // console.log(strDate);
+
+ strDate = helper.getDateStringFromInputString(
+ strDate,
+ renderInfo.dateFormatPrefix,
+ renderInfo.dateFormatSuffix
+ );
+
date = helper.strToDate(strDate, renderInfo.dateFormat);
- // console.log(date);
+ if (date.isValid()) {
+ return date;
+ }
}
}
return date;
}
-// Inline tags only
// Not support multiple targets
-// May merge with collectDataFromInlineTag
+// In form 'key: value', name group 'value' from plugin, not from users
export function getDateFromTag(
content: string,
query: Query,
renderInfo: RenderInfo
-) {
+): Moment {
// console.log("getDateFromTag");
+ // Get date from '#tagName: date'
+ // Inline value-attached tag only
let date = window.moment("");
@@ -78,72 +138,43 @@ export function getDateFromTag(
tagName = query.getParentTarget(); // use parent tag name for multiple values
}
// console.log(tagName);
- let strHashtagRegex =
+
+ let strRegex =
"(^|\\s)#" +
tagName +
- "(\\/[\\w-]+)*(:(?[\\d\\.\\/-]*)[a-zA-Z]*)?([\\.!,\\?;~-]*)?(\\s|$)";
- // console.log(strHashtagRegex);
- let hashTagRegex = new RegExp(strHashtagRegex, "gm");
- let match;
- while ((match = hashTagRegex.exec(content))) {
- // console.log(match);
- if (
- typeof match.groups !== "undefined" &&
- typeof match.groups.values !== "undefined"
- ) {
- let strDate = match.groups.values;
- date = helper.strToDate(strDate, renderInfo.dateFormat);
- if (date.isValid()) {
- break;
- }
- }
- }
- // console.log(date);
- return date;
+ "(\\/[\\w-]+)*(:(?[\\d\\.\\/-]*)[a-zA-Z]*)?([\\.!,\\?;~-]*)?(\\s|$)";
+ // console.log(strRegex);
+
+ return extractDateUsingRegexWithValue(content, strRegex, renderInfo);
}
// Not support multiple targets
-// May merge with colllectDataFromText
+// In form 'regex with value', name group 'value' from users
export function getDateFromText(
content: string,
query: Query,
renderInfo: RenderInfo
-) {
+): Moment {
// console.log("getDateFromText");
+ // Get date from text using regex with value
let date = window.moment("");
- let strTextRegex = query.getTarget();
+ let strRegex = query.getTarget();
// console.log(strTextRegex);
- let textRegex = new RegExp(strTextRegex, "gm");
- let match;
- while ((match = textRegex.exec(content))) {
- // console.log(match);
- if (
- typeof match.groups !== "undefined" &&
- typeof match.groups.value !== "undefined"
- ) {
- let strDate = match.groups.value.trim();
- // console.log(strDate);
- date = helper.strToDate(strDate, renderInfo.dateFormat);
- if (date.isValid()) {
- break;
- }
- }
- }
- // console.log(date);
- return date;
+ return extractDateUsingRegexWithValue(content, strRegex, renderInfo);
}
// Not support multiple targets
-// May merge with colllectDataFromDvField
+// In form 'key::value', named group 'value' from plugin
export function getDateFromDvField(
content: string,
query: Query,
renderInfo: RenderInfo
-) {
+): Moment {
// console.log("getDateFromDvField");
+ // Get date form 'targetName:: date'
let date = window.moment("");
@@ -156,39 +187,77 @@ export function getDateFromDvField(
dvTarget = dvTarget.replace("-", "[\\s\\-]");
// Test this in Regex101
- // (^|\s)\*{0,2}dvTarget\*{0,2}(::\s*(?[\d\.\/\-\w,@;\s]*))(\s|$)
- let strHashtagRegex =
- "(^|\\s)\\*{0,2}" +
+ // remember '\s' includes new line
+ // (^| |\t)\*{0,2}dvTarget\*{0,2}(::[ |\t]*(?[\d\.\/\-\w,@; \t:]*))(\r?\n|\r|$)
+ let strRegex =
+ "(^| |\\t)\\*{0,2}" +
dvTarget +
- "\\*{0,2}(::\\s*(?[\\d\\.\\/\\-\\w,@;\\s]*))(\r?\n|\r)";
- // console.log(strHashtagRegex);
- let hashTagRegex = new RegExp(strHashtagRegex, "gm");
- let match;
- while ((match = hashTagRegex.exec(content))) {
- // console.log(match);
- if (
- typeof match.groups !== "undefined" &&
- typeof match.groups.values !== "undefined"
- ) {
- let strDate = match.groups.values.trim();
- date = helper.strToDate(strDate, renderInfo.dateFormat);
- if (date.isValid()) {
- break;
+ "\\*{0,2}(::[ |\\t]*(?[\\d\\.\\/\\-\\w,@; \\t:]*))(\\r\\?\\n|\\r|$)";
+ // console.log(strRegex);
+
+ return extractDateUsingRegexWithValue(content, strRegex, renderInfo);
+}
+
+// Not support multiple targets
+// In form 'regex with value', name group 'value' from users
+export function getDateFromWiki(
+ fileCache: CachedMetadata,
+ query: Query,
+ renderInfo: RenderInfo
+): Moment {
+ //console.log("getDateFromWiki");
+ // Get date from '[[regex with value]]'
+
+ let date = window.moment("");
+
+ let links = fileCache.links;
+ if (!links) return date;
+
+ let searchTarget = query.getTarget();
+ let searchType = query.getType();
+
+ for (let link of links) {
+ if (!link) continue;
+
+ let wikiText = "";
+ if (searchType === SearchType.Wiki) {
+ if (link.displayText) {
+ wikiText = link.displayText;
+ } else {
+ wikiText = link.link;
+ }
+ } else if (searchType === SearchType.WikiLink) {
+ // wiki.link point to a file name
+ // a colon is not allowed be in file name
+ wikiText = link.link;
+ } else if (searchType === SearchType.WikiDisplay) {
+ if (link.displayText) {
+ wikiText = link.displayText;
+ }
+ } else {
+ if (link.displayText) {
+ wikiText = link.displayText;
+ } else {
+ wikiText = link.link;
}
}
+ wikiText = wikiText.trim();
+
+ let strRegex = "^" + searchTarget + "$";
+ return extractDateUsingRegexWithValue(wikiText, strRegex, renderInfo);
}
- // console.log(date);
+
return date;
}
// Not support multiple targets
-// May merge with colllectDataFromFileMeta
export function getDateFromFileMeta(
file: TFile,
query: Query,
renderInfo: RenderInfo
-) {
+): Moment {
// console.log("getDateFromFileMeta");
+ // Get date from cDate, mDate or baseFileName
let date = window.moment("");
@@ -197,12 +266,13 @@ export function getDateFromFileMeta(
let target = query.getTarget();
if (target === "cDate") {
- let ctime = file.stat.ctime;
+ let ctime = file.stat.ctime; // unix time
date = helper.getDateFromUnixTime(ctime, renderInfo.dateFormat);
} else if (target === "mDate") {
- let mtime = file.stat.mtime;
+ let mtime = file.stat.mtime; // unix time
date = helper.getDateFromUnixTime(mtime, renderInfo.dateFormat);
- } else if (target === "size") {
+ } else if (target === "name") {
+ date = getDateFromFilename(file, renderInfo);
}
}
@@ -211,49 +281,32 @@ export function getDateFromFileMeta(
}
// Not support multiple targets
-// May merge with colllectDataFromTask
+// In form 'regex with value', name group 'value' from users
export function getDateFromTask(
content: string,
query: Query,
renderInfo: RenderInfo
-) {
+): Moment {
// console.log("getDateFromTask");
+ // Get date from '- [ ] regex with value' or '- [x] regex with value'
let date = window.moment("");
let searchType = query.getType();
// console.log(searchType);
- let strTextRegex = query.getTarget();
+ let strRegex = query.getTarget();
if (searchType === SearchType.Task) {
- strTextRegex = "\\[[\\sx]\\]\\s" + strTextRegex;
+ strRegex = "\\[[\\sx]\\]\\s" + strRegex;
} else if (searchType === SearchType.TaskDone) {
- strTextRegex = "\\[x\\]\\s" + strTextRegex;
+ strRegex = "\\[x\\]\\s" + strRegex;
} else if (searchType === SearchType.TaskNotDone) {
- strTextRegex = "\\[\\s\\]\\s" + strTextRegex;
+ strRegex = "\\[\\s\\]\\s" + strRegex;
} else {
- strTextRegex = "\\[[\\sx]\\]\\s" + strTextRegex;
+ strRegex = "\\[[\\sx]\\]\\s" + strRegex;
}
// console.log(strTextRegex);
- let textRegex = new RegExp(strTextRegex, "gm");
- let match;
- while ((match = textRegex.exec(content))) {
- // console.log(match);
- if (
- typeof match.groups !== "undefined" &&
- typeof match.groups.value !== "undefined"
- ) {
- let strDate = match.groups.value.trim();
- // console.log(strDate);
-
- date = helper.strToDate(strDate, renderInfo.dateFormat);
- if (date.isValid()) {
- break;
- }
- }
- }
- // console.log(date);
- return date;
+ return extractDateUsingRegexWithValue(content, strRegex, renderInfo);
}
export function addToDataMap(
@@ -272,17 +325,124 @@ export function addToDataMap(
}
}
+// Helper function
+// Accept multiple values using custom separators
+// regex with value --> extract value
+// regex without value --> count occurrencies
+function extractDataUsingRegexWithMultipleValues(
+ text: string,
+ strRegex: string,
+ query: Query,
+ dataMap: DataMap,
+ xValueMap: XValueMap,
+ renderInfo: RenderInfo
+): boolean {
+ // console.log("extractDataUsingRegexWithMultipleValues");
+
+ let regex = new RegExp(strRegex, "gmu");
+ let match;
+ let measure = 0.0;
+ let extracted = false;
+ while ((match = regex.exec(text))) {
+ // console.log(match);
+ if (!renderInfo.ignoreAttachedValue[query.getId()]) {
+ if (
+ typeof match.groups !== "undefined" &&
+ typeof match.groups.value !== "undefined"
+ ) {
+ let values = match.groups.value.trim();
+ // console.log(values);
+ // console.log(query.getSeparator());
+ let splitted = values.split(query.getSeparator());
+ // console.log(splitted);
+ if (!splitted) continue;
+ if (splitted.length === 1) {
+ // console.log("single-value");
+ let toParse = splitted[0].trim();
+ // console.log(toParse);
+ let retParse = helper.parseFloatFromAny(
+ toParse,
+ renderInfo.textValueMap
+ );
+ if (retParse.value !== null) {
+ if (retParse.type === ValueType.Time) {
+ measure = retParse.value;
+ extracted = true;
+ query.valueType = ValueType.Time;
+ query.addNumTargets();
+ } else {
+ if (
+ !renderInfo.ignoreZeroValue[query.getId()] ||
+ retParse.value !== 0
+ ) {
+ measure += retParse.value;
+ extracted = true;
+ query.addNumTargets();
+ }
+ }
+ }
+ } else if (
+ splitted.length > query.getAccessor() &&
+ query.getAccessor() >= 0
+ ) {
+ // console.log("multiple-values");
+ let toParse = splitted[query.getAccessor()].trim();
+ let retParse = helper.parseFloatFromAny(
+ toParse,
+ renderInfo.textValueMap
+ );
+ //console.log(retParse);
+ if (retParse.value !== null) {
+ if (retParse.type === ValueType.Time) {
+ measure = retParse.value;
+ extracted = true;
+ query.valueType = ValueType.Time;
+ query.addNumTargets();
+ } else {
+ measure += retParse.value;
+ extracted = true;
+ query.addNumTargets();
+ }
+ }
+ }
+ } else {
+ // no named groups, count occurrencies
+ // console.log("count occurrencies");
+ measure += renderInfo.constValue[query.getId()];
+ extracted = true;
+ query.addNumTargets();
+ }
+ } else {
+ // force to count occurrencies
+ // console.log("forced count occurrencies");
+ measure += renderInfo.constValue[query.getId()];
+ extracted = true;
+ query.addNumTargets();
+ }
+ }
+
+ if (extracted) {
+ let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]);
+ addToDataMap(dataMap, xValue, query, measure);
+ return true;
+ }
+
+ return false;
+}
+
+// No value, count occurrences only
export function collectDataFromFrontmatterTag(
fileCache: CachedMetadata,
query: Query,
renderInfo: RenderInfo,
dataMap: DataMap,
xValueMap: XValueMap
-) {
+): boolean {
// console.log("collectDataFromFrontmatterTag");
// console.log(query);
// console.log(dataMap);
// console.log(xValueMap);
+
let frontMatter = fileCache.frontmatter;
let frontMatterTags: string[] = [];
if (frontMatter && frontMatter.tags) {
@@ -291,9 +451,17 @@ export function collectDataFromFrontmatterTag(
let tagExist = false;
if (Array.isArray(frontMatter.tags)) {
frontMatterTags = frontMatterTags.concat(frontMatter.tags);
- } else {
- frontMatterTags.push(frontMatter.tags);
+ } else if (typeof frontMatter.tags === "string") {
+ let splitted = frontMatter.tags.split(query.getSeparator(true));
+ for (let splittedPart of splitted) {
+ let part = splittedPart.trim();
+ if (part !== "") {
+ frontMatterTags.push(part);
+ }
+ }
}
+ // console.log(frontMatterTags);
+ // console.log(query.getTarget());
for (let tag of frontMatterTags) {
if (tag === query.getTarget()) {
@@ -311,7 +479,7 @@ export function collectDataFromFrontmatterTag(
}
// valued-tag in frontmatter is not supported
- // because the "tag:value" in frontmatter will be consider as a new tag for different values
+ // because the "tag:value" in frontmatter will be consider as a new tag for each existing value
let value = null;
if (tagExist) {
@@ -319,24 +487,127 @@ export function collectDataFromFrontmatterTag(
}
let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]);
addToDataMap(dataMap, xValue, query, value);
+ return true;
}
}
+
+ return false;
}
+// Track existence of frontmatter field (any non-empty value counts as 1.0)
+export function collectDataFromFrontmatterExists(
+ fileCache: CachedMetadata,
+ query: Query,
+ renderInfo: RenderInfo,
+ dataMap: DataMap,
+ xValueMap: XValueMap
+): boolean {
+ let frontMatter = fileCache.frontmatter;
+ if (frontMatter) {
+ let deepValue = helper.deepValue(frontMatter, query.getTarget());
+ // Check if field exists and is non-empty
+ if (deepValue !== null && deepValue !== undefined) {
+ // Handle different value types
+ let hasValue = false;
+ if (typeof deepValue === "string") {
+ hasValue = deepValue.trim() !== "";
+ } else if (Array.isArray(deepValue)) {
+ hasValue = deepValue.length > 0;
+ } else {
+ // Number, boolean, or other - deepValue converts these to strings
+ hasValue = true;
+ }
+
+ if (hasValue) {
+ // Field exists and has a value, use constValue (default 1.0)
+ query.addNumTargets();
+ let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]);
+ // Note: xValue can be undefined if xDataset wasn't set up, but addToDataMap should handle it
+ // Other functions don't check for undefined, so we'll follow the same pattern
+ addToDataMap(dataMap, xValue, query, renderInfo.constValue[query.getId()]);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// In form 'key[memberValue]', checks membership in a frontmatter list
+export function collectDataFromFrontmatterList(
+ fileCache: CachedMetadata,
+ query: Query,
+ renderInfo: RenderInfo,
+ dataMap: DataMap,
+ xValueMap: XValueMap
+): boolean {
+ let frontMatter = fileCache.frontmatter;
+ if (!frontMatter) return false;
+
+ // Parse searchTarget in the form "key[memberValue]"
+ let match = query.getTarget().match(/^(.+)\[(.+)\]$/);
+ if (!match) return false;
+
+ let listKey = match[1].trim();
+ let memberValue = match[2].trim().toLowerCase();
+
+ let rawValue = helper.deepValue(frontMatter, listKey);
+ if (rawValue == null) return false;
+
+ // Normalize to array of strings
+ let listValues: string[] = [];
+ if (Array.isArray(rawValue)) {
+ listValues = rawValue
+ .filter((v) => v != null)
+ .map((v) => String(v).trim())
+ .filter((v) => v !== "");
+ } else if (typeof rawValue === "string") {
+ listValues = rawValue
+ .split(query.getSeparator(true))
+ .map((s) => s.trim())
+ .filter((s) => s !== "");
+ } else {
+ return false;
+ }
+
+ if (listValues.some((v) => v.toLowerCase() === memberValue)) {
+ query.addNumTargets();
+ let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]);
+ addToDataMap(dataMap, xValue, query, renderInfo.constValue[query.getId()]);
+ return true;
+ }
+
+ return false;
+}
+
+// In form 'key: value', target used to identify 'frontmatter key'
export function collectDataFromFrontmatterKey(
fileCache: CachedMetadata,
query: Query,
renderInfo: RenderInfo,
dataMap: DataMap,
xValueMap: XValueMap
-) {
+): boolean {
// console.log("collectDataFromFrontmatterKey");
let frontMatter = fileCache.frontmatter;
if (frontMatter) {
- if (helper.deepValue(frontMatter, query.getTarget())) {
- let toParse = helper.deepValue(frontMatter, query.getTarget());
- let retParse = helper.parseFloatFromAny(toParse);
+ // console.log(frontMatter);
+ // console.log(query.getTarget());
+ let deepValue = helper.deepValue(frontMatter, query.getTarget());
+ // console.log(deepValue);
+ if (deepValue) {
+ let retParse = helper.parseFloatFromAny(
+ deepValue,
+ renderInfo.textValueMap
+ );
+ // console.log(retParse);
+ if (retParse.value === null) {
+ // Try parsing as a boolean: true means 1, false means 0.
+ if (deepValue === "true" || deepValue === "false") {
+ retParse.type = ValueType.Number;
+ retParse.value = deepValue === "true" ? 1 : 0;
+ }
+ }
if (retParse.value !== null) {
if (retParse.type === ValueType.Time) {
query.valueType = ValueType.Time;
@@ -344,6 +615,7 @@ export function collectDataFromFrontmatterKey(
query.addNumTargets();
let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]);
addToDataMap(dataMap, xValue, query, retParse.value);
+ return true;
}
} else if (
query.getParentTarget() &&
@@ -368,6 +640,7 @@ export function collectDataFromFrontmatterKey(
} else if (typeof toParse === "string") {
splitted = toParse.split(query.getSeparator());
}
+ // console.log(splitted);
if (
splitted &&
splitted.length > query.getAccessor() &&
@@ -375,7 +648,10 @@ export function collectDataFromFrontmatterKey(
) {
// TODO: it's not efficent to retrieve one value at a time, enhance this
let splittedPart = splitted[query.getAccessor()].trim();
- let retParse = helper.parseFloatFromAny(splittedPart);
+ let retParse = helper.parseFloatFromAny(
+ splittedPart,
+ renderInfo.textValueMap
+ );
if (retParse.value !== null) {
if (retParse.type === ValueType.Time) {
query.valueType = ValueType.Time;
@@ -385,49 +661,84 @@ export function collectDataFromFrontmatterKey(
renderInfo.xDataset[query.getId()]
);
addToDataMap(dataMap, xValue, query, retParse.value);
+ return true;
}
}
}
}
+
+ return false;
}
+// In form 'regex with value', name group 'value' from users
export function collectDataFromWiki(
fileCache: CachedMetadata,
query: Query,
renderInfo: RenderInfo,
dataMap: DataMap,
xValueMap: XValueMap
-) {
+): boolean {
let links = fileCache.links;
+ if (!links) return false;
+
+ let searchTarget = query.getTarget();
+ let searchType = query.getType();
+
+ let textToSearch = "";
+ let strRegex = searchTarget;
- let linkMeasure = 0.0;
- let linkExist = false;
+ // Prepare textToSearch
for (let link of links) {
- if (link.link === query.getTarget()) {
- linkExist = true;
- linkMeasure = linkMeasure + renderInfo.constValue[query.getId()];
- query.addNumTargets();
+ if (!link) continue;
+
+ let wikiText = "";
+ if (searchType === SearchType.Wiki) {
+ if (link.displayText) {
+ wikiText = link.displayText;
+ } else {
+ wikiText = link.link;
+ }
+ } else if (searchType === SearchType.WikiLink) {
+ // wiki.link point to a file name
+ // a colon is not allowed be in file name
+ wikiText = link.link;
+ } else if (searchType === SearchType.WikiDisplay) {
+ if (link.displayText) {
+ wikiText = link.displayText;
+ }
+ } else {
+ if (link.displayText) {
+ wikiText = link.displayText;
+ } else {
+ wikiText = link.link;
+ }
}
- }
+ wikiText = wikiText.trim();
- let linkValue = null;
- if (linkExist) {
- linkValue = linkMeasure;
+ textToSearch += wikiText + "\n";
}
- let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]);
- addToDataMap(dataMap, xValue, query, linkValue);
+
+ return extractDataUsingRegexWithMultipleValues(
+ textToSearch,
+ strRegex,
+ query,
+ dataMap,
+ xValueMap,
+ renderInfo
+ );
}
+// In form 'key: value', name group 'value' from plugin, not from users
export function collectDataFromInlineTag(
content: string,
query: Query,
renderInfo: RenderInfo,
dataMap: DataMap,
xValueMap: XValueMap
-) {
+): boolean {
// console.log(content);
// Test this in Regex101
- // (^|\s)#tagName(\/[\w-]+)*(:(?[\d\.\/-]*)[a-zA-Z]*)?([\\.!,\\?;~-]*)?(\s|$)
+ // (^|\s)#tagName(\/[\w-]+)*(:(?[\d\.\/-]*)[a-zA-Z]*)?([\\.!,\\?;~-]*)?(\s|$)
let tagName = query.getTarget();
if (query.getParentTarget()) {
tagName = query.getParentTarget(); // use parent tag name for multiple values
@@ -435,131 +746,43 @@ export function collectDataFromInlineTag(
if (tagName.length > 1 && tagName.startsWith("#")) {
tagName = tagName.substring(1);
}
- let strHashtagRegex =
+ let strRegex =
"(^|\\s)#" +
tagName +
- "(\\/[\\w-]+)*(:(?[\\d\\.\\/-]*)[a-zA-Z]*)?([\\.!,\\?;~-]*)?(\\s|$)";
- // console.log(strHashtagRegex);
- let hashTagRegex = new RegExp(strHashtagRegex, "gm");
- let match;
- let tagMeasure = 0.0;
- let tagExist = false;
- while ((match = hashTagRegex.exec(content))) {
- // console.log(match);
- if (
- !renderInfo.ignoreAttachedValue[query.getId()] &&
- typeof match.groups !== "undefined" &&
- typeof match.groups.values !== "undefined"
- ) {
- // console.log("value-attached tag");
- let values = match.groups.values;
- let splitted = values.split(query.getSeparator());
- if (!splitted) continue;
- if (splitted.length === 1) {
- // console.log("single-value");
- let toParse = splitted[0].trim();
- let retParse = helper.parseFloatFromAny(toParse);
- if (retParse.value !== null) {
- if (retParse.type === ValueType.Time) {
- tagMeasure = retParse.value;
- tagExist = true;
- query.valueType = ValueType.Time;
- query.addNumTargets();
- } else {
- if (
- !renderInfo.ignoreZeroValue[query.getId()] ||
- retParse.value !== 0
- ) {
- tagMeasure += retParse.value;
- tagExist = true;
- query.addNumTargets();
- }
- }
- }
- } else if (
- splitted.length > query.getAccessor() &&
- query.getAccessor() >= 0
- ) {
- let toParse = splitted[query.getAccessor()].trim();
- let retParse = helper.parseFloatFromAny(toParse);
- //console.log(retParse);
- if (retParse.value !== null) {
- if (retParse.type === ValueType.Time) {
- tagMeasure = retParse.value;
- tagExist = true;
- query.valueType = ValueType.Time;
- query.addNumTargets();
- } else {
- tagMeasure += retParse.value;
- tagExist = true;
- query.addNumTargets();
- }
- }
- }
- } else {
- // console.log("simple-tag");
- tagMeasure = tagMeasure + renderInfo.constValue[query.getId()];
- tagExist = true;
- query.addNumTargets();
- }
- }
-
- let value = null;
- if (tagExist) {
- value = tagMeasure;
- }
- let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]);
- addToDataMap(dataMap, xValue, query, value);
+ "(\\/[\\w-]+)*(:(?[\\d\\.\\/-]*)[a-zA-Z]*)?([\\.!,\\?;~-]*)?(\\s|$)";
+ // console.log(strRegex);
+
+ return extractDataUsingRegexWithMultipleValues(
+ content,
+ strRegex,
+ query,
+ dataMap,
+ xValueMap,
+ renderInfo
+ );
}
+// In form 'regex with value', name group 'value' from users
export function collectDataFromText(
content: string,
query: Query,
renderInfo: RenderInfo,
dataMap: DataMap,
xValueMap: XValueMap
-) {
- let strTextRegex = query.getTarget();
- // console.log(strTextRegex);
- let textRegex = new RegExp(strTextRegex, "gm");
- let match;
- let textMeasure = 0.0;
- let textExist = false;
- while ((match = textRegex.exec(content))) {
- // console.log(match);
- if (
- !renderInfo.ignoreAttachedValue[query.getId()] &&
- typeof match.groups !== "undefined"
- ) {
- // match[0] whole match
- // console.log("valued-text");
- if (typeof match.groups.value !== "undefined") {
- // set as null for missing value if it is valued-tag
- let value = parseFloat(match.groups.value);
- // console.log(value);
- if (!Number.isNaN(value)) {
- if (
- !renderInfo.ignoreZeroValue[query.getId()] ||
- value !== 0
- ) {
- textMeasure += value;
- textExist = true;
- query.addNumTargets();
- }
- }
- }
- } else {
- // console.log("simple-text");
- textMeasure = textMeasure + renderInfo.constValue[query.getId()];
- textExist = true;
- query.addNumTargets();
- }
- }
-
- if (textExist) {
- let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]);
- addToDataMap(dataMap, xValue, query, textMeasure);
- }
+): boolean {
+ // console.log("collectDataFromText");
+
+ let strRegex = query.getTarget();
+ // console.log(strRegex);
+
+ return extractDataUsingRegexWithMultipleValues(
+ content,
+ strRegex,
+ query,
+ dataMap,
+ xValueMap,
+ renderInfo
+ );
}
export function collectDataFromFileMeta(
@@ -569,7 +792,7 @@ export function collectDataFromFileMeta(
renderInfo: RenderInfo,
dataMap: DataMap,
xValueMap: XValueMap
-) {
+): boolean {
// console.log("collectDataFromFileMeta");
if (file && file instanceof TFile) {
@@ -579,41 +802,83 @@ export function collectDataFromFileMeta(
let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]);
if (target === "cDate") {
- let ctime = file.stat.ctime;
+ let ctime = file.stat.ctime; // unix time
query.valueType = ValueType.Date;
query.addNumTargets();
addToDataMap(dataMap, xValue, query, ctime);
+ return true;
} else if (target === "mDate") {
- let mtime = file.stat.mtime;
+ let mtime = file.stat.mtime; // unix time
query.valueType = ValueType.Date;
query.addNumTargets();
addToDataMap(dataMap, xValue, query, mtime);
+ return true;
} else if (target === "size") {
- let size = file.stat.size;
+ let size = file.stat.size; // number in
query.addNumTargets();
addToDataMap(dataMap, xValue, query, size);
+ return true;
} else if (target === "numWords") {
let numWords = helper.getWordCount(content);
addToDataMap(dataMap, xValue, query, numWords);
+ return true;
} else if (target === "numChars") {
let numChars = helper.getCharacterCount(content);
query.addNumTargets();
addToDataMap(dataMap, xValue, query, numChars);
+ return true;
} else if (target === "numSentences") {
let numSentences = helper.getSentenceCount(content);
query.addNumTargets();
addToDataMap(dataMap, xValue, query, numSentences);
+ return true;
+ } else if (target === "name") {
+ let targetMeasure = 0.0;
+ let targetExist = false;
+ let retParse = helper.parseFloatFromAny(
+ file.basename,
+ renderInfo.textValueMap
+ );
+ if (retParse.value !== null) {
+ if (retParse.type === ValueType.Time) {
+ targetMeasure = retParse.value;
+ targetExist = true;
+ query.valueType = ValueType.Time;
+ query.addNumTargets();
+ } else {
+ if (
+ !renderInfo.ignoreZeroValue[query.getId()] ||
+ retParse.value !== 0
+ ) {
+ targetMeasure += retParse.value;
+ targetExist = true;
+ query.addNumTargets();
+ }
+ }
+ }
+
+ let value = null;
+ if (targetExist) {
+ value = targetMeasure;
+ }
+ if (value !== null) {
+ addToDataMap(dataMap, xValue, query, value);
+ return true;
+ }
}
}
+
+ return false;
}
+// In form 'key::value', named group 'value' from plugin
export function collectDataFromDvField(
content: string,
query: Query,
renderInfo: RenderInfo,
dataMap: DataMap,
xValueMap: XValueMap
-) {
+): boolean {
let dvTarget = query.getTarget();
if (query.getParentTarget()) {
dvTarget = query.getParentTarget(); // use parent tag name for multiple values
@@ -623,146 +888,57 @@ export function collectDataFromDvField(
dvTarget = dvTarget.replace("-", "[\\s\\-]");
// Test this in Regex101
- // (^|\s)\*{0,2}dvTarget\*{0,2}(::\s*(?[\d\.\/\-\w,@;\s]*))(\s|$)
- let strHashtagRegex =
- "(^|\\s)\\*{0,2}" +
+ // remember '\s' includes new line
+ // (^| |\t|\|)(\[|\()?\*{0,2}dvTarget\*{0,2}(::[ |\t]*(?[\d\.\/\-\w,@; \t:]*)(\]|\))?)
+ let strRegex =
+ String.raw`(^| |\t|\|)(\[|\()?\*{0,2}` +
dvTarget +
- "\\*{0,2}(::\\s*(?[\\d\\.\\/\\-\\w,@;\\s]*))(\r?\n|\r|$)";
- // console.log(strHashtagRegex);
- let hashTagRegex = new RegExp(strHashtagRegex, "gm");
- let match;
- let tagMeasure = 0.0;
- let tagExist = false;
- while ((match = hashTagRegex.exec(content))) {
- // console.log(match);
- if (
- typeof match.groups !== "undefined" &&
- typeof match.groups.values !== "undefined"
- ) {
- let values = match.groups.values.trim();
- // console.log(values);
- // console.log(query.getSeparator());
- let splitted = values.split(query.getSeparator());
- // console.log(splitted);
- if (!splitted) continue;
- if (splitted.length === 1) {
- // console.log("single-value");
- let toParse = splitted[0];
- let retParse = helper.parseFloatFromAny(toParse);
- if (retParse.value !== null) {
- if (retParse.type === ValueType.Time) {
- tagMeasure = retParse.value;
- tagExist = true;
- query.valueType = ValueType.Time;
- query.addNumTargets();
- } else {
- if (
- !renderInfo.ignoreZeroValue[query.getId()] ||
- retParse.value !== 0
- ) {
- tagMeasure += retParse.value;
- tagExist = true;
- query.addNumTargets();
- }
- }
- }
- } else if (
- splitted.length > query.getAccessor() &&
- query.getAccessor() >= 0
- ) {
- // TODO: it's not efficent to retrieve one value at a time, enhance this
- // console.log("multiple-values");
- let toParse = splitted[query.getAccessor()].trim();
- let retParse = helper.parseFloatFromAny(toParse);
- if (retParse.value !== null) {
- if (retParse.type === ValueType.Time) {
- tagMeasure = retParse.value;
- tagExist = true;
- query.valueType = ValueType.Time;
- query.addNumTargets();
- } else {
- tagMeasure += retParse.value;
- tagExist = true;
- query.addNumTargets();
- }
- }
- }
- } else {
- // console.log("simple-tag");
- tagMeasure = tagMeasure + renderInfo.constValue[query.getId()];
- tagExist = true;
- query.addNumTargets();
- }
- }
-
- let value = null;
- if (tagExist) {
- value = tagMeasure;
- }
- let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]);
- addToDataMap(dataMap, xValue, query, value);
+ String.raw`\*{0,2}(::[ |\t]*(?[\p{ExtPict}\d\.\/\-\w,@; \t:` +
+ WordCharacters +
+ String.raw`]*)(\]|\))?)`;
+ // console.log(strRegex);
+
+ return extractDataUsingRegexWithMultipleValues(
+ content,
+ strRegex,
+ query,
+ dataMap,
+ xValueMap,
+ renderInfo
+ );
}
+// In form 'regex with value', name group 'value' from users
export function collectDataFromTask(
content: string,
query: Query,
renderInfo: RenderInfo,
dataMap: DataMap,
xValueMap: XValueMap
-) {
+): boolean {
+ // console.log("collectDataFromTask");
let searchType = query.getType();
// console.log(searchType);
- let strTextRegex = query.getTarget();
+ let strRegex = query.getTarget();
if (searchType === SearchType.Task) {
- strTextRegex = "\\[[\\sx]\\]\\s" + strTextRegex;
+ strRegex = "\\[[\\sx]\\]\\s" + strRegex;
} else if (searchType === SearchType.TaskDone) {
- strTextRegex = "\\[x\\]\\s" + strTextRegex;
+ strRegex = "\\[x\\]\\s" + strRegex;
} else if (searchType === SearchType.TaskNotDone) {
- strTextRegex = "\\[\\s\\]\\s" + strTextRegex;
+ strRegex = "\\[\\s\\]\\s" + strRegex;
} else {
// all
- strTextRegex = "\\[[\\sx]\\]\\s" + strTextRegex;
- }
- // console.log(strTextRegex);
-
- let textRegex = new RegExp(strTextRegex, "gm");
- let match;
- let textMeasure = 0.0;
- let textExist = false;
- while ((match = textRegex.exec(content))) {
- // console.log(match);
- if (
- !renderInfo.ignoreAttachedValue[query.getId()] &&
- typeof match.groups !== "undefined"
- ) {
- // match[0] whole match
- // console.log("valued-text");
- if (typeof match.groups.value !== "undefined") {
- // set as null for missing value if it is valued-tag
- let value = parseFloat(match.groups.value);
- // console.log(value);
- if (!Number.isNaN(value)) {
- if (
- !renderInfo.ignoreZeroValue[query.getId()] ||
- value !== 0
- ) {
- textMeasure += value;
- textExist = true;
- query.addNumTargets();
- }
- }
- }
- } else {
- // console.log("simple-text");
- textMeasure = textMeasure + renderInfo.constValue[query.getId()];
- textExist = true;
- query.addNumTargets();
- }
- }
-
- if (textExist) {
- let xValue = xValueMap.get(renderInfo.xDataset[query.getId()]);
- addToDataMap(dataMap, xValue, query, textMeasure);
+ strRegex = "\\[[\\sx]\\]\\s" + strRegex;
}
+ // console.log(strRegex);
+
+ return extractDataUsingRegexWithMultipleValues(
+ content,
+ strRegex,
+ query,
+ dataMap,
+ xValueMap,
+ renderInfo
+ );
}
diff --git a/src/data.ts b/src/data.ts
index 11f865e4..ad39c85b 100644
--- a/src/data.ts
+++ b/src/data.ts
@@ -3,7 +3,11 @@ import { Moment } from "moment";
export enum SearchType {
Tag,
Frontmatter,
+ FrontmatterExists,
+ FrontmatterList, // new :)
Wiki,
+ WikiLink,
+ WikiDisplay,
Text,
dvField,
Table,
@@ -13,7 +17,7 @@ export enum SearchType {
TaskNotDone,
}
-export enum OutputType {
+export enum GraphType {
Line,
Bar,
Pie,
@@ -35,6 +39,15 @@ export enum ValueType {
String,
}
+export enum ThresholdType {
+ GreaterThan = "greaterthan",
+ LessThan = "lessthan"
+}
+
+export type TextValueMap = {
+ [key: string]: number;
+};
+
export class DataPoint {
date: Moment;
value: number;
@@ -62,7 +75,7 @@ export class Query {
constructor(id: number, searchType: SearchType, searchTarget: string) {
this.type = searchType;
this.target = searchTarget;
- this.separator = "/";
+ this.separator = ""; // separator to separate multiple values
this.id = id;
this.accessor = -1;
this.accessor1 = -1;
@@ -166,7 +179,13 @@ export class Query {
this.separator = sep;
}
- public getSeparator() {
+ public getSeparator(isForFrontmatterTags: boolean = false) {
+ if (this.separator === "") {
+ if (isForFrontmatterTags) {
+ return ",";
+ }
+ return "/";
+ }
return this.separator;
}
@@ -199,6 +218,8 @@ export class Dataset implements IterableIterator {
private lineInfo: LineInfo;
private barInfo: BarInfo;
+ private isTmpDataset: boolean;
+
valueType: ValueType;
private currentIndex = 0; // IterableIterator
@@ -216,13 +237,33 @@ export class Dataset implements IterableIterator {
this.numTargets = 0;
this.lineInfo = null;
this.barInfo = null;
- this.valueType = query.valueType;
+
+ this.isTmpDataset = false;
+
+ this.valueType = query?.valueType;
for (let ind = 0; ind < parent.getDates().length; ind++) {
this.values.push(null);
}
}
+ public cloneToTmpDataset() {
+ if (!this.isTmpDataset) {
+ let tmpDataset = new Dataset(this.parent, null);
+ tmpDataset.name = "tmp";
+ tmpDataset.values = [...this.values];
+ tmpDataset.yMin = this.yMin;
+ tmpDataset.yMax = this.yMax;
+ tmpDataset.startDate = this.startDate.clone();
+ tmpDataset.endDate = this.endDate.clone();
+ tmpDataset.numTargets = this.numTargets;
+ tmpDataset.isTmpDataset = true;
+ tmpDataset.valueType = this.valueType;
+ return tmpDataset;
+ }
+ return this; // already tmp dataset
+ }
+
public getName() {
return this.name;
}
@@ -258,15 +299,20 @@ export class Dataset implements IterableIterator {
public setValue(date: Moment, value: number) {
let ind = this.parent.getIndexOfDate(date);
// console.log(ind);
- if (ind >= 0) {
+
+ if (ind >= 0 && ind < this.values.length) {
+ // Set value
this.values[ind] = value;
+ // Update yMin and yMax
if (this.yMin === null || value < this.yMin) {
this.yMin = value;
}
if (this.yMax === null || value > this.yMax) {
this.yMax = value;
}
+
+ // Update startDate and endDate
if (this.startDate === null || date < this.startDate) {
this.startDate = date.clone();
}
@@ -276,6 +322,11 @@ export class Dataset implements IterableIterator {
}
}
+ public recalculateMinMax() {
+ this.yMin = Math.min(...this.values);
+ this.yMax = Math.max(...this.values);
+ }
+
public getYMin() {
return this.yMin;
}
@@ -292,14 +343,25 @@ export class Dataset implements IterableIterator {
return this.endDate;
}
- public shift(shiftAmount: number) {
+ public shift(shiftAmount: number, doLargerthan: number) {
+ let anyShifted = false;
for (let ind = 0; ind < this.values.length; ind++) {
if (this.values[ind] !== null) {
- this.values[ind] = this.values[ind] + shiftAmount;
+ if (doLargerthan === null) {
+ this.values[ind] = this.values[ind] + shiftAmount;
+ anyShifted = true;
+ } else {
+ if (this.values[ind] >= doLargerthan) {
+ this.values[ind] = this.values[ind] + shiftAmount;
+ anyShifted = true;
+ }
+ }
}
}
- this.yMin = this.yMin + shiftAmount;
- this.yMax = this.yMax + shiftAmount;
+ if (anyShifted) {
+ this.yMin = this.yMin + shiftAmount;
+ this.yMax = this.yMax + shiftAmount;
+ }
}
public setPenalty(penalty: number) {
@@ -336,6 +398,25 @@ export class Dataset implements IterableIterator {
}
}
+ public shiftByDataset(shiftDataset: Dataset) {
+ // Assume all datasets are of the same length
+ for (let ind = 0; ind < this.values.length; ind++) {
+ let currentValue = this.values[ind];
+ if (shiftDataset.values[ind] !== null && currentValue !== null) {
+ currentValue += shiftDataset.values[ind];
+ } else if (shiftDataset.values[ind] !== null) {
+ currentValue = shiftDataset.values[ind];
+ }
+ this.values[ind] = currentValue;
+ if (currentValue < this.yMin) {
+ this.yMin = currentValue;
+ }
+ if (currentValue > this.yMax) {
+ this.yMax = currentValue;
+ }
+ }
+ }
+
public getValues() {
return this.values;
}
@@ -502,6 +583,10 @@ export class RenderInfo {
queries: Query[];
xDataset: number[];
folder: string;
+ file: string[];
+ specifiedFilesOnly: boolean;
+ fileContainsLinkedFiles: string[];
+ fileMultiplierAfterLink: string;
dateFormat: string;
dateFormatPrefix: string;
dateFormatSuffix: string;
@@ -512,16 +597,19 @@ export class RenderInfo {
ignoreAttachedValue: boolean[];
ignoreZeroValue: boolean[];
accum: boolean[];
+ stack: boolean;
penalty: number[];
valueShift: number[];
+ shiftOnlyValueLargerThan: number[];
valueType: string[]; // number/float, int, string, boolean, date, time, datetime
+ textValueMap: TextValueMap;
dataAreaSize: Size;
margin: Margin;
- tooltipSize: Size;
fixedScale: number;
fitPanelWidth: boolean;
+ aspectRatio: AspectRatio;
output: any[];
line: LineInfo[];
@@ -531,6 +619,7 @@ export class RenderInfo {
month: MonthInfo[];
heatmap: HeatmapInfo[];
bullet: BulletInfo[];
+ customDataset: CustomDatasetInfo[];
public datasets: Datasets | null;
@@ -538,6 +627,10 @@ export class RenderInfo {
this.queries = queries;
this.xDataset = []; // use file name
this.folder = "/";
+ this.file = []; // extra files to use
+ this.specifiedFilesOnly = false; // if true, use files specified only
+ this.fileContainsLinkedFiles = [];
+ this.fileMultiplierAfterLink = ""; // regex pattern to extract multiplier after link
this.dateFormat = "YYYY-MM-DD";
this.dateFormatPrefix = "";
this.dateFormatSuffix = "";
@@ -548,13 +641,16 @@ export class RenderInfo {
this.ignoreAttachedValue = []; // false
this.ignoreZeroValue = []; // false
this.accum = []; // false, accum values start from zero over days
+ this.stack = false;
this.penalty = []; // null, use this value instead of null value
this.valueShift = [];
+ this.shiftOnlyValueLargerThan = [];
this.valueType = [];
+ this.textValueMap = {};
this.dataAreaSize = new Size(300, 300);
+ this.aspectRatio = new AspectRatio(1, 1);
this.margin = new Margin(10, 10, 10, 10); // top, right, bottom, left
- this.tooltipSize = new Size(90, 45);
this.fixedScale = 1.0;
this.fitPanelWidth = false;
@@ -567,6 +663,7 @@ export class RenderInfo {
this.month = [];
this.heatmap = [];
this.bullet = [];
+ this.customDataset = [];
this.datasets = null;
}
@@ -580,11 +677,33 @@ export class RenderInfo {
}
}
-export class OutputInfo {
- constructor() {}
+export class CustomDatasetInfo {
+ id: number;
+ name: string;
+ xData: string[];
+ yData: string[];
+
+ constructor() {
+ this.id = -1;
+ this.name = "";
+ this.xData = [];
+ this.yData = [];
+ }
+}
+
+export interface IGraph {
+ GetGraphType(): GraphType;
+}
+
+export interface ILegend {
+ showLegend: boolean;
+ legendPosition: string;
+ legendOrientation: string;
+ legendBgColor: string;
+ legendBorderColor: string;
}
-export class CommonChartInfo extends OutputInfo {
+export class CommonChartInfo implements IGraph, ILegend {
title: string;
xAxisLabel: string;
xAxisColor: string;
@@ -593,19 +712,23 @@ export class CommonChartInfo extends OutputInfo {
yAxisColor: string[];
yAxisLabelColor: string[];
yAxisUnit: string[];
+ xAxisTickInterval: string;
+ yAxisTickInterval: string[];
+ xAxisTickLabelFormat: string;
+ yAxisTickLabelFormat: string[];
yMin: number[];
yMax: number[];
reverseYAxis: boolean[];
allowInspectData: boolean;
+
+ // ILegend
showLegend: boolean;
legendPosition: string;
legendOrientation: string;
legendBgColor: string;
legendBorderColor: string;
- chartType: OutputType;
constructor() {
- super();
this.title = "";
this.xAxisLabel = "Date";
this.xAxisColor = "";
@@ -614,20 +737,25 @@ export class CommonChartInfo extends OutputInfo {
this.yAxisColor = []; // "", 2 elements
this.yAxisLabelColor = []; // "", 2 elements
this.yAxisUnit = []; // "", 2 elements
+ this.xAxisTickInterval = null; // the string will be converted to Duration (a month is not nesscesary to 30 days)
+ this.yAxisTickInterval = []; // null, 2 elements
+ this.xAxisTickLabelFormat = null;
+ this.yAxisTickLabelFormat = []; // null, 2 elements
this.yMin = []; // null, 2 elements
this.yMax = []; // null, 2 elements
this.reverseYAxis = []; // false, 2 elements
this.allowInspectData = true;
+
+ // ILegend
this.showLegend = false;
this.legendPosition = ""; // top, bottom, left, right
this.legendOrientation = ""; // horizontal, vertical
this.legendBgColor = "";
this.legendBorderColor = "";
- this.chartType = OutputType.Unknown;
}
- public GetChartType() {
- return this.chartType;
+ public GetGraphType() {
+ return GraphType.Unknown;
}
}
@@ -657,108 +785,168 @@ export class LineInfo extends CommonChartInfo {
this.yAxisLocation = []; // left, for each target
}
- public GetChartType() {
- return OutputType.Line;
+ public GetGraphType() {
+ return GraphType.Line;
}
}
export class BarInfo extends CommonChartInfo {
barColor: string[];
yAxisLocation: string[];
+ xAxisPadding: string;
constructor() {
super();
this.barColor = []; // #69b3a2
this.yAxisLocation = []; // left, for each target
+ this.xAxisPadding = null; // the string will be converted to Duration (a month is not nesscesary to 30 days)
}
- public GetChartType() {
- return OutputType.Bar;
+ public GetGraphType() {
+ return GraphType.Bar;
}
}
-export class PieInfo extends OutputInfo {
+export class PieInfo implements IGraph, ILegend {
title: string;
data: string[];
dataColor: string[];
+ dataName: string[];
+ label: string[];
+ hideLabelLessThan: number;
+ showExtLabelOnlyIfNoLabel: boolean;
+ extLabel: string[];
+
ratioInnerRadius: number;
+ // ILegend
+ showLegend: boolean;
+ legendPosition: string;
+ legendOrientation: string;
+ legendBgColor: string;
+ legendBorderColor: string;
+
constructor() {
- super();
this.title = "";
this.data = [];
this.dataColor = [];
+ this.dataName = [];
+ this.label = [];
+ this.hideLabelLessThan = 0.03;
+ this.extLabel = [];
+ this.showExtLabelOnlyIfNoLabel = false;
this.ratioInnerRadius = 0.0;
+
+ // ILegend
+ this.showLegend = false;
+ this.legendPosition = ""; // top, bottom, left, right
+ this.legendOrientation = ""; // horizontal, vertical
+ this.legendBgColor = "";
+ this.legendBorderColor = "";
+ }
+
+ public GetGraphType() {
+ return GraphType.Pie;
}
}
-export class SummaryInfo extends OutputInfo {
+export class SummaryInfo implements IGraph {
template: string;
style: string;
constructor() {
- super();
this.template = "";
this.style = "";
}
+
+ public GetGraphType() {
+ return GraphType.Summary;
+ }
}
-export class MonthInfo extends OutputInfo {
+export class MonthInfo implements IGraph {
mode: string;
dataset: number[];
startWeekOn: string;
threshold: number[];
+ thresholdType: string[];
yMin: number[];
yMax: number[];
- showCircle: boolean;
color: string;
dimNotInMonth: boolean;
+ initMonth: string; // YYYY-MM
+ showSelectedValue: boolean;
+
+ // header
+ headerYearColor: string;
+ headerMonthColor: string;
+ dividingLineColor: string;
+
+ // circles and rings
+ showCircle: boolean;
showStreak: boolean;
showTodayRing: boolean;
- showSelectedValue: boolean;
showSelectedRing: boolean;
circleColor: string;
circleColorByValue: boolean;
- headerYearColor: string;
- headerMonthColor: string;
- dividingLineColor: string;
+ circleColorByStreak: boolean;
todayRingColor: string;
selectedRingColor: string;
- initMonth: string; // YYYY-MM
+ // annotations
+ showAnnotation: boolean;
+ annotation: string[];
+ showAnnotationOfAllTargets: boolean;
+
+ // internal
selectedDate: string;
selectedDataset: number;
constructor() {
- super();
- this.mode = "circle"; // circle, symbol
+ this.mode = "circle"; // circle, annotation
this.dataset = [];
this.startWeekOn = "Sun";
this.threshold = []; // if value > threshold, will show dot
+ this.thresholdType = [];
this.yMin = [];
this.yMax = [];
- this.showCircle = true;
this.color = null;
this.dimNotInMonth = true;
+ this.initMonth = "";
+ this.showSelectedValue = true;
+
+ // header
+ this.headerYearColor = null;
+ this.headerMonthColor = null;
+ this.dividingLineColor = null;
+
+ // circles and rings
+ this.showCircle = true;
this.showStreak = true; // a streak connects neigbor dots
this.showTodayRing = true;
- this.showSelectedValue = true;
this.showSelectedRing = true;
this.circleColor = null;
this.circleColorByValue = false;
- this.headerYearColor = null;
- this.headerMonthColor = null;
- this.dividingLineColor = null;
+ this.circleColorByStreak = false;
this.todayRingColor = ""; // white
this.selectedRingColor = "firebrick";
- this.initMonth = "";
+ // annotations
+ this.showAnnotation = true;
+ this.annotation = []; // annotation for each dataset, accept expression thus value
+ this.showAnnotationOfAllTargets = true;
+
+ // internal
this.selectedDate = ""; // selected date
this.selectedDataset = null; // selected index of dataset
}
+
+ public GetGraphType() {
+ return GraphType.Month;
+ }
}
-export class HeatmapInfo {
+export class HeatmapInfo implements IGraph {
dataset: string;
startWeekOn: string;
orientation: string;
@@ -774,9 +962,13 @@ export class HeatmapInfo {
this.yMax = null;
this.color = null;
}
+
+ public GetGraphType() {
+ return GraphType.Heatmap;
+ }
}
-export class BulletInfo extends OutputInfo {
+export class BulletInfo implements IGraph {
title: string;
dataset: string;
orientation: string;
@@ -790,7 +982,6 @@ export class BulletInfo extends OutputInfo {
markerColor: string;
constructor() {
- super();
this.title = "";
this.dataset = "0"; // dataset id or name
this.orientation = "horizontal"; // or vertical
@@ -803,6 +994,10 @@ export class BulletInfo extends OutputInfo {
this.markerValue = 0;
this.markerColor = "";
}
+
+ public GetGraphType() {
+ return GraphType.Bullet;
+ }
}
export class Size {
@@ -815,6 +1010,22 @@ export class Size {
}
}
+export class AspectRatio {
+ x: number;
+ y: number;
+
+ constructor(x: number, y: number) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public recalculateSize(size: Size): Size {
+ let aspectRatio = this.x / this.y;
+ let width = parseFloat((size.width * aspectRatio).toFixed(2))
+ return new Size(width, size.height);
+ }
+}
+
export class Margin {
top: number;
right: number;
@@ -867,5 +1078,29 @@ export class TableData {
}
}
+export class CollectingProcessInfo {
+ fileTotal: number; // total number of files
+ fileAvailable: number; // total available count
+ fileOutOfDateRange: number;
+ fileNotInFormat: number;
+ errorMessage: string;
+ minDate: Moment;
+ maxDate: Moment;
+ gotAnyValidXValue: boolean;
+ gotAnyValidYValue: boolean;
+
+ constructor() {
+ this.fileTotal = 0;
+ this.fileAvailable = 0;
+ this.fileOutOfDateRange = 0;
+ this.fileNotInFormat = 0;
+ this.errorMessage = "";
+ this.minDate = window.moment(""); // invalid date
+ this.maxDate = window.moment(""); // invalid date
+ this.gotAnyValidXValue = false;
+ this.gotAnyValidYValue = false;
+ }
+}
+
export type XValueMap = Map;
export type DataMap = Map>;
diff --git a/src/expr.ts b/src/expr.ts
index dbb9d86b..51c5fc84 100644
--- a/src/expr.ts
+++ b/src/expr.ts
@@ -1,108 +1,183 @@
-import { RenderInfo } from "./data";
+import { RenderInfo, Dataset } from "./data";
import * as d3 from "d3";
-import { Moment } from "moment";
+import { isMoment, Moment } from "moment";
import * as helper from "./helper";
-import { parse, eval as evaluate } from "expression-eval";
+import jsep from "jsep";
+import { sprintf } from "sprintf-js";
-let fnSet = {
+// Function accept datasetId as first argument
+type FnDatasetToValue = (
+ dataset: Dataset,
+ renderInfo: RenderInfo
+) => number | Moment | string;
+type FnDatasetToDataset = (
+ dataset: Dataset,
+ args: Array,
+ renderInfo: RenderInfo
+) => Dataset | string;
+type FnUniryOp = (
+ u: number | Moment | Dataset
+) => number | Moment | Dataset | string;
+type FnBinaryOp = (
+ l: number | Moment | Dataset,
+ r: number | Moment | Dataset
+) => number | Moment | Dataset | string;
+
+interface FnMapDatasetToValue {
+ [key: string]: FnDatasetToValue;
+}
+
+interface FnMapDatasetToDataset {
+ [key: string]: FnDatasetToDataset;
+}
+
+interface FnMapBinaryOp {
+ [key: string]: FnBinaryOp;
+}
+
+interface FnMapUniryOp {
+ [key: string]: FnUniryOp;
+}
+
+function checkDivisor(divisor: any) {
+ // console.log("checking divior");
+ if (typeof divisor === "number") {
+ if (divisor === 0) return false;
+ } else if (divisor instanceof Dataset) {
+ if (
+ divisor.getValues().some(function (v) {
+ return v === 0;
+ })
+ ) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function checkBinaryOperantType(left: any, right: any) {
+ if (typeof left === "string") return left;
+ if (typeof right === "string") return right;
+ if (
+ typeof left !== "number" &&
+ !window.moment.isMoment(left) &&
+ !(left instanceof Dataset)
+ ) {
+ return "Error: invalid operant type";
+ }
+ if (
+ typeof right !== "number" &&
+ !window.moment.isMoment(right) &&
+ !(right instanceof Dataset)
+ ) {
+ return "Error: invalide operant type";
+ }
+ return "";
+}
+
+const fnMapDatasetToValue: FnMapDatasetToValue = {
+ // first value of a dataset
+ first: function (dataset, renderInfo) {
+ // return number
+ return dataset.getValue(this.startDate(...arguments));
+ },
+ // last value of a dataset
+ last: function (dataset, renderInfo) {
+ // return number
+ return dataset.getValue(this.endDate(...arguments));
+ },
// min value of a dataset
- min: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ min: function (dataset, renderInfo) {
+ // return number
return d3.min(dataset.getValues());
},
// the latest date with min value
- minDate: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ minDate: function (dataset, renderInfo) {
+ // return Moment
let min = d3.min(dataset.getValues());
if (Number.isNumber(min)) {
let arrayDataset = Array.from(dataset);
for (let dataPoint of arrayDataset.reverse()) {
if (dataPoint.value !== null && dataPoint.value === min) {
- return helper.dateToStr(
- dataPoint.date,
- renderInfo.dateFormat
- );
+ return dataPoint.date;
}
}
}
- return "min not found";
+ return "Error: min not found";
},
// max value of a dataset
- max: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ max: function (dataset, renderInfo) {
+ // return number
return d3.max(dataset.getValues());
},
// the latest date with max value
- maxDate: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ maxDate: function (dataset, renderInfo) {
+ // return Moment
let max = d3.max(dataset.getValues());
if (Number.isNumber(max)) {
let arrayDataset = Array.from(dataset);
for (let dataPoint of arrayDataset.reverse()) {
if (dataPoint.value !== null && dataPoint.value === max) {
- return helper.dateToStr(
- dataPoint.date,
- renderInfo.dateFormat
- );
+ return dataPoint.date;
}
}
}
- return "max not found";
+ return "Error: max not found";
},
// start date of a dataset
// if datasetId not found, return overall startDate
- startDate: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ startDate: function (dataset, renderInfo) {
+ // return Moment
if (dataset) {
let startDate = dataset.getStartDate();
if (startDate && startDate.isValid()) {
- return helper.dateToStr(startDate, renderInfo.dateFormat);
+ return startDate;
}
}
- return helper.dateToStr(renderInfo.startDate, renderInfo.dateFormat);
+ return renderInfo.startDate;
},
// end date of a dataset
// if datasetId not found, return overall endDate
- endDate: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ endDate: function (dataset, renderInfo) {
+ // return Moment
if (dataset) {
let endDate = dataset.getEndDate();
if (endDate && endDate.isValid()) {
- return helper.dateToStr(endDate, renderInfo.dateFormat);
+ return endDate;
}
}
- return helper.dateToStr(renderInfo.endDate, renderInfo.dateFormat);
+ return renderInfo.endDate;
},
// sum of all values in a dataset
- sum: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ sum: function (dataset, renderInfo) {
+ // return number
return d3.sum(dataset.getValues());
},
- count: function (renderInfo: RenderInfo, datasetId: number) {
- return "deprecated template variable 'count'";
+ count: function (dataset, renderInfo) {
+ return "Error: deprecated function 'count'";
},
// number of occurrences of a target in a dataset
- numTargets: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ numTargets: function (dataset, renderInfo) {
+ // return number
return dataset.getNumTargets();
},
- days: function (renderInfo: RenderInfo, datasetId: number) {
- return "deprecated template variable 'days'";
+ days: function (dataset, renderInfo) {
+ return "Error: deprecated function 'days'";
},
- numDays: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ numDays: function (dataset, renderInfo) {
+ // return number
return dataset.getLength();
},
- numDaysHavingData: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ numDaysHavingData: function (dataset, renderInfo) {
+ // return number
return dataset.getLengthNotNull();
},
- maxStreak: function (renderInfo: RenderInfo, datasetId: number) {
+ maxStreak: function (dataset, renderInfo) {
+ // return number
let streak = 0;
let maxStreak = 0;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
for (let dataPoint of dataset) {
- if (dataPoint.value !== null) {
+ if (dataPoint.value) {
streak++;
} else {
streak = 0;
@@ -113,15 +188,15 @@ let fnSet = {
}
return maxStreak;
},
- maxStreakStart: function (renderInfo: RenderInfo, datasetId: number) {
+ maxStreakStart: function (dataset, renderInfo) {
+ // return Moment
let streak = 0;
let maxStreak = 0;
let streakStart: Moment = null;
let maxStreakStart: Moment = null;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
if (dataset) {
for (let dataPoint of dataset) {
- if (dataPoint.value !== null) {
+ if (dataPoint.value) {
if (streak === 0) {
streakStart = dataPoint.date;
}
@@ -135,14 +210,14 @@ let fnSet = {
}
}
}
- return helper.dateToStr(maxStreakStart, renderInfo.dateFormat);
+ return maxStreakStart;
},
- maxStreakEnd: function (renderInfo: RenderInfo, datasetId: number) {
+ maxStreakEnd: function (dataset, renderInfo) {
+ // return Moment
let streak = 0;
let maxStreak = 0;
let streakEnd: Moment = null;
let maxStreakEnd: Moment = null;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
if (dataset) {
let arrayDataset = Array.from(dataset);
for (let ind = 0; ind < arrayDataset.length; ind++) {
@@ -151,9 +226,9 @@ let fnSet = {
if (ind < arrayDataset.length - 1) {
nextPoint = arrayDataset[ind + 1];
}
- if (point.value !== null) {
+ if (point.value) {
streak++;
- if (nextPoint?.value === null) {
+ if (!nextPoint?.value) {
streakEnd = point.date;
}
} else {
@@ -167,15 +242,14 @@ let fnSet = {
}
}
}
- return helper.dateToStr(maxStreakEnd, renderInfo.dateFormat);
+ return maxStreakEnd;
},
- maxBreaks: function (renderInfo: RenderInfo, datasetId: number) {
+ maxBreaks: function (dataset, renderInfo) {
+ // return number
let breaks = 0;
let maxBreaks = 0;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
-
for (let dataPoint of dataset) {
- if (dataPoint.value === null) {
+ if (!dataPoint.value) {
breaks++;
} else {
breaks = 0;
@@ -186,15 +260,15 @@ let fnSet = {
}
return maxBreaks;
},
- maxBreaksStart: function (renderInfo: RenderInfo, datasetId: number) {
+ maxBreaksStart: function (dataset, renderInfo) {
+ // return Moment
let breaks = 0;
let maxBreaks = 0;
let breaksStart: Moment = null;
let maxBreaksStart: Moment = null;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
if (dataset) {
for (let dataPoint of dataset) {
- if (dataPoint.value === null) {
+ if (!dataPoint.value) {
if (breaks === 0) {
breaksStart = dataPoint.date;
}
@@ -208,14 +282,14 @@ let fnSet = {
}
}
}
- return helper.dateToStr(maxBreaksStart, renderInfo.dateFormat);
+ return maxBreaksStart;
},
- maxBreaksEnd: function (renderInfo: RenderInfo, datasetId: number) {
+ maxBreaksEnd: function (dataset, renderInfo) {
+ // return Moment
let breaks = 0;
let maxBreaks = 0;
let breaksEnd: Moment = null;
let maxBreaksEnd: Moment = null;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
if (dataset) {
let arrayDataset = Array.from(dataset);
for (let ind = 0; ind < arrayDataset.length; ind++) {
@@ -224,9 +298,9 @@ let fnSet = {
if (ind < arrayDataset.length - 1) {
nextPoint = arrayDataset[ind + 1];
}
- if (point.value === null) {
+ if (!point.value) {
breaks++;
- if (nextPoint?.value !== null) {
+ if (nextPoint?.value) {
breaksEnd = point.date;
}
} else {
@@ -238,19 +312,19 @@ let fnSet = {
}
}
}
- return helper.dateToStr(maxBreaksEnd, renderInfo.dateFormat);
+ return maxBreaksEnd;
},
- lastStreak: function (renderInfo: RenderInfo, datasetId: number) {
- return "deprecated template variable 'lastStreak'";
+ lastStreak: function (dataset, renderInfo) {
+ return "Error: deprecated function 'lastStreak'";
},
- currentStreak: function (renderInfo: RenderInfo, datasetId: number) {
+ currentStreak: function (dataset, renderInfo) {
+ // return number
let currentStreak = 0;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
if (dataset) {
let arrayDataset = Array.from(dataset);
for (let ind = arrayDataset.length - 1; ind >= 0; ind--) {
let point = arrayDataset[ind];
- if (point.value === null) {
+ if (!point.value) {
break;
} else {
currentStreak++;
@@ -259,10 +333,10 @@ let fnSet = {
}
return currentStreak;
},
- currentStreakStart: function (renderInfo: RenderInfo, datasetId: number) {
+ currentStreakStart: function (dataset, renderInfo) {
+ // return Moment
let currentStreak = 0;
let currentStreakStart: Moment = null;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
if (dataset) {
let arrayDataset = Array.from(dataset);
for (let ind = arrayDataset.length - 1; ind >= 0; ind--) {
@@ -270,7 +344,7 @@ let fnSet = {
if (ind < arrayDataset.length - 1) {
currentStreakStart = arrayDataset[ind + 1].date;
}
- if (point.value === null) {
+ if (!point.value) {
break;
} else {
currentStreak++;
@@ -279,19 +353,19 @@ let fnSet = {
}
if (currentStreakStart === null) {
- return "absense";
+ return "Error: absense";
}
- return helper.dateToStr(currentStreakStart, renderInfo.dateFormat);
+ return currentStreakStart;
},
- currentStreakEnd: function (renderInfo: RenderInfo, datasetId: number) {
+ currentStreakEnd: function (dataset, renderInfo) {
+ // return Moment
let currentStreak = 0;
let currentStreakEnd: Moment = null;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
if (dataset) {
let arrayDataset = Array.from(dataset);
for (let ind = arrayDataset.length - 1; ind >= 0; ind--) {
let point = arrayDataset[ind];
- if (point.value === null) {
+ if (!point.value) {
break;
} else {
if (currentStreak === 0) {
@@ -303,18 +377,18 @@ let fnSet = {
}
if (currentStreakEnd === null) {
- return "absense";
+ return "Error: absense";
}
- return helper.dateToStr(currentStreakEnd, renderInfo.dateFormat);
+ return currentStreakEnd;
},
- currentBreaks: function (renderInfo: RenderInfo, datasetId: number) {
+ currentBreaks: function (dataset, renderInfo) {
+ // return number
let currentBreaks = 0;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
if (dataset) {
let arrayDataset = Array.from(dataset);
for (let ind = arrayDataset.length - 1; ind >= 0; ind--) {
let point = arrayDataset[ind];
- if (point.value === null) {
+ if (!point.value) {
currentBreaks++;
} else {
break;
@@ -323,10 +397,10 @@ let fnSet = {
}
return currentBreaks;
},
- currentBreaksStart: function (renderInfo: RenderInfo, datasetId: number) {
+ currentBreaksStart: function (dataset, renderInfo) {
+ // return Moment
let currentBreaks = 0;
let currentBreaksStart: Moment = null;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
if (dataset) {
let arrayDataset = Array.from(dataset);
for (let ind = arrayDataset.length - 1; ind >= 0; ind--) {
@@ -334,7 +408,7 @@ let fnSet = {
if (ind < arrayDataset.length - 1) {
currentBreaksStart = arrayDataset[ind + 1].date;
}
- if (point.value === null) {
+ if (!point.value) {
currentBreaks++;
} else {
break;
@@ -343,19 +417,19 @@ let fnSet = {
}
if (currentBreaksStart === null) {
- return "absense";
+ return "Error: absense";
}
- return helper.dateToStr(currentBreaksStart, renderInfo.dateFormat);
+ return currentBreaksStart;
},
- currentBreaksEnd: function (renderInfo: RenderInfo, datasetId: number) {
+ currentBreaksEnd: function (dataset, renderInfo) {
+ // return Moment
let currentBreaks = 0;
let currentBreaksEnd: Moment = null;
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
if (dataset) {
let arrayDataset = Array.from(dataset);
for (let ind = arrayDataset.length - 1; ind >= 0; ind--) {
let point = arrayDataset[ind];
- if (point.value === null) {
+ if (!point.value) {
if (currentBreaks === 0) {
currentBreaksEnd = point.date;
}
@@ -367,166 +441,589 @@ let fnSet = {
}
if (currentBreaksEnd === null) {
- return "absense";
+ return "Error: absense";
}
- return helper.dateToStr(currentBreaksEnd, renderInfo.dateFormat);
+ return currentBreaksEnd;
},
- average: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ average: function (dataset, renderInfo) {
+ // return number
let countNotNull = dataset.getLengthNotNull();
- if (countNotNull > 0) {
- let sum = d3.sum(dataset.getValues());
- return sum / countNotNull;
+ if (!checkDivisor(countNotNull)) {
+ return "Error: divide by zero in expression";
}
- return null;
+ let sum = d3.sum(dataset.getValues());
+ return sum / countNotNull;
},
- median: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ median: function (dataset, renderInfo) {
+ // return number
return d3.median(dataset.getValues());
},
- variance: function (renderInfo: RenderInfo, datasetId: number) {
- let dataset = renderInfo.datasets.getDatasetById(datasetId);
+ variance: function (dataset, renderInfo) {
+ // return number
return d3.variance(dataset.getValues());
},
};
-export function resolveTemplate(template: string, renderInfo: RenderInfo) {
- //console.log("resolveTemplate");
- let replaceMap: { [key: string]: string } = {};
- // Loop over fnSet, prepare replaceMap
- Object.entries(fnSet).forEach(([fnName, fn]) => {
- // {{\s*max(\(\s*Dataset\(\s*(?\d+)\s*\)\s*\))?\s*}}
- let strRegex =
- "{{\\s*" +
- fnName +
- "(\\(\\s*Dataset\\(\\s*((?\\d+)|(?\\w+))\\s*\\)\\s*\\))?\\s*}}";
- // console.log(strRegex);
- let regex = new RegExp(strRegex, "gm");
- let match;
- while ((match = regex.exec(template))) {
- // console.log(match);
- if (typeof match.groups !== "undefined") {
- if (typeof match.groups.datasetId !== "undefined") {
- let datasetId = parseInt(match.groups.datasetId);
- // console.log(datasetId);
- if (Number.isInteger(datasetId)) {
- let strReplaceRegex =
- "{{\\s*" +
- fnName +
- "(\\(\\s*Dataset\\(\\s*" +
- datasetId.toString() +
- "\\s*\\)\\s*\\))?\\s*}}";
-
- if (!(strReplaceRegex in replaceMap)) {
- let result = fn(renderInfo, datasetId); // calculate result
- let strResult = "{{NA}}";
- if (
- typeof result !== "undefined" &&
- result !== null
- ) {
- if (Number.isInteger(result)) {
- strResult = result.toFixed(0);
- } else {
- strResult = result.toFixed(2);
- }
- }
-
- replaceMap[strReplaceRegex] = strResult;
+const fnMapUniryOp: FnMapUniryOp = {
+ "-": function (u) {
+ if (typeof u === "number") {
+ return -1 * u;
+ } else if (u instanceof Dataset) {
+ let tmpDataset = u.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = -1 * value;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ }
+ return "Error: unknown operation for '-'";
+ },
+ "+": function (u) {
+ if (typeof u === "number") {
+ return u;
+ } else if (u instanceof Dataset) {
+ let tmpDataset = u.cloneToTmpDataset();
+ return tmpDataset;
+ }
+ return "Error: unknown operation for '+'";
+ },
+};
+
+const fnMapBinaryOp: FnMapBinaryOp = {
+ "+": function (l, r) {
+ if (typeof l === "number" && typeof r === "number") {
+ // return number
+ return l + r;
+ } else if (typeof l === "number" && r instanceof Dataset) {
+ // return Dataset
+ let tmpDataset = r.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = l + value;
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ } else if (l instanceof Dataset && typeof r === "number") {
+ // return Dataset
+ let tmpDataset = l.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = value + r;
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ } else if (l instanceof Dataset && r instanceof Dataset) {
+ // return Dataset
+ let tmpDataset = l.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = value + r.getValues()[index];
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ }
+ return "Error: unknown operation for '+'";
+ },
+ "-": function (l, r) {
+ if (typeof l === "number" && typeof r === "number") {
+ // return number
+ return l - r;
+ } else if (typeof l === "number" && r instanceof Dataset) {
+ // return Dataset
+ let tmpDataset = r.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = l - value;
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ } else if (l instanceof Dataset && typeof r === "number") {
+ // return Dataset
+ let tmpDataset = l.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = value - r;
+ } else {
+ array[index] = null;
+ }
+ });
+ return tmpDataset;
+ } else if (l instanceof Dataset && r instanceof Dataset) {
+ // return Dataset
+ let tmpDataset = l.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = value - r.getValues()[index];
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ }
+ return "Error: unknown operation for '-'";
+ },
+ "*": function (l, r) {
+ if (typeof l === "number" && typeof r === "number") {
+ // return number
+ return l * r;
+ } else if (typeof l === "number" && r instanceof Dataset) {
+ // return Dataset
+ let tmpDataset = r.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = l * value;
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ } else if (l instanceof Dataset && typeof r === "number") {
+ // return Dataset
+ let tmpDataset = l.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = value * r;
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ } else if (l instanceof Dataset && r instanceof Dataset) {
+ // return Dataset
+ let tmpDataset = l.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = value * r.getValues()[index];
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ }
+ return "Error: unknown operation for '*'";
+ },
+ "/": function (l, r) {
+ if (!checkDivisor(r)) {
+ return "Error: divide by zero in expression";
+ }
+ if (typeof l === "number" && typeof r === "number") {
+ // return number
+ return l / r;
+ } else if (typeof l === "number" && r instanceof Dataset) {
+ // return Dataset
+ let tmpDataset = r.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = l / value;
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ } else if (l instanceof Dataset && typeof r === "number") {
+ // return Dataset
+ let tmpDataset = l.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = value / r;
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ } else if (l instanceof Dataset && r instanceof Dataset) {
+ // return Dataset
+ let tmpDataset = l.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = value / r.getValues()[index];
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ }
+ return "Error: unknown operation for '/'";
+ },
+ "%": function (l, r) {
+ if (!checkDivisor(r)) {
+ return "Error: divide by zero in expression";
+ }
+ if (typeof l === "number" && typeof r === "number") {
+ // return number
+ return l % r;
+ } else if (typeof l === "number" && r instanceof Dataset) {
+ // return Dataset
+ let tmpDataset = r.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = l % value;
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ } else if (l instanceof Dataset && typeof r === "number") {
+ // return Dataset
+ let tmpDataset = l.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = value % r;
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ } else if (l instanceof Dataset && r instanceof Dataset) {
+ // return Dataset
+ let tmpDataset = l.cloneToTmpDataset();
+ tmpDataset.getValues().forEach(function (value, index, array) {
+ if (array[index] !== null) {
+ array[index] = value % r.getValues()[index];
+ } else {
+ array[index] = null;
+ }
+ });
+ tmpDataset.recalculateMinMax();
+ return tmpDataset;
+ }
+ return "Error: unknown operation for '%'";
+ },
+};
+
+const fnMapDatasetToDataset: FnMapDatasetToDataset = {
+ // min value of a dataset
+ normalize: function (dataset, args, renderInfo) {
+ // console.log("normalize");
+ // console.log(dataset);
+ let yMin = dataset.getYMin();
+ let yMax = dataset.getYMax();
+ // console.log(`yMin/yMax: ${yMin}/${yMax}`);
+ if (yMin !== null && yMax !== null && yMax > yMin) {
+ let normalized = dataset.cloneToTmpDataset();
+ normalized.getValues().forEach(function (value, index, array) {
+ array[index] = (value - yMin) / (yMax - yMin);
+ });
+ normalized.recalculateMinMax();
+ return normalized;
+ }
+ return "Error: invalid data range for function 'normalize'";
+ },
+ setMissingValues: function (dataset, args, renderInfo) {
+ // console.log("setMissingValues");
+ // console.log(dataset);
+ // console.log(args);
+ if (args && args.length > 0) {
+ let missingValue = args[0];
+ // console.log(missingValue);
+ let newDataset = dataset.cloneToTmpDataset();
+ if (Number.isNumber(missingValue) && !Number.isNaN(missingValue)) {
+ newDataset.getValues().forEach(function (value, index, array) {
+ if (value === null) {
+ array[index] = missingValue as number;
+ }
+ });
+ newDataset.recalculateMinMax();
+ return newDataset;
+ }
+ return "Error: invalid arguments for function 'setMissingValues'";
+ }
+ return "Error: invalid arguments for function 'setMissingValues";
+ },
+};
+
+function getDatasetById(datasetId: number, renderInfo: RenderInfo) {
+ return renderInfo.datasets.getDatasetById(datasetId);
+}
+
+function evaluateArray(arr: any, renderInfo: RenderInfo) {
+ return arr.map(function (expr: jsep.Expression) {
+ return evaluate(expr, renderInfo);
+ });
+}
+
+function evaluate(expr: jsep.Expression, renderInfo: RenderInfo): any {
+ // console.log(expr);
+
+ switch (expr.type) {
+ case "Literal":
+ let literalExpr = expr as jsep.Literal;
+ return literalExpr.value; // string, number, boolean
+
+ case "Identifier":
+ let identifierExpr = expr as jsep.Identifier;
+ let identifierName = identifierExpr.name;
+ if (identifierName in fnMapDatasetToValue) {
+ return `Error: deprecated template variable '${identifierName}', use '${identifierName}()' instead`;
+ } else if (identifierName in fnMapDatasetToDataset) {
+ return `Error: deprecated template variable '${identifierName}', use '${identifierName}()' instead`;
+ }
+ return `Error: unknown function name '${identifierName}'`;
+
+ case "UnaryExpression":
+ let uniryExpr = expr as jsep.UnaryExpression;
+ let retUniryArg = evaluate(uniryExpr.argument, renderInfo);
+ if (typeof retUniryArg === "string") {
+ return retUniryArg;
+ }
+ return fnMapUniryOp[uniryExpr.operator](retUniryArg);
+
+ case "BinaryExpression":
+ let binaryExpr = expr as jsep.BinaryExpression;
+ let leftValue = evaluate(binaryExpr.left, renderInfo);
+ let rightValue = evaluate(binaryExpr.right, renderInfo);
+ let retCheck = checkBinaryOperantType(leftValue, rightValue);
+ if (typeof retCheck === "string" && retCheck.startsWith("Error:")) {
+ return retCheck;
+ }
+ return fnMapBinaryOp[binaryExpr.operator](leftValue, rightValue);
+
+ case "CallExpression":
+ let callExpr = expr as jsep.CallExpression;
+
+ let calleeIdentifier = callExpr.callee as jsep.Identifier;
+ let fnName = calleeIdentifier.name;
+ let args = callExpr.arguments;
+ // console.log(fnName);
+ // console.log(args);
+ let evaluatedArgs = evaluateArray(args, renderInfo);
+ if (typeof evaluatedArgs === "string") return evaluatedArgs;
+
+ // function dataset accept only one arg in number
+ if (fnName === "dataset") {
+ if (evaluatedArgs.length === 1) {
+ let arg = evaluatedArgs[0];
+ if (typeof arg === "string") return arg;
+ if (typeof arg !== "number") {
+ return "Error: function 'dataset' only accepts id in number";
+ }
+ let dataset = getDatasetById(arg, renderInfo);
+ if (!dataset) {
+ return `Error: no dataset found for id '${arg}'`;
+ }
+ return dataset;
+ }
+ }
+ // fnDataset accept only one arg in number or Dataset
+ else if (fnName in fnMapDatasetToValue) {
+ if (evaluatedArgs.length === 0) {
+ // Use first non-X dataset
+ let dataset = null;
+ for (let ds of renderInfo.datasets) {
+ if (!dataset && !ds.getQuery().usedAsXDataset) {
+ dataset = ds;
+ // if breaks here, the index of Datasets not reset???
}
}
- } else if (typeof match.groups.datasetName !== "undefined") {
- let datasetName = match.groups.datasetName;
- // console.log(datasetName);
- let strReplaceRegex =
- "{{\\s*" +
- fnName +
- "(\\(\\s*Dataset\\(\\s*" +
- datasetName +
- "\\s*\\)\\s*\\))?\\s*}}";
-
- let datasetId = renderInfo.datasetName.indexOf(datasetName);
- // console.log(datasetName);
- // console.log(renderInfo.datasetName);
- // console.log(datasetId);
- if (!(strReplaceRegex in replaceMap)) {
- let strResult = "{{NA}}";
- if (datasetId >= 0) {
- let result = fn(renderInfo, datasetId); // calculate result
- if (
- typeof result !== "undefined" &&
- result !== null
+ if (!dataset) {
+ return `No available dataset found for function ${fnName}`;
+ }
+ return fnMapDatasetToValue[fnName](dataset, renderInfo);
+ }
+ if (evaluatedArgs.length === 1) {
+ let arg = evaluatedArgs[0];
+ if (typeof arg === "string") return arg;
+ if (arg instanceof Dataset) {
+ return fnMapDatasetToValue[fnName](arg, renderInfo);
+ } else {
+ return `Error: function '${fnName}' only accepts Dataset`;
+ }
+ }
+ return `Error: Too many arguments for function ${fnName}`;
+ } else if (fnName in fnMapDatasetToDataset) {
+ if (evaluatedArgs.length === 1) {
+ if (typeof evaluatedArgs[0] === "string")
+ return evaluatedArgs[0]; // error message
+ if (evaluatedArgs[0] instanceof Dataset) {
+ let dataset = evaluatedArgs[0];
+ return fnMapDatasetToDataset[fnName](
+ dataset,
+ null,
+ renderInfo
+ );
+ } else {
+ return `Error: function ${fnName} only accept Dataset`;
+ }
+ } else if (evaluatedArgs.length > 1) {
+ if (typeof evaluatedArgs[0] === "string") {
+ return evaluatedArgs[0];
+ }
+ if (evaluatedArgs[0] instanceof Dataset) {
+ let dataset = evaluatedArgs[0];
+ return fnMapDatasetToDataset[fnName](
+ dataset,
+ evaluatedArgs.filter(function (
+ value: any,
+ index: number,
+ arr: any
) {
- if (Number.isInteger(result)) {
- strResult = result.toFixed(0);
- } else {
- strResult = result.toFixed(2);
- }
- }
- }
- replaceMap[strReplaceRegex] = strResult;
+ return index > 0;
+ }),
+ renderInfo
+ );
+ } else {
+ return `Error: function ${fnName} only accept Dataset`;
}
- } else {
- // no datasetId assigned use id 0
- // console.log("{{" + fnName + "}}")
- let strReplaceRegex = "{{\\s*" + fnName + "\\s*}}";
- if (!(strReplaceRegex in replaceMap)) {
- let result = fn(renderInfo, 0); // calculate result
- let strResult = "{{NA}}";
- if (typeof result !== "undefined" && result !== null) {
- if (typeof result === "number") {
- if (Number.isInteger(result)) {
- strResult = result.toFixed(0);
- } else {
- strResult = result.toFixed(2);
- }
- } else if (typeof result === "string") {
- strResult = result;
- }
- }
+ }
+ return `Error: Too many arguments for function ${fnName}`;
+ }
+ return `Error: unknown function name '${fnName}'`;
+ }
+ return "Error: unknown expression";
+}
- replaceMap[strReplaceRegex] = strResult;
- }
+interface ExprResolved {
+ source: string;
+ value: number | Moment;
+ format: string;
+}
+
+// Get a list of resolved result containing source, value, and format
+function resolve(
+ text: string,
+ renderInfo: RenderInfo
+): Array | string {
+ // console.log(text);
+
+ let exprMap: Array = [];
+
+ // {{(?[\w+\-*\/0-9\s()\[\]%.]+)(::(?[\w+\-*\/0-9\s()\[\]%.:]+))?}}
+ let strExprRegex =
+ "{{(?[\\w+\\-*\\/0-9\\s()\\[\\]%.,]+)(::(?[\\w+\\-*\\/0-9\\s()\\[\\]%.:]+))?}}";
+ let exprRegex = new RegExp(strExprRegex, "gm");
+ let match;
+ while ((match = exprRegex.exec(text))) {
+ // console.log(match);
+ let fullmatch = match[0];
+ if (exprMap.some((e) => e.source === fullmatch)) continue;
+
+ if (typeof match.groups !== "undefined") {
+ if (typeof match.groups.expr !== "undefined") {
+ let expr = match.groups.expr;
+
+ let ast = null;
+ try {
+ ast = jsep(expr);
+ } catch (err) {
+ return "Error:" + err.message;
}
- } else {
- // groups undefined
- // no datasetId assigned use id 0
- // console.log("{{" + fnName + "}}")
- let strReplaceRegex = "{{\\s*" + fnName + "\\s*}}";
- if (!(strReplaceRegex in replaceMap)) {
- let result = fn(renderInfo, 0); // calculate result
- let strResult = "{{NA}}";
- if (typeof result !== "undefined" && result !== null) {
- if (Number.isInteger(result)) {
- strResult = result.toFixed(0);
- } else {
- strResult = result.toFixed(2);
- }
- } else if (typeof result === "string") {
- strResult = result;
+ if (!ast) {
+ return "Error: failed to parse expression";
+ }
+ // console.log(ast);
+
+ const value = evaluate(ast, renderInfo);
+ if (typeof value === "string") {
+ return value; // error message
+ }
+
+ if (
+ typeof value === "number" ||
+ window.moment.isMoment(value)
+ ) {
+ let format = null;
+ if (typeof match.groups.format !== "undefined") {
+ format = match.groups.format;
}
- replaceMap[strReplaceRegex] = strResult;
+ exprMap.push({
+ source: fullmatch,
+ value: value,
+ format: format,
+ });
}
}
}
- });
+ }
- // console.log(replaceMap);
- // Do replace
- for (let strReplaceRegex in replaceMap) {
- let strResult = replaceMap[strReplaceRegex];
- let regex = new RegExp(strReplaceRegex, "gi");
- template = template.replace(regex, strResult);
+ return exprMap;
+}
+
+// Resolve the template expression in string and return a resolved string
+export function resolveTemplate(
+ template: string,
+ renderInfo: RenderInfo
+): string {
+ let retResolve = resolve(template, renderInfo);
+ if (typeof retResolve === "string") {
+ return retResolve; // error message
+ }
+ let exprMap = retResolve as Array;
+
+ for (let exprResolved of exprMap) {
+ let source = exprResolved.source;
+ let value = exprResolved.value;
+ let format = exprResolved.format;
+ let strValue = "";
+ if (typeof value === "number") {
+ if (format) {
+ strValue = sprintf("%" + format, value);
+ } else {
+ strValue = value.toFixed(1);
+ }
+ } else if (window.moment.isMoment(value)) {
+ if (format) {
+ strValue = helper.dateToStr(value, format);
+ } else {
+ strValue = helper.dateToStr(value, renderInfo.dateFormat);
+ }
+ }
+
+ if (strValue) {
+ // console.log(exprResolved);
+ template = template.split(source).join(strValue);
+ }
}
return template;
}
-export function resolve(s: string, renderInfo: RenderInfo) {
- console.log(s);
- s = resolveTemplate(s, renderInfo);
- console.log(s);
- const ast = parse(s);
- const value = evaluate(ast, {});
- return value;
+// Resolve the template expression in string and return a number or date
+export function resolveValue(
+ text: string,
+ renderInfo: RenderInfo
+): number | Moment | string {
+ // console.log(template);
+ text = text.trim();
+
+ // input is pure number
+ if (/^([\-]?[0-9]+[\.][0-9]+|[\-]?[0-9]+)$/.test(text)) {
+ return parseFloat(text);
+ }
+
+ // template
+ let retResolve = resolve(text, renderInfo);
+ if (typeof retResolve === "string") {
+ return retResolve; // error message
+ }
+ let exprMap = retResolve as Array;
+
+ if (exprMap.length > 0) {
+ return exprMap[0].value; // only first value will be return
+ }
+
+ return "Error: failed to resolve values";
}
diff --git a/src/heatmap.ts b/src/heatmap.ts
index 9531ba27..359db0b5 100644
--- a/src/heatmap.ts
+++ b/src/heatmap.ts
@@ -9,7 +9,7 @@ import {
Size,
Transform,
ChartElements,
- OutputType,
+ GraphType,
ValueType,
} from "./data";
import * as helper from "./helper";
@@ -128,17 +128,16 @@ function renderHeatmapDays(
// Prepare data for graph
let daysInHeatmapView: Array = [];
const dataStartDate = dataset.getStartDate().clone();
- let startDate = dataStartDate
- .clone()
- .subtract(dataStartDate.day(), "days");
- if (heatmapInfo.startWeekOn.toLowerCase() === "mon") {
- startDate = startDate.add(1, "days");
- }
+ const startDayIndex = helper.getDayIndex(heatmapInfo.startWeekOn);
+ const currentDayOfWeek = dataStartDate.day(); // 0=Sunday, 1=Monday, etc.
+ // Calculate days to subtract to get to the start of the week
+ const daysToSubtract = (currentDayOfWeek - startDayIndex + 7) % 7;
+ let startDate = dataStartDate.clone().subtract(daysToSubtract, "days");
const dataEndDate = dataset.getEndDate().clone();
- let endDate = dataEndDate.clone().add(7 - dataEndDate.day() - 1, "days");
- if (heatmapInfo.startWeekOn.toLowerCase() === "mon") {
- endDate = endDate.add(1, "days");
- }
+ const endDayOfWeek = dataEndDate.day();
+ // Calculate days to add to complete the week (get to Saturday of that week)
+ const daysToAdd = (6 - endDayOfWeek + startDayIndex) % 7;
+ let endDate = dataEndDate.clone().add(daysToAdd, "days");
// console.log(startDate.format("YYYY-MM-DD"));
// console.log(endDate.format("YYYY-MM-DD"));
@@ -150,16 +149,10 @@ function renderHeatmapDays(
curDate <= endDate;
curDate.add(1, "days")
) {
- if (heatmapInfo.startWeekOn.toLowerCase() === "mon") {
- indCol = curDate.day() - 1;
- if (indCol < 0) {
- indCol = 6;
- }
- indRow = Math.floor(ind / 7);
- } else {
- indCol = curDate.day(); // 0~6
- indRow = Math.floor(ind / 7);
- }
+ const dayOfWeek = curDate.day(); // 0=Sunday, 1=Monday, etc.
+ // Calculate column: shift by startDayIndex
+ indCol = (dayOfWeek - startDayIndex + 7) % 7;
+ indRow = Math.floor(ind / 7);
// curValue and scaledValue
let curValue = dataset.getValue(curDate);
diff --git a/src/helper.ts b/src/helper.ts
index 8449bd6b..ad1e7b32 100644
--- a/src/helper.ts
+++ b/src/helper.ts
@@ -1,24 +1,76 @@
-import { RenderInfo, Size, Transform } from "./data";
+import { RenderInfo, Size, TextValueMap, Transform } from "./data";
import { TFile, TFolder, normalizePath } from "obsidian";
import { ValueType } from "./data";
import * as d3 from "d3";
-import { Moment } from "moment";
+import { Moment, Duration } from "moment";
// date and time
-const timeFormat = [
- "HH:mm",
- "HH:m",
- "H:mm",
- "H:m",
- "hh:mm A",
- "hh:mm a",
- "hh:m A",
- "hh:m a",
- "h:mm A",
- "h:mm a",
- "h:m A",
- "h:m a",
-];
+function makeTimeFormat() {
+ //HH: 2-digits hours (24 hour time) from 0 to 23, H:, 2-digits hours (24 hour time) from 0 to 23 without leading 0
+ // hh: 2-digits hours (12 hour time), h: 2-digits hours (12 hour time) without leading 0
+ // a/A: am or pm
+ const fmtHours = ["HH", "H", "hh", "h"];
+ //mm: 2-digits minutes, m: 2-digits minutes without leading zero
+ const fmtMins = ["mm", "m"];
+ // ss: 2-digits seconds, s: 2-digits seconds without leading zero
+ // can be empty
+ const fmtSecs = ["ss", "s", ""];
+
+ let timeFormat = [];
+ for (let fmtHour of fmtHours) {
+ for (let fmtMin of fmtMins) {
+ for (let fmtSec of fmtSecs) {
+ let fmt = `${fmtHour}:${fmtMin}`;
+ if (fmtSec !== "") {
+ fmt += `:${fmtSec}`;
+ }
+ if (fmtHour.includes("h")) {
+ fmt += " a";
+ }
+ timeFormat.push(fmt);
+ }
+ }
+ }
+ //console.log(timeFormat);
+ return timeFormat;
+}
+const timeFormat = makeTimeFormat();
+
+export function getDateStringFromInputString(
+ inputString: string,
+ dateFormatPrefix: string,
+ dateFormatSuffix: string
+) {
+ if (!dateFormatPrefix && !dateFormatSuffix) return inputString;
+
+ let dateString = inputString;
+ if (dateString.startsWith("^")) {
+ dateString = dateString.slice(1);
+ }
+ // console.log(dateString);
+
+ if (dateFormatPrefix) {
+ let strRegex = "^(" + dateFormatPrefix + ")";
+ // console.log(strRegex);
+ let regex = new RegExp(strRegex, "gm");
+ if (regex.test(dateString)) {
+ dateString = dateString.replace(regex, "");
+ }
+ }
+ // console.log(dateString);
+
+ if (dateFormatSuffix) {
+ let strRegex = "(" + dateFormatSuffix + ")$";
+ // console.log(strRegex);
+ let regex = new RegExp(strRegex, "gm");
+ if (regex.test(dateString)) {
+ dateString = dateString.replace(regex, "");
+ }
+ }
+ // console.log(dateString);
+
+ return dateString;
+}
export function strToDate(strDate: string, dateFormat: string): Moment {
let format: any = dateFormat;
@@ -43,39 +95,187 @@ export function strToDate(strDate: string, dateFormat: string): Moment {
return date;
}
-export function relDateStringToDate(
+function extractValueFromDurationString(
+ strDuration: string,
+ units: Array,
+ removePattern: boolean = true
+): [number, string] {
+ if (!strDuration || !units || units.length === 0) {
+ return [null, strDuration];
+ }
+
+ let value = null;
+ const strRegex = "^(?[0-9]+)(" + units.join("|") + ")$";
+ // console.log(strRegex);
+ const regex = new RegExp(strRegex, "gm");
+ let match = regex.exec(strDuration);
+ if (
+ match &&
+ typeof match.groups !== "undefined" &&
+ typeof match.groups.value !== "undefined"
+ ) {
+ // console.log(match);
+ value = parseFloat(match.groups.value);
+ if (Number.isNumber(value) && !Number.isNaN(value)) {
+ if (removePattern) {
+ strDuration = strDuration.replace(regex, "");
+ }
+ // console.log(value);
+ // console.log(strDuration);
+ return [value, strDuration];
+ }
+ }
+
+ return [null, strDuration];
+}
+
+export function parseDurationString(strDuration: string) {
+ //duration string format:
+ //year (years, y, Y),
+ //month (months, M), // m will conflict with minute!!!
+ //week (weeks, w, W),
+ //day (days, d, D),
+ //hour (hours, h, H),
+ //minute (minutes, m), // M will conflict with month!!!
+ //second (seconds, s, S)
+ if (!strDuration) return null;
+
+ let duration: Duration = window.moment.duration(0);
+ let hasValue = false;
+
+ let negativeValue = false;
+ if (strDuration.startsWith("+")) {
+ negativeValue = false;
+ strDuration = strDuration.substring(1);
+ }
+ if (strDuration.startsWith("-")) {
+ negativeValue = true;
+ strDuration = strDuration.substring(1);
+ }
+
+ let yearValue = null;
+ [yearValue, strDuration] = extractValueFromDurationString(strDuration, [
+ "year",
+ "years",
+ "Y",
+ "y",
+ ]);
+ if (yearValue !== null) {
+ if (negativeValue) {
+ yearValue *= -1;
+ }
+ duration.add(yearValue, "years");
+ hasValue = true;
+ }
+
+ let monthValue = null;
+ [monthValue, strDuration] = extractValueFromDurationString(strDuration, [
+ "month",
+ "months",
+ "M",
+ ]);
+ if (monthValue !== null) {
+ if (negativeValue) {
+ monthValue *= -1;
+ }
+ duration.add(monthValue, "months");
+ hasValue = true;
+ }
+
+ let weekValue = null;
+ [weekValue, strDuration] = extractValueFromDurationString(strDuration, [
+ "week",
+ "weeks",
+ "W",
+ "w",
+ ]);
+ if (weekValue !== null) {
+ if (negativeValue) {
+ weekValue *= -1;
+ }
+ duration.add(weekValue, "weeks");
+ hasValue = true;
+ }
+
+ let dayValue = null;
+ [dayValue, strDuration] = extractValueFromDurationString(strDuration, [
+ "day",
+ "days",
+ "D",
+ "d",
+ ]);
+ if (dayValue !== null) {
+ if (negativeValue) {
+ dayValue *= -1;
+ }
+ duration.add(dayValue, "days");
+ hasValue = true;
+ }
+
+ let hourValue = null;
+ [hourValue, strDuration] = extractValueFromDurationString(strDuration, [
+ "hour",
+ "hours",
+ "H",
+ "h",
+ ]);
+ if (hourValue !== null) {
+ if (negativeValue) {
+ hourValue *= -1;
+ }
+ duration.add(hourValue, "hours");
+ hasValue = true;
+ }
+
+ let minuteValue = null;
+ [minuteValue, strDuration] = extractValueFromDurationString(strDuration, [
+ "minute",
+ "minutes",
+ "m",
+ ]);
+ if (minuteValue !== null) {
+ if (negativeValue) {
+ minuteValue *= -1;
+ }
+ duration.add(minuteValue, "minutes");
+ hasValue = true;
+ }
+
+ let secondValue = null;
+ [secondValue, strDuration] = extractValueFromDurationString(strDuration, [
+ "second",
+ "seconds",
+ "S",
+ "s",
+ ]);
+ if (secondValue !== null) {
+ if (negativeValue) {
+ secondValue *= -1;
+ }
+ duration.add(secondValue, "seconds");
+ hasValue = true;
+ }
+
+ if (!hasValue) return null;
+ return duration;
+}
+
+export function getDateByDurationToToday(
relDateString: string,
dateFormat: string
): Moment {
let date = null;
- const relDateRegex = /^(?[\-\+]?[0-9]+)(?[dwmy])$/;
- if (relDateRegex.test(relDateString)) {
- let match = relDateRegex.exec(relDateString);
- if (
- typeof match.groups !== "undefined" &&
- typeof match.groups.value !== "undefined" &&
- typeof match.groups.unit !== "undefined"
- ) {
- let value = parseFloat(match.groups.value);
- let unit = match.groups.unit;
- date = getDateToday(dateFormat);
- if (unit === "d") {
- date = date.add(value, "days");
- } else if (unit === "w") {
- date = date.add(value, "weeks");
- } else if (unit === "m") {
- date = date.add(value, "months");
- } else if (unit === "y") {
- date = date.add(value, "years");
- }
- }
- }
+ let duration = parseDurationString(relDateString);
+ if (duration && window.moment.isDuration(duration)) {
+ date = getDateToday(dateFormat);
+ date = date.add(duration);
- if (date && date.isValid()) {
- return date;
+ if (date && date.isValid()) {
+ return date;
+ }
}
- return null;
+ return date;
}
export function dateToStr(date: Moment, dateFormat: string): string {
@@ -111,10 +311,15 @@ export function deepValue(obj: any, str: string) {
if (k in obj) {
obj = obj[k];
} else {
- return;
+ return null;
}
}
- return obj;
+ if (typeof obj === "string" || Array.isArray(obj)) {
+ return obj;
+ } else if (typeof obj === "number" || typeof obj === "boolean") {
+ return obj.toString();
+ }
+ return null;
}
// String helpers
@@ -127,8 +332,59 @@ export function trimByChar(str: string, char: string) {
: str.substring(first, str.length - last);
}
+/**
+ * Convert day abbreviation or full name to day index (0-6)
+ * @param dayAbbr - Day abbreviation or full name: "Sun"/"Sunday", "Mon"/"Monday", etc.
+ * @returns Day index (0=Sunday, 1=Monday, ..., 6=Saturday)
+ */
+export function getDayIndex(dayAbbr: string): number {
+ const dayMap: { [key: string]: number } = {
+ "sun": 0,
+ "sunday": 0,
+ "mon": 1,
+ "monday": 1,
+ "tue": 2,
+ "tuesday": 2,
+ "wed": 3,
+ "wednesday": 3,
+ "thu": 4,
+ "thursday": 4,
+ "fri": 5,
+ "friday": 5,
+ "sat": 6,
+ "saturday": 6
+ };
+ const normalized = dayAbbr.toLowerCase().trim();
+ return dayMap[normalized] !== undefined ? dayMap[normalized] : 0; // Default to Sunday
+}
+
+export function replaceImgTagByAlt(input: string) {
+ if (input === null) return null;
+
+ //
]*?alt\s*=\s*[""']?(?[^'"" >]+?)[ '""][^>]*?>
+ let strRegex =
+ '
]*?alt\\s*=\\s*[""\']?(?[^\'"" >]+?)[ \'""][^>]*?>';
+ // console.log(strRegex);
+ let regex = new RegExp(strRegex, "g");
+
+ let output = input.replace(regex, (...args) => {
+ let groups = args[args.length - 1];
+ if (groups && groups.emoji) {
+ return groups.emoji.trim();
+ }
+ return "";
+ });
+
+ return output;
+}
// Parsing
-export function parseFloatFromAny(toParse: any) {
+export function parseFloatFromAny(
+ toParse: any,
+ textValueMap: TextValueMap = null
+) {
+ // console.log("parseFloatFromAny");
+ // console.log(toParse);
+
let value = null;
let valueType = ValueType.Number;
if (typeof toParse === "string") {
@@ -151,7 +407,36 @@ export function parseFloatFromAny(toParse: any) {
valueType = ValueType.Time;
}
} else {
- value = parseFloat(toParse);
+ if (textValueMap) {
+ let anyMatch = false;
+ const keys = Object.keys(textValueMap) as Array;
+ for (let key of keys) {
+ if (typeof key === "string") {
+ let regex = new RegExp(key, "gm");
+ // console.log(toParse);
+ if (
+ regex.test(toParse) &&
+ Number.isNumber(textValueMap[key])
+ ) {
+ let strReplacedValue = textValueMap[key].toString();
+ toParse = toParse.replace(regex, strReplacedValue);
+ // console.log(toParse);
+ anyMatch = true;
+ break;
+ }
+ }
+ }
+
+ value = parseFloat(toParse);
+ if (Number.isNaN(value)) {
+ value = null;
+ }
+ } else {
+ value = parseFloat(toParse);
+ if (Number.isNaN(value)) {
+ value = null;
+ }
+ }
}
} else if (typeof toParse === "number") {
value = toParse;
diff --git a/src/main.ts b/src/main.ts
index ba09ace0..819749c3 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,18 +1,21 @@
-import { App, CachedMetadata, Plugin } from "obsidian";
+import { App, CachedMetadata, getLinkpath, Plugin } from "obsidian";
import { MarkdownPostProcessorContext, MarkdownView, Editor } from "obsidian";
import { TFile, TFolder, normalizePath } from "obsidian";
-import { render, renderErrorMessage } from "./rendering";
+import * as rendering from "./rendering";
import { getRenderInfoFromYaml } from "./parsing";
import {
Datasets,
Query,
QueryValuePair,
- OutputType,
+ GraphType,
SearchType,
TableData,
RenderInfo,
XValueMap,
DataMap,
+ CustomDatasetInfo,
+ CollectingProcessInfo,
+ ValueType,
} from "./data";
import * as collecting from "./collecting";
import {
@@ -31,6 +34,12 @@ declare global {
}
}
+declare module 'obsidian' {
+ interface Vault {
+ getConfig(prop: string): any;
+ }
+}
+
export default class Tracker extends Plugin {
settings: TrackerSettings;
@@ -49,19 +58,19 @@ export default class Tracker extends Plugin {
this.addCommand({
id: "add-line-chart-tracker",
name: "Add Line Chart Tracker",
- callback: () => this.addCodeBlock(OutputType.Line),
+ callback: () => this.addCodeBlock(GraphType.Line),
});
this.addCommand({
id: "add-bar-chart-tracker",
name: "Add Bar Chart Tracker",
- callback: () => this.addCodeBlock(OutputType.Bar),
+ callback: () => this.addCodeBlock(GraphType.Bar),
});
this.addCommand({
id: "add-summary-tracker",
name: "Add Summary Tracker",
- callback: () => this.addCodeBlock(OutputType.Summary),
+ callback: () => this.addCodeBlock(GraphType.Summary),
});
}
@@ -77,6 +86,12 @@ export default class Tracker extends Plugin {
await this.saveData(this.settings);
}
+ renderErrorMessage(message: string, canvas: HTMLElement, el: HTMLElement) {
+ rendering.renderErrorMessage(canvas, message);
+ el.appendChild(canvas);
+ return;
+ }
+
onunload() {
console.log("unloading obsidian-tracker plugin");
}
@@ -102,19 +117,143 @@ export default class Tracker extends Plugin {
return files;
}
- getFiles(folderToSearch: string, includeSubFolders: boolean = true) {
- let files: TFile[] = [];
+ async getFiles(
+ files: TFile[],
+ renderInfo: RenderInfo,
+ includeSubFolders: boolean = true
+ ) {
+ if (!files) return;
+
+ let folderToSearch = renderInfo.folder;
+ let useSpecifiedFilesOnly = renderInfo.specifiedFilesOnly;
+ let specifiedFiles = renderInfo.file;
+ let filesContainsLinkedFiles = renderInfo.fileContainsLinkedFiles;
+ let fileMultiplierAfterLink = renderInfo.fileMultiplierAfterLink;
+
+ // Include files in folder
+ // console.log(useSpecifiedFilesOnly);
+ if (!useSpecifiedFilesOnly) {
+ let folder = this.app.vault.getAbstractFileByPath(
+ normalizePath(folderToSearch)
+ );
+ if (folder && folder instanceof TFolder) {
+ let folderFiles = this.getFilesInFolder(folder);
+ for (let file of folderFiles) {
+ files.push(file);
+ }
+ }
+ }
- let folder = this.app.vault.getAbstractFileByPath(
- normalizePath(folderToSearch)
- );
- if (!folder || !(folder instanceof TFolder)) {
- // Folder not exists
- } else {
- files = files.concat(this.getFilesInFolder(folder));
+ // Include specified file
+ // console.log(specifiedFiles);
+ for (let filePath of specifiedFiles) {
+ let path = filePath;
+ if (!path.endsWith(".md")) {
+ path += ".md";
+ }
+ path = normalizePath(path);
+ // console.log(path);
+
+ let file = this.app.vault.getAbstractFileByPath(path);
+ // console.log(file);
+ if (file && file instanceof TFile) {
+ files.push(file);
+ }
}
+ // console.log(files);
- return files;
+ // Include files in pointed by links in file
+ // console.log(filesContainsLinkedFiles);
+ // console.log(fileMultiplierAfterLink);
+ let linkedFileMultiplier = 1;
+ let searchFileMultifpierAfterLink = true;
+ if (fileMultiplierAfterLink === "") {
+ searchFileMultifpierAfterLink = false;
+ } else if (/^[0-9]+$/.test(fileMultiplierAfterLink)) {
+ // integer
+ linkedFileMultiplier = parseFloat(fileMultiplierAfterLink);
+ searchFileMultifpierAfterLink = false;
+ } else if (!/\?/.test(fileMultiplierAfterLink)) {
+ // no 'value' named group
+ searchFileMultifpierAfterLink = false;
+ }
+ for (let filePath of filesContainsLinkedFiles) {
+ if (!filePath.endsWith(".md")) {
+ filePath += ".md";
+ }
+ let file = this.app.vault.getAbstractFileByPath(
+ normalizePath(filePath)
+ );
+ if (file && file instanceof TFile) {
+ // Get linked files
+ let fileCache = this.app.metadataCache.getFileCache(file);
+ let fileContent = await this.app.vault.adapter.read(file.path);
+ let lines = fileContent.split(
+ /\r\n|[\n\v\f\r\x85\u2028\u2029]/
+ );
+ // console.log(lines);
+
+ if (!fileCache?.links) continue;
+
+ for (let link of fileCache.links) {
+ if (!link) continue;
+ let linkedFile =
+ this.app.metadataCache.getFirstLinkpathDest(
+ link.link,
+ filePath
+ );
+ if (linkedFile && linkedFile instanceof TFile) {
+ if (searchFileMultifpierAfterLink) {
+ // Get the line of link in file
+ let lineNumber = link.position.end.line;
+ // console.log(lineNumber);
+ if (lineNumber >= 0 && lineNumber < lines.length) {
+ let line = lines[lineNumber];
+ // console.log(line);
+
+ // Try extract multiplier
+ // if (link.position)
+ let splitted = line.split(link.original);
+ // console.log(splitted);
+ if (splitted.length === 2) {
+ let toParse = splitted[1].trim();
+ let strRegex = fileMultiplierAfterLink;
+ let regex = new RegExp(strRegex, "gm");
+ let match;
+ while ((match = regex.exec(toParse))) {
+ // console.log(match);
+ if (
+ typeof match.groups !==
+ "undefined" &&
+ typeof match.groups.value !==
+ "undefined"
+ ) {
+ // must have group name 'value'
+ let retParse =
+ helper.parseFloatFromAny(
+ match.groups.value.trim(),
+ renderInfo.textValueMap
+ );
+ if (retParse.value !== null) {
+ linkedFileMultiplier =
+ retParse.value;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (let i = 0; i < linkedFileMultiplier; i++) {
+ files.push(linkedFile);
+ }
+ }
+ }
+ }
+ }
+
+ // console.log(files);
}
async postprocessor(
@@ -126,25 +265,33 @@ export default class Tracker extends Plugin {
const canvas = document.createElement("div");
let yamlText = source.trim();
+
+ // Replace all tabs by spaces
+ let tabSize = this.app.vault.getConfig("tabSize");
+ let spaces = Array(tabSize).fill(" ").join("");
+ yamlText = yamlText.replace(/\t/gm, spaces);
+
+ // Get render info
let retRenderInfo = getRenderInfoFromYaml(yamlText, this);
if (typeof retRenderInfo === "string") {
- let errorMessage = retRenderInfo;
- renderErrorMessage(canvas, errorMessage);
- el.appendChild(canvas);
- return;
+ return this.renderErrorMessage(retRenderInfo, canvas, el);
}
let renderInfo = retRenderInfo as RenderInfo;
// console.log(renderInfo);
// Get files
- let files: TFile[];
+ let files: TFile[] = [];
try {
- files = this.getFiles(renderInfo.folder);
+ await this.getFiles(files, renderInfo);
} catch (e) {
- let errorMessage = e.message;
- renderErrorMessage(canvas, errorMessage);
- el.appendChild(canvas);
- return;
+ return this.renderErrorMessage(e.message, canvas, el);
+ }
+ if (files.length === 0) {
+ return this.renderErrorMessage(
+ "No markdown files found in folder",
+ canvas,
+ el
+ );
}
// console.log(files);
@@ -154,11 +301,15 @@ export default class Tracker extends Plugin {
// Use own settings panel for now
// Collecting data to dataMap first
- let minDate = window.moment("");
- let maxDate = window.moment("");
- let fileCounter = 0;
-
let dataMap: DataMap = new Map(); // {strDate: [query: value, ...]}
+ let processInfo = new CollectingProcessInfo();
+ processInfo.fileTotal = files.length;
+
+ // Store dates in local variables to ensure they're captured correctly in async closures
+ // This fixes the issue where dates become null inside async functions
+ const startDate = renderInfo.startDate ? renderInfo.startDate.clone() : null;
+ const endDate = renderInfo.endDate ? renderInfo.endDate.clone() : null;
+
// Collect data from files, each file has one data point for each query
const loopFilePromises = files.map(async (file) => {
// console.log(file.basename);
@@ -169,8 +320,12 @@ export default class Tracker extends Plugin {
let target = q.getTarget();
if (
type === SearchType.Frontmatter ||
+ type === SearchType.FrontmatterExists ||
+ type === SearchType.FrontmatterList ||
type === SearchType.Tag ||
- type === SearchType.Wiki
+ type === SearchType.Wiki ||
+ type === SearchType.WikiLink ||
+ type === SearchType.WikiDisplay
) {
return true;
}
@@ -213,6 +368,7 @@ export default class Tracker extends Plugin {
let skipThisFile = false;
// console.log(renderInfo.xDataset);
for (let xDatasetId of renderInfo.xDataset) {
+ // console.log(`xDatasetId: ${xDatasetId}`);
if (!xValueMap.has(xDatasetId)) {
let xDate = window.moment("");
if (xDatasetId === -1) {
@@ -276,43 +432,53 @@ export default class Tracker extends Plugin {
if (!xDate.isValid()) {
// console.log("Invalid xDate");
skipThisFile = true;
+ processInfo.fileNotInFormat++;
} else {
- // console.log("file " + file.basename + " accepted");
- if (renderInfo.startDate !== null) {
- if (xDate < renderInfo.startDate) {
+ // Date filtering: only include files within the specified date range
+ // Use isBefore/isAfter with 'day' granularity for clear, readable date comparisons
+ // Use local variables (startDate/endDate) captured before async loop to avoid closure issues
+ if (startDate !== null && startDate.isValid()) {
+ // Skip files with dates before the start date
+ if (xDate.isBefore(startDate, 'day')) {
skipThisFile = true;
+ processInfo.fileOutOfDateRange++;
}
}
- if (renderInfo.endDate !== null) {
- if (xDate > renderInfo.endDate) {
+ if (endDate !== null && endDate.isValid()) {
+ // Skip files with dates after the end date
+ if (xDate.isAfter(endDate, 'day')) {
skipThisFile = true;
+ processInfo.fileOutOfDateRange++;
}
}
}
if (!skipThisFile) {
+ processInfo.gotAnyValidXValue ||= true;
xValueMap.set(
xDatasetId,
helper.dateToStr(xDate, renderInfo.dateFormat)
);
- fileCounter++;
+ processInfo.fileAvailable++;
// Get min/max date
- if (fileCounter == 1) {
- minDate = xDate.clone();
- maxDate = xDate.clone();
+ if (processInfo.fileAvailable == 1) {
+ processInfo.minDate = xDate.clone();
+ processInfo.maxDate = xDate.clone();
} else {
- if (xDate < minDate) {
- minDate = xDate.clone();
+ if (xDate < processInfo.minDate) {
+ processInfo.minDate = xDate.clone();
}
- if (xDate > maxDate) {
- maxDate = xDate.clone();
+ if (xDate > processInfo.maxDate) {
+ processInfo.maxDate = xDate.clone();
}
}
}
}
}
- if (skipThisFile) return;
+ if (skipThisFile) {
+ return;
+ }
// console.log(xValueMap);
// console.log(`minDate: ${minDate}`);
// console.log(`maxDate: ${maxDate}`);
@@ -321,74 +487,115 @@ export default class Tracker extends Plugin {
let yDatasetQueries = renderInfo.queries.filter((q) => {
return q.getType() !== SearchType.Table && !q.usedAsXDataset;
});
+ // console.log(yDatasetQueries);
+
const loopQueryPromises = yDatasetQueries.map(async (query) => {
// Get xValue from file if xDataset assigned
// if (renderInfo.xDataset !== null)
// let xDatasetId = renderInfo.xDataset;
+ // console.log(query);
// console.log("Search frontmatter tags");
if (fileCache && query.getType() === SearchType.Tag) {
// Add frontmatter tags, allow simple tag only
- collecting.collectDataFromFrontmatterTag(
+ let gotAnyValue = collecting.collectDataFromFrontmatterTag(
fileCache,
query,
renderInfo,
dataMap,
xValueMap
);
+ processInfo.gotAnyValidYValue ||= gotAnyValue;
} // Search frontmatter tags
+ // Search frontmatter list membership
+ if (fileCache && query.getType() === SearchType.FrontmatterList) {
+ let gotAnyValue = collecting.collectDataFromFrontmatterList(
+ fileCache,
+ query,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+ processInfo.gotAnyValidYValue ||= gotAnyValue;
+ }
+
// console.log("Search frontmatter keys");
if (
fileCache &&
query.getType() === SearchType.Frontmatter &&
query.getTarget() !== "tags"
) {
- collecting.collectDataFromFrontmatterKey(
+ let gotAnyValue = collecting.collectDataFromFrontmatterKey(
fileCache,
query,
renderInfo,
dataMap,
xValueMap
);
+ processInfo.gotAnyValidYValue ||= gotAnyValue;
} // console.log("Search frontmatter keys");
+ // console.log("Search frontmatter exists");
+ if (
+ fileCache &&
+ query.getType() === SearchType.FrontmatterExists &&
+ query.getTarget() !== "tags"
+ ) {
+ let gotAnyValue = collecting.collectDataFromFrontmatterExists(
+ fileCache,
+ query,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+ processInfo.gotAnyValidYValue ||= gotAnyValue;
+ } // console.log("Search frontmatter exists");
+
// console.log("Search wiki links");
- if (fileCache && query.getType() === SearchType.Wiki) {
- collecting.collectDataFromWiki(
+ if (
+ fileCache &&
+ (query.getType() === SearchType.Wiki ||
+ query.getType() === SearchType.WikiLink ||
+ query.getType() === SearchType.WikiDisplay)
+ ) {
+ let gotAnyValue = collecting.collectDataFromWiki(
fileCache,
query,
renderInfo,
dataMap,
xValueMap
);
+ processInfo.gotAnyValidYValue ||= gotAnyValue;
}
// console.log("Search inline tags");
if (content && query.getType() === SearchType.Tag) {
- collecting.collectDataFromInlineTag(
+ let gotAnyValue = collecting.collectDataFromInlineTag(
content,
query,
renderInfo,
dataMap,
xValueMap
);
+ processInfo.gotAnyValidYValue ||= gotAnyValue;
} // Search inline tags
// console.log("Search Text");
if (content && query.getType() === SearchType.Text) {
- collecting.collectDataFromText(
+ let gotAnyValue = collecting.collectDataFromText(
content,
query,
renderInfo,
dataMap,
xValueMap
);
+ processInfo.gotAnyValidYValue ||= gotAnyValue;
} // Search text
// console.log("Search FileMeta");
if (query.getType() === SearchType.FileMeta) {
- collecting.collectDataFromFileMeta(
+ let gotAnyValue = collecting.collectDataFromFileMeta(
file,
content,
query,
@@ -396,17 +603,19 @@ export default class Tracker extends Plugin {
dataMap,
xValueMap
);
+ processInfo.gotAnyValidYValue ||= gotAnyValue;
} // Search FileMeta
// console.log("Search dvField");
if (content && query.getType() === SearchType.dvField) {
- collecting.collectDataFromDvField(
+ let gotAnyValue = collecting.collectDataFromDvField(
content,
query,
renderInfo,
dataMap,
xValueMap
);
+ processInfo.gotAnyValidYValue ||= gotAnyValue;
} // search dvField
// console.log("Search Task");
@@ -416,13 +625,14 @@ export default class Tracker extends Plugin {
query.getType() === SearchType.TaskDone ||
query.getType() === SearchType.TaskNotDone)
) {
- collecting.collectDataFromTask(
+ let gotAnyValue = collecting.collectDataFromTask(
content,
query,
renderInfo,
dataMap,
xValueMap
);
+ processInfo.gotAnyValidYValue ||= gotAnyValue;
} // search Task
});
await Promise.all(loopQueryPromises);
@@ -431,13 +641,178 @@ export default class Tracker extends Plugin {
// console.log(dataMap);
// Collect data from a file, one file contains full dataset
+ await this.collectDataFromTable(dataMap, renderInfo, processInfo);
+ if (processInfo.errorMessage) {
+ return this.renderErrorMessage(
+ processInfo.errorMessage,
+ canvas,
+ el
+ );
+ }
+ // console.log(minDate);
+ // console.log(maxDate);
+ // console.log(dataMap);
+
+ // Check date range
+ // minDate and maxDate are collected without knowing startDate and endDate
+ // console.log(`fileTotal: ${processInfo.fileTotal}`);
+ // console.log(`fileAvailable: ${processInfo.fileAvailable}`);
+ // console.log(`fileNotInFormat: ${processInfo.fileNotInFormat}`);
+ // console.log(`fileOutOfDateRange: ${processInfo.fileOutOfDateRange}`);
+ let dateErrorMessage = "";
+ if (
+ !processInfo.minDate.isValid() ||
+ !processInfo.maxDate.isValid() ||
+ processInfo.fileAvailable === 0 ||
+ !processInfo.gotAnyValidXValue
+ ) {
+ dateErrorMessage = `No valid date as X value found in notes`;
+ if (processInfo.fileOutOfDateRange > 0) {
+ dateErrorMessage += `\n${processInfo.fileOutOfDateRange} files are out of the date range.`;
+ }
+ if (processInfo.fileNotInFormat) {
+ dateErrorMessage += `\n${processInfo.fileNotInFormat} files are not in the right format.`;
+ }
+ }
+ if (renderInfo.startDate === null && renderInfo.endDate === null) {
+ // No date arguments
+ renderInfo.startDate = processInfo.minDate.clone();
+ renderInfo.endDate = processInfo.maxDate.clone();
+ } else if (
+ renderInfo.startDate !== null &&
+ renderInfo.endDate === null
+ ) {
+ if (renderInfo.startDate < processInfo.maxDate) {
+ renderInfo.endDate = processInfo.maxDate.clone();
+ } else {
+ dateErrorMessage = "Invalid date range";
+ }
+ } else if (
+ renderInfo.endDate !== null &&
+ renderInfo.startDate === null
+ ) {
+ if (renderInfo.endDate > processInfo.minDate) {
+ renderInfo.startDate = processInfo.minDate.clone();
+ } else {
+ dateErrorMessage = "Invalid date range";
+ }
+ } else {
+ // startDate and endDate are valid
+ if (
+ (renderInfo.startDate < processInfo.minDate &&
+ renderInfo.endDate < processInfo.minDate) ||
+ (renderInfo.startDate > processInfo.maxDate &&
+ renderInfo.endDate > processInfo.maxDate)
+ ) {
+ dateErrorMessage = "Invalid date range";
+ }
+ }
+ if (dateErrorMessage) {
+ return this.renderErrorMessage(dateErrorMessage, canvas, el);
+ }
+ // console.log(renderInfo.startDate);
+ // console.log(renderInfo.endDate);
+
+ if (!processInfo.gotAnyValidYValue) {
+ return this.renderErrorMessage(
+ "No valid Y value found in notes",
+ canvas,
+ el
+ );
+ }
+
+ // Reshape data for rendering
+ let datasets = new Datasets(renderInfo.startDate, renderInfo.endDate);
+ for (let query of renderInfo.queries) {
+ // We still create a dataset for xDataset,
+ // to keep the sequence and order of targets
+ let dataset = datasets.createDataset(query, renderInfo);
+ // Add number of targets to the dataset
+ // Number of targets has been accumulated while collecting data
+ dataset.addNumTargets(query.getNumTargets());
+ for (
+ let curDate = renderInfo.startDate.clone();
+ curDate <= renderInfo.endDate;
+ curDate.add(1, "days")
+ ) {
+ // console.log(curDate);
+
+ // dataMap --> {date: [query: value, ...]}
+ if (
+ dataMap.has(
+ helper.dateToStr(curDate, renderInfo.dateFormat)
+ )
+ ) {
+ let queryValuePairs = dataMap
+ .get(helper.dateToStr(curDate, renderInfo.dateFormat))
+ .filter(function (pair) {
+ return pair.query.equalTo(query);
+ });
+ if (queryValuePairs.length > 0) {
+ // Merge values of the same day same query
+ let value = null;
+ for (
+ let indPair = 0;
+ indPair < queryValuePairs.length;
+ indPair++
+ ) {
+ let collected = queryValuePairs[indPair].value;
+ if (
+ Number.isNumber(collected) &&
+ !Number.isNaN(collected)
+ ) {
+ if (value === null) {
+ value = collected;
+ } else {
+ value += collected;
+ }
+ }
+ }
+ // console.log(hasValue);
+ // console.log(value);
+ if (value !== null) {
+ dataset.setValue(curDate, value);
+ }
+ }
+ }
+ }
+ }
+ renderInfo.datasets = datasets;
+ // console.log(renderInfo.datasets);
+
+ let retRender = rendering.render(canvas, renderInfo);
+ if (typeof retRender === "string") {
+ return this.renderErrorMessage(retRender, canvas, el);
+ }
+
+ el.appendChild(canvas);
+ }
+
+ // TODO: remove this.app and move to collecting.ts
+ async collectDataFromTable(
+ dataMap: DataMap,
+ renderInfo: RenderInfo,
+ processInfo: CollectingProcessInfo
+ ) {
+ // console.log("collectDataFromTable");
+
let tableQueries = renderInfo.queries.filter(
(q) => q.getType() === SearchType.Table
);
+ // console.log(tableQueries);
// Separate queries by tables and xDatasets/yDatasets
let tables: Array = [];
+ let tableFileNotFound = false;
for (let query of tableQueries) {
let filePath = query.getParentTarget();
+ let file = this.app.vault.getAbstractFileByPath(
+ normalizePath(filePath + ".md")
+ );
+ if (!file || !(file instanceof TFile)) {
+ tableFileNotFound = true;
+ break;
+ }
+
let tableIndex = query.getAccessor();
let isX = query.usedAsXDataset;
@@ -462,9 +837,18 @@ export default class Tracker extends Plugin {
}
// console.log(tables);
+ if (tableFileNotFound) {
+ processInfo.errorMessage = "File containing tables not found";
+ return;
+ }
+
for (let tableData of tables) {
//extract xDataset from query
let xDatasetQuery = tableData.xDataset;
+ if (!xDatasetQuery) {
+ // missing xDataset
+ continue;
+ }
let yDatasetQueries = tableData.yDatasets;
let filePath = xDatasetQuery.getParentTarget();
let tableIndex = xDatasetQuery.getAccessor();
@@ -476,7 +860,7 @@ export default class Tracker extends Plugin {
normalizePath(filePath)
);
if (file && file instanceof TFile) {
- fileCounter++;
+ processInfo.fileAvailable++;
let content = await this.app.vault.adapter.read(file.path);
// console.log(content);
@@ -550,15 +934,18 @@ export default class Tracker extends Plugin {
if (date.isValid()) {
xValues.push(date);
- if (!minDate.isValid() && !maxDate.isValid()) {
- minDate = date.clone();
- maxDate = date.clone();
+ if (
+ !processInfo.minDate.isValid() &&
+ !processInfo.maxDate.isValid()
+ ) {
+ processInfo.minDate = date.clone();
+ processInfo.maxDate = date.clone();
} else {
- if (date < minDate) {
- minDate = date.clone();
+ if (date < processInfo.minDate) {
+ processInfo.minDate = date.clone();
}
- if (date > maxDate) {
- maxDate = date.clone();
+ if (date > processInfo.maxDate) {
+ processInfo.maxDate = date.clone();
}
}
} else {
@@ -571,11 +958,16 @@ export default class Tracker extends Plugin {
}
// console.log(xValues);
- if (xValues.every((v) => v === null)) {
- let errorMessage = "No valid X value found";
- renderErrorMessage(canvas, errorMessage);
- el.appendChild(canvas);
+ if (
+ xValues.every((v) => {
+ return v === null;
+ })
+ ) {
+ processInfo.errorMessage =
+ "No valid date as X value found in table";
return;
+ } else {
+ processInfo.gotAnyValidXValue ||= true;
}
// get y data
@@ -591,14 +983,24 @@ export default class Tracker extends Plugin {
if (columnOfInterest < dataRowSplitted.length) {
let data = dataRowSplitted[columnOfInterest].trim();
let splitted = data.split(yDatasetQuery.getSeparator());
+ // console.log(splitted);
if (!splitted) continue;
if (splitted.length === 1) {
- let value = parseFloat(splitted[0]);
- if (Number.isNumber(value)) {
+ let retParse = helper.parseFloatFromAny(
+ splitted[0],
+ renderInfo.textValueMap
+ );
+ // console.log(retParse);
+ if (retParse.value !== null) {
+ if (retParse.type === ValueType.Time) {
+ yDatasetQuery.valueType = ValueType.Time;
+ }
+ let value = retParse.value;
if (
indLine < xValues.length &&
xValues[indLine]
) {
+ processInfo.gotAnyValidYValue ||= true;
collecting.addToDataMap(
dataMap,
helper.dateToStr(
@@ -617,12 +1019,22 @@ export default class Tracker extends Plugin {
let value = null;
let splittedPart =
splitted[yDatasetQuery.getAccessor(2)].trim();
- value = parseFloat(splittedPart);
- if (Number.isNumber(value)) {
+ // console.log(splittedPart);
+ let retParse = helper.parseFloatFromAny(
+ splittedPart,
+ renderInfo.textValueMap
+ );
+ // console.log(retParse);
+ if (retParse.value !== null) {
+ if (retParse.type === ValueType.Time) {
+ yDatasetQuery.valueType = ValueType.Time;
+ }
+ value = retParse.value;
if (
indLine < xValues.length &&
xValues[indLine]
) {
+ processInfo.gotAnyValidYValue ||= true;
collecting.addToDataMap(
dataMap,
helper.dateToStr(
@@ -641,144 +1053,13 @@ export default class Tracker extends Plugin {
} // Loop over tableLines
}
}
-
- if (fileCounter === 0) {
- let errorMessage =
- "No notes found under the given search condition";
- renderErrorMessage(canvas, errorMessage);
- el.appendChild(canvas);
- return;
- }
- // console.log(minDate);
- // console.log(maxDate);
- // console.log(dataMap);
-
- // Check date range
- if (!minDate.isValid() || !maxDate.isValid()) {
- let errorMessage = "Invalid date range";
- renderErrorMessage(canvas, errorMessage);
- el.appendChild(canvas);
- return;
- }
- if (renderInfo.startDate === null && renderInfo.endDate === null) {
- // No date arguments
- renderInfo.startDate = minDate.clone();
- renderInfo.endDate = maxDate.clone();
- } else if (
- renderInfo.startDate !== null &&
- renderInfo.endDate === null
- ) {
- if (renderInfo.startDate < maxDate) {
- renderInfo.endDate = maxDate.clone();
- } else {
- let errorMessage = "Invalid date range";
- renderErrorMessage(canvas, errorMessage);
- el.appendChild(canvas);
- return;
- }
- } else if (
- renderInfo.endDate !== null &&
- renderInfo.startDate === null
- ) {
- if (renderInfo.endDate > minDate) {
- renderInfo.startDate = minDate.clone();
- } else {
- let errorMessage = "Invalid date range";
- renderErrorMessage(canvas, errorMessage);
- el.appendChild(canvas);
- return;
- }
- } else {
- // startDate and endDate are valid
- if (
- (renderInfo.startDate < minDate &&
- renderInfo.endDate < minDate) ||
- (renderInfo.startDate > maxDate && renderInfo.endDate > maxDate)
- ) {
- let errorMessage = "Invalid date range";
- renderErrorMessage(canvas, errorMessage);
- el.appendChild(canvas);
- return;
- }
- }
- // console.log(renderInfo.startDate);
- // console.log(renderInfo.endDate);
-
- // Reshape data for rendering
- let datasets = new Datasets(renderInfo.startDate, renderInfo.endDate);
- for (let query of renderInfo.queries) {
- // We still create a dataset for xDataset,
- // to keep the sequence and order of targets
- let dataset = datasets.createDataset(query, renderInfo);
- // Add number of targets to the dataset
- // Number of targets has been accumulated while collecting data
- dataset.addNumTargets(query.getNumTargets());
- for (
- let curDate = renderInfo.startDate.clone();
- curDate <= renderInfo.endDate;
- curDate.add(1, "days")
- ) {
- // console.log(curDate);
-
- // dataMap --> {date: [query: value, ...]}
- if (
- dataMap.has(
- helper.dateToStr(curDate, renderInfo.dateFormat)
- )
- ) {
- let queryValuePairs = dataMap
- .get(helper.dateToStr(curDate, renderInfo.dateFormat))
- .filter(function (pair) {
- return pair.query.equalTo(query);
- });
- if (queryValuePairs.length > 0) {
- // Merge values of the same day same query
- let value = null;
- for (
- let indPair = 0;
- indPair < queryValuePairs.length;
- indPair++
- ) {
- let collected = queryValuePairs[indPair].value;
- if (
- Number.isNumber(collected) &&
- !Number.isNaN(collected)
- ) {
- if (value === null) {
- value = collected;
- } else {
- value += collected;
- }
- }
- }
- // console.log(hasValue);
- // console.log(value);
- if (value !== null) {
- dataset.setValue(curDate, value);
- }
- }
- }
- }
- }
- renderInfo.datasets = datasets;
- // console.log(renderInfo.datasets);
-
- let result = render(canvas, renderInfo);
- if (typeof result === "string") {
- let errorMessage = result;
- renderErrorMessage(canvas, errorMessage);
- el.appendChild(canvas);
- return;
- }
-
- el.appendChild(canvas);
}
getEditor(): Editor {
return this.app.workspace.getActiveViewOfType(MarkdownView).editor;
}
- addCodeBlock(outputType: OutputType): void {
+ addCodeBlock(outputType: GraphType): void {
const currentView = this.app.workspace.activeLeaf.view;
if (!(currentView instanceof MarkdownView)) {
@@ -787,7 +1068,7 @@ export default class Tracker extends Plugin {
let codeblockToInsert = "";
switch (outputType) {
- case OutputType.Line:
+ case GraphType.Line:
codeblockToInsert = `\`\`\` tracker
searchType: tag
searchTarget: tagName
@@ -800,7 +1081,7 @@ line:
yAxisLabel: Value
\`\`\``;
break;
- case OutputType.Bar:
+ case GraphType.Bar:
codeblockToInsert = `\`\`\` tracker
searchType: tag
searchTarget: tagName
@@ -813,7 +1094,7 @@ bar:
yAxisLabel: Value
\`\`\``;
break;
- case OutputType.Summary:
+ case GraphType.Summary:
codeblockToInsert = `\`\`\` tracker
searchType: tag
searchTarget: tagName
@@ -821,7 +1102,7 @@ folder: /
startDate:
endDate:
summary:
- template: "Average value of tagName is {{average}}"
+ template: "Average value of tagName is {{average()}}"
style: "color:white;"
\`\`\``;
break;
diff --git a/src/month.ts b/src/month.ts
index c1de5825..4d42cfc8 100644
--- a/src/month.ts
+++ b/src/month.ts
@@ -6,9 +6,10 @@ import {
MonthInfo,
Dataset,
Size,
+ ThresholdType,
Transform,
ChartElements,
- OutputType,
+ GraphType,
ValueType,
} from "./data";
import * as helper from "./helper";
@@ -30,6 +31,36 @@ interface DayInfo {
showCircle: boolean;
streakIn: boolean;
streakOut: boolean;
+ curStreakCount: number;
+ annotation: string;
+}
+
+function setChartScale(
+ _canvas: HTMLElement,
+ chartElements: ChartElements,
+ renderInfo: RenderInfo
+) {
+ let canvas = d3.select(_canvas);
+ let svg = chartElements.svg;
+ let svgWidth = parseFloat(svg.attr("width"));
+ let svgHeight = parseFloat(svg.attr("height"));
+ svg.attr("width", null)
+ .attr("height", null)
+ .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`)
+ .attr("preserveAspectRatio", "xMidYMid meet");
+
+ if (renderInfo.fitPanelWidth) {
+ canvas.style("width", "100%");
+ } else {
+ canvas.style(
+ "width",
+ (svgWidth * renderInfo.fixedScale).toString() + "px"
+ );
+ canvas.style(
+ "height",
+ (svgHeight * renderInfo.fixedScale).toString() + "px"
+ );
+ }
}
function toNextDataset(renderInfo: RenderInfo, monthInfo: MonthInfo): boolean {
@@ -182,7 +213,7 @@ function renderMonthHeader(
let curDaysInMonth = curMonthDate.daysInMonth(); // 28~31
let curYear = curMonthDate.year();
- let maxDayTextSize = helper.measureTextSize("30", "tracker-axis-label");
+ let maxDayTextSize = helper.measureTextSize("30", "tracker-month-label");
let cellSize =
Math.max(maxDayTextSize.width, maxDayTextSize.height) * ratioCellToText;
let dotRadius = ((cellSize / ratioCellToText) * ratioDotToText) / 2.0;
@@ -266,21 +297,31 @@ function renderMonthHeader(
headerHeight += headerYearSize.height;
+ if (monthInfo.mode === "annotation" && monthInfo.showAnnotationOfAllTargets && monthInfo.dataset.length > 1) {
+ datasetName = "All Targets"
+ }
+
// dataset rotator
let datasetNameSize = helper.measureTextSize(
datasetName,
"tracker-month-title-rotator"
);
+
let datasetRotator = headerGroup
.append("text")
.text(datasetName)
.attr(
"transform",
- "translate(" + 3.5 * cellSize + "," + datasetNameSize.height + ")"
+ "translate(" +
+ 3.5 * cellSize +
+ "," +
+ datasetNameSize.height +
+ ")"
)
.attr("class", "tracker-month-title-rotator")
- .style("cursor", "pointer")
- .on("click", function (event: any) {
+ .style("cursor", "pointer");
+ if (!monthInfo.showAnnotationOfAllTargets || monthInfo.mode !== "annotation") {
+ datasetRotator.on("click", function (event: any) {
// show next target
if (toNextDataset(renderInfo, monthInfo)) {
// clear circles
@@ -295,6 +336,7 @@ function renderMonthHeader(
);
}
});
+ }
chartElements["rotator"] = datasetRotator;
// value monitor
@@ -353,7 +395,7 @@ function renderMonthHeader(
let arrowRight = headerGroup
.append("text")
.text(">") // pivot at center
- .attr("id", "arrowLeft")
+ .attr("id", "arrowRight")
.attr(
"transform",
"translate(" +
@@ -405,7 +447,9 @@ function renderMonthHeader(
// week day names
let weekdayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
- if (monthInfo.startWeekOn.toLowerCase() === "mon") {
+ const startDayIndex = helper.getDayIndex(monthInfo.startWeekOn);
+ // Rotate array so startWeekOn is first
+ for (let i = 0; i < startDayIndex; i++) {
weekdayNames.push(weekdayNames.shift());
}
let weekdayNameSize = helper.measureTextSize(
@@ -476,22 +520,35 @@ function renderMonthDays(
) {
// console.log("renderMonthDays");
// console.log(renderInfo);
+ // console.log(monthInfo);
if (!renderInfo || !monthInfo) return;
+ let mode = monthInfo.mode;
+ if (mode !== "circle" && mode !== "annotation") {
+ return "Unknown month view mode";
+ }
+
let curDatasetId = monthInfo.selectedDataset;
if (curDatasetId === null) return;
let dataset = renderInfo.datasets.getDatasetById(curDatasetId);
if (!dataset) return;
+ // console.log(dataset);
let curDatasetIndex = monthInfo.dataset.findIndex((id) => {
return id === curDatasetId;
});
+ if (curDatasetId < 0) curDatasetIndex = 0;
let threshold = monthInfo.threshold[curDatasetIndex];
+ let thresholdType = monthInfo.thresholdType[curDatasetIndex];
+ if(logToConsole){
+ console.log(`threshold: ${threshold}, thresholdType: ${thresholdType}`);
+ }
+
let curMonth = curMonthDate.month(); // 0~11
let curDaysInMonth = curMonthDate.daysInMonth(); // 28~31
- let maxDayTextSize = helper.measureTextSize("30", "tracker-axis-label");
+ let maxDayTextSize = helper.measureTextSize("30", "tracker-month-label");
let cellSize =
Math.max(maxDayTextSize.width, maxDayTextSize.height) * ratioCellToText;
let dotRadius = ((cellSize / ratioCellToText) * ratioDotToText) / 2.0;
@@ -517,43 +574,66 @@ function renderMonthDays(
// Start and end
const monthStartDate = curMonthDate.clone().startOf("month");
- let startDate = monthStartDate
- .clone()
- .subtract(monthStartDate.day(), "days");
- if (monthInfo.startWeekOn.toLowerCase() === "mon") {
- startDate = startDate.add(1, "days");
- }
+ const startDayIndex = helper.getDayIndex(monthInfo.startWeekOn);
+ const currentDayOfWeek = monthStartDate.day(); // 0=Sunday, 1=Monday, etc.
+ // Calculate days to subtract to get to the start of the week
+ const daysToSubtract = (currentDayOfWeek - startDayIndex + 7) % 7;
+ let startDate = monthStartDate.clone().subtract(daysToSubtract, "days");
const monthEndDate = curMonthDate.clone().endOf("month");
- let endDate = monthEndDate.clone().add(7 - monthEndDate.day() - 1, "days");
- if (monthInfo.startWeekOn.toLowerCase() === "mon") {
- endDate = endDate.add(1, "days");
- }
+ const endDayOfWeek = monthEndDate.day();
+ // Calculate days to add to complete the week (get to Saturday of that week)
+ const daysToAdd = (6 - endDayOfWeek + startDayIndex) % 7;
+ let endDate = monthEndDate.clone().add(daysToAdd, "days");
const dataStartDate = dataset.getStartDate();
const dataEndDate = dataset.getEndDate();
// console.log(monthStartDate.format("YYYY-MM-DD"));
// console.log(startDate.format("YYYY-MM-DD"));
+ // annotations
+ let showAnnotation = monthInfo.showAnnotation;
+ let annotations = monthInfo.annotation;
+ let curAnnotation = annotations[curDatasetIndex];
+ let showAnnotationOfAllTargets = monthInfo.showAnnotationOfAllTargets;
+
// Prepare data for graph
let daysInMonthView: Array = [];
let indCol = 0;
let indRow = 0;
let ind = 0;
+ let curStreakCount = 0;
+
+ let streakStartDate = startDate.clone().subtract(1, "days");
+ while(curStreakCount < 28) {
+ let curValue = dataset.getValue(streakStartDate);
+ if(curValue != null && ((thresholdType === ThresholdType.LessThan && curValue < threshold)
+ || (thresholdType === ThresholdType.GreaterThan && curValue > threshold))) {
+ curStreakCount++;
+ streakStartDate = streakStartDate.subtract(1, "days");
+ } else {
+ break;
+ }
+ }
+
for (
let curDate = startDate.clone();
curDate <= endDate;
curDate.add(1, "days")
) {
- if (monthInfo.startWeekOn.toLowerCase() === "mon") {
- indCol = curDate.day() - 1;
- if (indCol < 0) {
- indCol = 6;
- }
- indRow = Math.floor(ind / 7);
- } else {
- indCol = curDate.day(); // 0~6
- indRow = Math.floor(ind / 7);
+ // not sure why we need to do this to stablize the date
+ // sometimes, curValue is wrong without doing this
+ curDate = helper.strToDate(
+ helper.dateToStr(curDate, renderInfo.dateFormat),
+ renderInfo.dateFormat
+ );
+ if (curDate.format("YYYY-MM-DD") === "2021-09-13") {
+ logToConsole = false; // Change this to do dubugging
}
+ const dayOfWeek = curDate.day(); // 0=Sunday, 1=Monday, etc.
+ // Calculate column: shift by startDayIndex
+ indCol = (dayOfWeek - startDayIndex + 7) % 7;
+ indRow = Math.floor(ind / 7);
+
// is this day in this month
let isInThisMonth = true;
if (
@@ -573,30 +653,22 @@ function renderMonthDays(
isOutOfDataRange = false;
}
- // curValue
- let curValue = dataset.getValue(curDate);
+ const curValue = dataset.getValue(curDate);
+ if (logToConsole) {
+ console.log(dataset);
+ console.log(helper.dateToStr(curDate, renderInfo.dateFormat));
+ console.log(curValue);
+ }
- // showCircle
- let showCircle = false;
- if (!monthInfo.circleColorByValue) {
- // shown or not shown
- if (curValue !== null) {
- if (curValue > threshold) {
- showCircle = true;
- }
- }
+
+ let showCircle = curValue != null;
+
+ if(thresholdType === ThresholdType.LessThan){
+ showCircle = showCircle && (curValue < threshold);
} else {
- if (!allowScaledValue) {
- if (curValue !== null) {
- if (curValue > threshold) {
- showCircle = true;
- }
- }
- } else {
- showCircle = true;
- }
+ showCircle = showCircle && (curValue > threshold);
}
-
+
// scaledValue
let scaledValue = null;
if (monthInfo.circleColorByValue) {
@@ -604,35 +676,71 @@ function renderMonthDays(
scaledValue = (curValue - yMin) / (yMax - yMin);
}
}
- // console.log(yMin);
- // console.log(yMax);
- // console.log(scaledValue);
-
- // if (curDate.format("YYYY-MM-DD") === "2021-11-02") {
- // logToConsole = true;
- // }
+ if (logToConsole) {
+ console.log(yMin);
+ console.log(yMax);
+ console.log(scaledValue);
+ }
// streakIn and streakOut
let nextValue = dataset.getValue(curDate, 1);
let prevValue = dataset.getValue(curDate, -1);
let streakIn = false;
- if (curValue !== null && curValue > threshold) {
- if (prevValue !== null && prevValue > threshold) {
+ if (showCircle) {
+ if (prevValue !== null && ((thresholdType === ThresholdType.LessThan && prevValue
+ < threshold) || (thresholdType === ThresholdType.GreaterThan && prevValue > threshold))) {
streakIn = true;
- }
+ }
}
+
+ if(showCircle) {
+ curStreakCount != 28? curStreakCount++: curStreakCount;
+ }
+ else {
+ curStreakCount = 0;
+ }
+
let streakOut = false;
- if (curValue !== null && curValue > threshold) {
- if (nextValue !== null && nextValue > threshold) {
+ if (showCircle) {
+ if (nextValue !== null && ((thresholdType === ThresholdType.LessThan && nextValue
+ < threshold) || (thresholdType === ThresholdType.GreaterThan && nextValue > threshold))) {
streakOut = true;
}
}
- // if (logToConsole) {
- // console.log(`preValue: ${prevValue}, curValue: ${curValue}, nextValue: ${nextValue}`);
- // console.log(monthInfo.threshold);
- // console.log(`streakIn: ${streakIn}, streakOut: ${streakOut}`);
- // logToConsole = false;
- // }
+ if (logToConsole) {
+ console.log(
+ `preValue: ${prevValue}, curValue: ${curValue}, nextValue: ${nextValue}`
+ );
+ console.log(monthInfo.threshold);
+ console.log(`streakIn: ${streakIn}, streakOut: ${streakOut}`);
+ }
+
+ let textAnnotation = "";
+ if (showAnnotation) {
+ if (!showAnnotationOfAllTargets) {
+ if (showCircle) {
+ textAnnotation = curAnnotation;
+ }
+ } else {
+ for (let datasetId of monthInfo.dataset) {
+ let datasetIndex = monthInfo.dataset.findIndex((id) => {
+ return id === datasetId;
+ });
+ if (datasetIndex >= 0) {
+ let v = renderInfo.datasets
+ .getDatasetById(datasetId)
+ .getValue(curDate);
+
+ let ttype = monthInfo.thresholdType[datasetIndex];
+ let t = monthInfo.threshold[datasetIndex];
+ if (v !== null && ((ttype === ThresholdType.LessThan && v < t) ||
+ (ttype == ThresholdType.GreaterThan && v > t))) {
+ textAnnotation += annotations[datasetIndex];
+ }
+ }
+ }
+ }
+ }
daysInMonthView.push({
date: helper.dateToStr(curDate, renderInfo.dateFormat),
@@ -646,9 +754,16 @@ function renderMonthDays(
showCircle: showCircle,
streakIn: streakIn,
streakOut: streakOut,
+ curStreakCount: curStreakCount,
+ annotation: textAnnotation,
});
ind++;
+
+ // Disable logging starts at the beginning of each loop
+ if (logToConsole) {
+ logToConsole = false;
+ }
}
// console.log(daysInMonthView);
// console.log(daysInMonthView.filter(function (d: DayInfo) {
@@ -667,7 +782,7 @@ function renderMonthDays(
.range([0, totalDayBlockWidth]);
// streak lines
- if (monthInfo.showStreak) {
+ if (mode === "circle" && monthInfo.showCircle && monthInfo.showStreak) {
let streakColor = "#69b3a2";
if (monthInfo.circleColor) {
streakColor = monthInfo.circleColor;
@@ -699,16 +814,20 @@ function renderMonthDays(
.attr("height", streakHeight)
.style("fill", function (d: DayInfo) {
if (d.showCircle) {
- if (!monthInfo.circleColorByValue) {
+ if (!(monthInfo.circleColorByValue || monthInfo.circleColorByStreak)) {
return streakColor;
}
- if (d.scaledValue !== null) {
+ if (monthInfo.circleColorByValue && d.scaledValue !== null) {
return d3.interpolateLab(
"white",
streakColor
)(d.scaledValue * 0.8 + 0.2);
+ } else if(monthInfo.circleColorByStreak && d.curStreakCount > 0){
+ return d3.interpolateLab( "white", streakColor)(
+ Math.log10(d.curStreakCount)/Math.log10(28) * 0.6 + 0.4
+ );
} else {
- return streakColor;
+ return "none";
}
}
return "none";
@@ -746,16 +865,20 @@ function renderMonthDays(
.attr("height", streakHeight)
.style("fill", function (d: DayInfo) {
if (d.showCircle) {
- if (!monthInfo.circleColorByValue) {
+ if (!(monthInfo.circleColorByValue || monthInfo.circleColorByStreak)) {
return streakColor;
}
- if (d.scaledValue !== null) {
+ if (monthInfo.circleColorByValue && d.scaledValue !== null) {
return d3.interpolateLab(
"white",
streakColor
)(d.scaledValue * 0.8 + 0.2);
+ } else if(monthInfo.circleColorByStreak && d.curStreakCount > 0){
+ return d3.interpolateLab("white", streakColor)(
+ Math.log10(d.curStreakCount)/Math.log10(28) * 0.6 + 0.4
+ );
} else {
- return streakColor;
+ return "none";
}
}
return "none";
@@ -778,7 +901,7 @@ function renderMonthDays(
} else if (monthInfo.color) {
circleColor = monthInfo.color;
}
- if (monthInfo.showCircle) {
+ if (mode === "circle" && monthInfo.showCircle) {
let dots = chartElements.dataArea
.selectAll("dot")
.data(daysInMonthView)
@@ -793,19 +916,20 @@ function renderMonthDays(
})
.style("fill", function (d: DayInfo) {
if (d.showCircle) {
- if (!monthInfo.circleColorByValue) {
+ if (!(monthInfo.circleColorByValue || monthInfo.circleColorByStreak)) {
return circleColor;
}
- if (d.scaledValue !== null) {
- let scaledColor = d3.interpolateLab(
+ if (monthInfo.circleColorByValue && d.scaledValue !== null) {
+ return d3.interpolateLab(
"white",
circleColor
- )(d.scaledValue * 0.8 + 0.2);
- // console.log(d.scaledValue);
- // console.log(scaledColor);
- return scaledColor;
+ )(d.scaledValue * 0.8 + 0.2);
+ } else if(monthInfo.circleColorByStreak && d.curStreakCount > 0){
+ return d3.interpolateLab( "white", circleColor)(
+ Math.log10(d.curStreakCount)/Math.log10(28) * 0.6 + 0.4
+ );
} else {
- return circleColor;
+ return "none";
}
}
return "none";
@@ -824,7 +948,7 @@ function renderMonthDays(
// today rings
let today = helper.dateToStr(window.moment(), renderInfo.dateFormat);
- if (monthInfo.showTodayRing) {
+ if (mode === "circle" && monthInfo.showTodayRing) {
let todayRings = chartElements.dataArea
.selectAll("todayRing")
.data(
@@ -852,7 +976,7 @@ function renderMonthDays(
}
// selected rings
- if (monthInfo.showSelectedRing) {
+ if (mode === "circle" && monthInfo.showSelectedRing) {
let selectedRings = chartElements.dataArea
.selectAll("selectedRing")
.data(daysInMonthView)
@@ -883,12 +1007,9 @@ function renderMonthDays(
return d.dayInMonth.toString();
})
.attr("transform", function (d: DayInfo) {
- let strTranslate =
- "translate(" +
- scale(d.col) +
- "," +
- (scale(d.row) + maxDayTextSize.height / 4) +
- ")";
+ let transX = scale(d.col);
+ let transY = scale(d.row) + maxDayTextSize.height / 4;
+ let strTranslate = "translate(" + transX + "," + transY + ")";
return strTranslate;
})
@@ -910,7 +1031,7 @@ function renderMonthDays(
.attr("valueType", function (d: DayInfo) {
return ValueType[dataset.valueType];
})
- .attr("class", "tracker-axis-label")
+ .attr("class", "tracker-month-label")
.on("click", function (event: any) {
// clear circles
clearSelection(chartElements, monthInfo);
@@ -942,6 +1063,29 @@ function renderMonthDays(
})
.style("cursor", "pointer");
+ // annotation
+ if (mode === "annotation" && showAnnotation) {
+ let dayAnnotation = chartElements.dataArea
+ .selectAll("dayAnnotation")
+ .data(daysInMonthView)
+ .enter()
+ .append("text")
+ .text(function (d: DayInfo) {
+ return d.annotation;
+ })
+ .attr("transform", function (d: DayInfo) {
+ let transX = scale(d.col);
+ let transY = scale(d.row) + maxDayTextSize.height / 4;
+ if (d.annotation) {
+ transY += dotRadius;
+ }
+ let strTranslate = "translate(" + transX + "," + transY + ")";
+
+ return strTranslate;
+ })
+ .attr("class", "tracker-month-annotation");
+ }
+
// Expand areas
let svgWidth = parseFloat(chartElements.svg.attr("width"));
let svgHeight = parseFloat(chartElements.svg.attr("height"));
@@ -991,6 +1135,8 @@ function refresh(
);
renderMonthDays(canvas, chartElements, renderInfo, monthInfo, curMonthDate);
+
+ setChartScale(canvas, chartElements, renderInfo);
}
export function renderMonth(
@@ -1024,15 +1170,20 @@ export function renderMonth(
let monthDate: Moment = null;
if (monthInfo.initMonth) {
- let initMonth = window.moment(monthInfo.initMonth, "YYYY-MM", true);
- // console.log(initMonth);
- if (initMonth.isValid()) {
- monthDate = initMonth;
- } else {
- return "Invalid initMonth";
+ monthDate = helper.getDateByDurationToToday(
+ monthInfo.initMonth,
+ renderInfo.dateFormat
+ );
+ if (!monthDate) {
+ let initMonth = window.moment(monthInfo.initMonth, "YYYY-MM", true);
+ // console.log(initMonth);
+ if (initMonth.isValid()) {
+ monthDate = initMonth;
+ } else {
+ return "Invalid initMonth";
+ }
}
} else {
- let today = window.moment();
monthDate = renderInfo.datasets.getDates().last();
}
if (!monthDate) return;
@@ -1040,4 +1191,6 @@ export function renderMonth(
renderMonthHeader(canvas, chartElements, renderInfo, monthInfo, monthDate);
renderMonthDays(canvas, chartElements, renderInfo, monthInfo, monthDate);
+
+ setChartScale(canvas, chartElements, renderInfo);
}
diff --git a/src/parsing.ts b/src/parsing.ts
index c4f8d962..c26c0102 100644
--- a/src/parsing.ts
+++ b/src/parsing.ts
@@ -7,17 +7,19 @@ import {
RenderInfo,
SummaryInfo,
Margin,
- OutputType,
+ GraphType,
LineInfo,
PieInfo,
MonthInfo,
HeatmapInfo,
BulletInfo,
Dataset,
+ CustomDatasetInfo,
+ AspectRatio,
+ ThresholdType
} from "./data";
import { TFolder, normalizePath } from "obsidian";
import { parseYaml } from "obsidian";
-import { getDailyNoteSettings } from "obsidian-daily-notes-interface";
import * as helper from "./helper";
function strToBool(str: string): boolean | null {
@@ -42,7 +44,11 @@ function validateSearchType(searchType: string): boolean {
searchType.toLowerCase() === "tag" ||
searchType.toLowerCase() === "text" ||
searchType.toLowerCase() === "frontmatter" ||
+ searchType.toLowerCase() === "frontmatter.exists" ||
+ searchType.toLowerCase() === "frontmatterlist" ||
searchType.toLowerCase() === "wiki" ||
+ searchType.toLowerCase() === "wiki.link" ||
+ searchType.toLowerCase() === "wiki.display" ||
searchType.toLowerCase() === "dvfield" ||
searchType.toLowerCase() === "table" ||
searchType.toLowerCase() === "filemeta" ||
@@ -67,6 +73,18 @@ function validateColor(color: string): boolean {
return true;
}
+function splitInputByComma(input: string) {
+ // Split string by ',' but not by '\,'
+ // let splitted = input.split(/(?lookbehind not support in Safari for now
+ const dummy = "::::::tracker::::::";
+ let temp = input.split("\\,").join(dummy);
+ let splitted = temp.split(",");
+ for (let ind = 0; ind < splitted.length; ind++) {
+ splitted[ind] = splitted[ind].split(dummy).join(",");
+ }
+ return splitted;
+}
+
function getBoolArrayFromInput(
name: string,
input: any,
@@ -132,7 +150,7 @@ function getBoolArrayFromInput(
}
}
} else if (typeof input === "string") {
- let splitted = input.split(",");
+ let splitted = splitInputByComma(input);
if (splitted.length > 1) {
if (splitted.length > numDataset) {
errorMessage = "Too many inputs for parameter '" + name + "'";
@@ -215,6 +233,8 @@ function getNumberArrayFromInput(
defaultValue: number,
allowNoValidValue: boolean
): Array | string {
+ // console.log("getNumberArrayFromInput");
+
let array: Array = [];
let errorMessage = "";
let numValidValue = 0;
@@ -273,7 +293,7 @@ function getNumberArrayFromInput(
}
}
} else if (typeof input === "string") {
- let splitted = input.split(",");
+ let splitted = splitInputByComma(input);
if (splitted.length > 1) {
if (splitted.length > numDataset) {
errorMessage = "Too many inputs for parameter '" + name + "'";
@@ -296,7 +316,7 @@ function getNumberArrayFromInput(
}
} else {
let currNum = helper.parseFloatFromAny(curr).value;
- if (Number.isNumber(currNum)) {
+ if (currNum !== null) {
array[ind] = currNum;
numValidValue++;
} else {
@@ -309,7 +329,7 @@ function getNumberArrayFromInput(
let last = helper.parseFloatFromAny(
splitted[input.length - 1].trim()
).value;
- if (numValidValue > 0 && Number.isNumber(last)) {
+ if (numValidValue > 0 && last !== null) {
array[ind] = last;
} else {
array[ind] = defaultValue;
@@ -321,7 +341,7 @@ function getNumberArrayFromInput(
// all defaultValue
} else {
let inputNum = helper.parseFloatFromAny(input).value;
- if (Number.isNumber(inputNum)) {
+ if (inputNum !== null) {
array[0] = inputNum;
numValidValue++;
for (let ind = 1; ind < array.length; ind++) {
@@ -357,6 +377,15 @@ function getNumberArrayFromInput(
return array;
}
+function getStringFromInput(input: any, defaultValue: string): string {
+ if (typeof input === "string") {
+ return helper.replaceImgTagByAlt(input);
+ } else if (typeof input === "number") {
+ return input.toString();
+ }
+ return defaultValue;
+}
+
function getStringArrayFromInput(
name: string,
input: any,
@@ -430,7 +459,7 @@ function getStringArrayFromInput(
}
}
} else if (typeof input === "string") {
- let splitted = input.split(",");
+ let splitted = splitInputByComma(input);
if (splitted.length > 1) {
if (splitted.length > numDataset) {
errorMessage = "Too many inputs for parameter '" + name + "'";
@@ -496,6 +525,25 @@ function getStringArrayFromInput(
}
}
}
+ } else if (typeof input === "number") {
+ let strNumber = input.toString();
+ if (validator) {
+ if (validator(strNumber)) {
+ array[0] = strNumber;
+ numValidValue++;
+ for (let ind = 1; ind < array.length; ind++) {
+ array[ind] = strNumber;
+ }
+ } else {
+ errorMessage = "Invalid inputs for " + name;
+ }
+ } else {
+ array[0] = strNumber;
+ numValidValue++;
+ for (let ind = 1; ind < array.length; ind++) {
+ array[ind] = strNumber;
+ }
+ }
} else {
errorMessage = "Invalid inputs for " + name;
}
@@ -508,6 +556,10 @@ function getStringArrayFromInput(
return errorMessage;
}
+ for (let ind = 0; ind < array.length; ind++) {
+ array[ind] = helper.replaceImgTagByAlt(array[ind]);
+ }
+
return array;
}
@@ -531,7 +583,7 @@ function getNumberArray(name: string, input: any): Array | string {
}
}
} else if (typeof input === "string") {
- let splitted = input.split(",");
+ let splitted = splitInputByComma(input);
if (splitted.length > 1) {
for (let piece of splitted) {
let v = parseFloat(piece.trim());
@@ -574,16 +626,16 @@ function getStringArray(name: string, input: any): Array | string {
if (Array.isArray(input)) {
for (let elem of input) {
if (typeof elem === "string") {
- strArray.push(elem);
+ strArray.push(elem.trim());
}
}
}
} else if (typeof input === "string") {
- let splitted = input.split(",");
+ let splitted = splitInputByComma(input);
// console.log(splitted);
if (splitted.length > 1) {
for (let piece of splitted) {
- strArray.push(piece);
+ strArray.push(piece.trim());
}
} else if (input === "") {
let errorMessage = `Empty ${name} is not allowed.`;
@@ -596,6 +648,10 @@ function getStringArray(name: string, input: any): Array | string {
return errorMessage;
}
+ for (let ind = 0; ind < strArray.length; ind++) {
+ strArray[ind] = helper.replaceImgTagByAlt(strArray[ind]);
+ }
+
return strArray;
}
@@ -605,24 +661,25 @@ function parseCommonChartInfo(yaml: any, renderInfo: CommonChartInfo) {
// single value, use default value if no value from YAML
if (yaml) {
// title
- if (typeof yaml.title === "string") {
- renderInfo.title = yaml.title;
- }
+ renderInfo.title = getStringFromInput(yaml?.title, renderInfo.title);
// xAxisLabel
- if (typeof yaml.xAxisLabel === "string") {
- renderInfo.xAxisLabel = yaml.xAxisLabel;
- }
+ renderInfo.xAxisLabel = getStringFromInput(
+ yaml?.xAxisLabel,
+ renderInfo.xAxisLabel
+ );
// xAxisColor
- if (typeof yaml.xAxisColor === "string") {
- renderInfo.xAxisColor = yaml.xAxisColor;
- }
+ renderInfo.xAxisColor = getStringFromInput(
+ yaml?.xAxisColor,
+ renderInfo.xAxisColor
+ );
// xAxisLabelColor
- if (typeof yaml.xAxisLabelColor === "string") {
- renderInfo.xAxisLabelColor = yaml.xAxisLabelColor;
- }
+ renderInfo.xAxisLabelColor = getStringFromInput(
+ yaml?.xAxisLabelColor,
+ renderInfo.xAxisLabelColor
+ );
// allowInspectData
if (typeof yaml.allowInspectData === "boolean") {
@@ -663,14 +720,16 @@ function parseCommonChartInfo(yaml: any, renderInfo: CommonChartInfo) {
// console.log(renderInfo.legendOrientation);
// legendBgColor
- if (typeof yaml.legendBgColor === "string") {
- renderInfo.legendBgColor = yaml.legendBgColor;
- }
+ renderInfo.legendBgColor = getStringFromInput(
+ yaml?.legendBgColor,
+ renderInfo.legendBgColor
+ );
// legendBorderColor
- if (typeof yaml.legendBorderColor === "string") {
- renderInfo.legendBorderColor = yaml.legendBorderColor;
- }
+ renderInfo.legendBorderColor = getStringFromInput(
+ yaml?.legendBorderColor,
+ renderInfo.legendBorderColor
+ );
}
// yAxisLabel
@@ -745,6 +804,56 @@ function parseCommonChartInfo(yaml: any, renderInfo: CommonChartInfo) {
renderInfo.yAxisUnit = retYAxisUnit;
// console.log(renderInfo.yAxisUnit);
+ // xAxisTickInterval
+ renderInfo.xAxisTickInterval = getStringFromInput(
+ yaml?.xAxisTickInterval,
+ renderInfo.xAxisTickInterval
+ );
+ // console.log(renderInfo.xAxisTickInterval);
+
+ // yAxisTickInterval
+ let retYAxisTickInterval = getStringArrayFromInput(
+ "yAxisTickInterval",
+ yaml?.yAxisTickInterval,
+ 2,
+ null,
+ null,
+ true
+ );
+ if (typeof retYAxisTickInterval === "string") {
+ return retYAxisTickInterval; // errorMessage
+ }
+ if (retYAxisTickInterval.length > 2) {
+ return "yAxisTickInterval accepts not more than two values for left and right y-axes";
+ }
+ renderInfo.yAxisTickInterval = retYAxisTickInterval;
+ // console.log(renderInfo.yAxisTickInterval);
+
+ // xAxisTickLabelFormat
+ renderInfo.xAxisTickLabelFormat = getStringFromInput(
+ yaml?.xAxisTickLabelFormat,
+ renderInfo.xAxisTickLabelFormat
+ );
+ // console.log(renderInfo.xAxisTickLabelFormat);
+
+ // yAxisTickLabelFormat
+ let retYAxisTickLabelFormat = getStringArrayFromInput(
+ "yAxisTickLabelFormat",
+ yaml?.yAxisTickLabelFormat,
+ 2,
+ null,
+ null,
+ true
+ );
+ if (typeof retYAxisTickLabelFormat === "string") {
+ return retYAxisTickLabelFormat; // errorMessage
+ }
+ if (retYAxisTickLabelFormat.length > 2) {
+ return "yAxisTickLabelFormat accepts not more than two values for left and right y-axes";
+ }
+ renderInfo.yAxisTickLabelFormat = retYAxisTickLabelFormat;
+ // console.log(renderInfo.yAxisTickLabelFormat);
+
// yMin
let retYMin = getNumberArrayFromInput("yMin", yaml?.yMin, 2, null, true);
if (typeof retYMin === "string") {
@@ -802,6 +911,7 @@ export function getRenderInfoFromYaml(
): RenderInfo | string {
let yaml;
try {
+ // console.log(yamlText);
yaml = parseYaml(yamlText);
} catch (err) {
let errorMessage = "Error parsing YAML";
@@ -838,7 +948,8 @@ export function getRenderInfoFromYaml(
}
}
} else if (typeof yaml.searchTarget === "string") {
- let splitted = yaml.searchTarget.split(",");
+ let splitted = splitInputByComma(yaml.searchTarget);
+ // console.log(splitted);
if (splitted.length > 1) {
for (let piece of splitted) {
piece = piece.trim();
@@ -857,6 +968,9 @@ export function getRenderInfoFromYaml(
} else {
errorMessage = "Invalid search target (searchTarget)";
}
+ for (let ind = 0; ind < searchTarget.length; ind++) {
+ searchTarget[ind] = helper.replaceImgTagByAlt(searchTarget[ind]);
+ }
// console.log(searchTarget);
if (errorMessage !== "") {
@@ -890,9 +1004,21 @@ export function getRenderInfoFromYaml(
case "frontmatter":
searchType.push(SearchType.Frontmatter);
break;
+ case "frontmatter.exists":
+ searchType.push(SearchType.FrontmatterExists);
+ break;
+ case "frontmatterlist":
+ searchType.push(SearchType.FrontmatterList);
+ break;
case "wiki":
searchType.push(SearchType.Wiki);
break;
+ case "wiki.link":
+ searchType.push(SearchType.WikiLink);
+ break;
+ case "wiki.display":
+ searchType.push(SearchType.WikiDisplay);
+ break;
case "text":
searchType.push(SearchType.Text);
break;
@@ -925,7 +1051,7 @@ export function getRenderInfoFromYaml(
searchType.filter((t) => t !== SearchType.Table).length > 0
) {
let errorMessage =
- "searchType 'table' doestn't work with other types for now";
+ "searchType 'table' doesn't work with other types for now";
return errorMessage;
}
// console.log(searchType);
@@ -936,7 +1062,7 @@ export function getRenderInfoFromYaml(
"separator",
yaml.separator,
numDatasets,
- "/",
+ "", // set the default value later
null,
true
);
@@ -944,7 +1070,7 @@ export function getRenderInfoFromYaml(
return retMultipleValueSparator; // errorMessage
}
multipleValueSparator = retMultipleValueSparator.map((sep) => {
- if (sep === "comma") {
+ if (sep === "comma" || sep === "\\,") {
return ",";
}
return sep;
@@ -1026,6 +1152,32 @@ export function getRenderInfoFromYaml(
additionalAllowedKeys.push(key);
}
}
+ // Custom dataset
+ let yamlCustomDatasetKeys = [];
+ for (let key of keysFoundInYAML) {
+ if (/^dataset[0-9]*$/.test(key)) {
+ // Check the id of custom dataset is not duplicated
+ let customDatasetId = -1;
+ let strCustomDatasetId = key.replace("dataset", "");
+ if (strCustomDatasetId === "") {
+ customDatasetId = 0;
+ } else {
+ customDatasetId = parseFloat(strCustomDatasetId);
+ }
+
+ if (
+ queries.some((q) => {
+ return q.getId() === customDatasetId;
+ })
+ ) {
+ errorMessage = "Duplicated dataset id for key '" + key + "'";
+ return errorMessage;
+ }
+
+ yamlCustomDatasetKeys.push(key);
+ additionalAllowedKeys.push(key);
+ }
+ }
// console.log(additionalAllowedKeys);
for (let key of keysFoundInYAML) {
if (
@@ -1049,17 +1201,12 @@ export function getRenderInfoFromYaml(
return "No output parameter provided, please place line, bar, pie, month, bullet, or summary.";
}
- // Get daily notes settings using obsidian-daily-notes-interface
- let dailyNotesSettings = getDailyNoteSettings();
-
// Root folder to search
- if (typeof yaml.folder === "string") {
- if (yaml.folder === "") {
- renderInfo.folder = plugin.settings.folder;
- } else {
- renderInfo.folder = yaml.folder;
- }
- } else {
+ renderInfo.folder = getStringFromInput(
+ yaml?.folder,
+ plugin.settings.folder
+ );
+ if (renderInfo.folder.trim() === "") {
renderInfo.folder = plugin.settings.folder;
}
// console.log("renderInfo folder: " + renderInfo.folder);
@@ -1072,6 +1219,42 @@ export function getRenderInfoFromYaml(
return errorMessage;
}
+ // file
+ if (typeof yaml.file === "string") {
+ let retFiles = getStringArray("file", yaml.file);
+ if (typeof retFiles === "string") {
+ return retFiles; // error message
+ }
+ renderInfo.file = retFiles;
+ }
+ // console.log(renderInfo.file);
+
+ // specifiedFilesOnly
+ if (typeof yaml.specifiedFilesOnly === "boolean") {
+ renderInfo.specifiedFilesOnly = yaml.specifiedFilesOnly;
+ }
+ // console.log(renderInfo.specifiedFilesOnly);
+
+ // fileContainsLinkedFiles
+ if (typeof yaml.fileContainsLinkedFiles === "string") {
+ let retFiles = getStringArray(
+ "fileContainsLinkedFiles",
+ yaml.fileContainsLinkedFiles
+ );
+ if (typeof retFiles === "string") {
+ return retFiles;
+ }
+ renderInfo.fileContainsLinkedFiles = retFiles;
+ }
+ // console.log(renderInfo.fileContainsLinkedFiles);
+
+ // fileMultiplierAfterLink
+ renderInfo.fileMultiplierAfterLink = getStringFromInput(
+ yaml?.fileMultiplierAfterLink,
+ renderInfo.fileMultiplierAfterLink
+ );
+ // console.log(renderInfo.fileMultiplierAfterLink);
+
// Date format
const dateFormat = yaml.dateFormat;
//?? not sure why I need this to make it works,
@@ -1088,43 +1271,48 @@ export function getRenderInfoFromYaml(
// console.log("renderInfo dateFormat: " + renderInfo.dateFormat);
// Date format prefix
- if (typeof yaml.dateFormatPrefix === "string") {
- renderInfo.dateFormatPrefix = yaml.dateFormatPrefix;
- }
+ renderInfo.dateFormatPrefix = getStringFromInput(
+ yaml?.dateFormatPrefix,
+ renderInfo.dateFormatPrefix
+ );
// Date fromat suffix
- if (typeof yaml.dateFormatSuffix === "string") {
- renderInfo.dateFormatSuffix = yaml.dateFormatSuffix;
- }
+ renderInfo.dateFormatSuffix = getStringFromInput(
+ yaml?.dateFormatSuffix,
+ renderInfo.dateFormatSuffix
+ );
// startDate, endDate
// console.log("Parsing startDate");
- if (typeof yaml.startDate === "string") {
- let strStartDate = yaml.startDate;
- if (
- renderInfo.dateFormatPrefix &&
- strStartDate.startsWith(renderInfo.dateFormatPrefix)
- ) {
- strStartDate = strStartDate.slice(
- renderInfo.dateFormatPrefix.length
- );
+ // Handle both string and number types (YAML may parse numeric dates as numbers)
+ if (typeof yaml.startDate === "string" || typeof yaml.startDate === "number") {
+ let strStartDate: string;
+ if (typeof yaml.startDate === "number") {
+ strStartDate = yaml.startDate.toString();
+ } else {
+ strStartDate = yaml.startDate;
}
- if (
- renderInfo.dateFormatSuffix &&
- strStartDate.endsWith(renderInfo.dateFormatSuffix)
- ) {
- strStartDate = strStartDate.slice(
- 0,
- strStartDate.length - renderInfo.dateFormatSuffix.length
- );
+ if (/^([\-]?[0-9]+[\.][0-9]+|[\-]?[0-9]+)m$/.test(strStartDate)) {
+ let errorMessage =
+ "'m' for 'minute' is too small for parameter startDate, please use 'd' for 'day' or 'M' for month";
+ return errorMessage;
}
+ strStartDate = helper.getDateStringFromInputString(
+ strStartDate,
+ renderInfo.dateFormatPrefix,
+ renderInfo.dateFormatSuffix
+ );
+ // console.log(strStartDate);
+ // relative date
let startDate = null;
let isStartDateValid = false;
- startDate = helper.relDateStringToDate(
+ startDate = helper.getDateByDurationToToday(
strStartDate,
renderInfo.dateFormat
);
+ // console.log(startDate);
+
if (startDate) {
isStartDateValid = true;
} else {
@@ -1133,6 +1321,7 @@ export function getRenderInfoFromYaml(
isStartDateValid = true;
}
}
+ // console.log(startDate);
if (!isStartDateValid || startDate === null) {
let errorMessage =
@@ -1144,27 +1333,31 @@ export function getRenderInfoFromYaml(
}
// console.log("Parsing endDate");
- if (typeof yaml.endDate === "string") {
- let strEndDate = yaml.endDate;
- if (
- renderInfo.dateFormatPrefix &&
- strEndDate.startsWith(renderInfo.dateFormatPrefix)
- ) {
- strEndDate = strEndDate.slice(renderInfo.dateFormatPrefix.length);
+ // Handle both string and number types (YAML may parse numeric dates as numbers)
+ if (typeof yaml.endDate === "string" || typeof yaml.endDate === "number") {
+ let strEndDate: string;
+ if (typeof yaml.endDate === "number") {
+ strEndDate = yaml.endDate.toString();
+ } else {
+ strEndDate = yaml.endDate;
}
- if (
- renderInfo.dateFormatSuffix &&
- strEndDate.endsWith(renderInfo.dateFormatSuffix)
- ) {
- strEndDate = strEndDate.slice(
- 0,
- strEndDate.length - renderInfo.dateFormatSuffix.length
- );
+ if (/^([\-]?[0-9]+[\.][0-9]+|[\-]?[0-9]+)m$/.test(strEndDate)) {
+ let errorMessage =
+ "'m' for 'minute' is too small for parameter endDate, please use 'd' for 'day' or 'M' for month";
+ return errorMessage;
}
+ strEndDate = helper.getDateStringFromInputString(
+ strEndDate,
+ renderInfo.dateFormatPrefix,
+ renderInfo.dateFormatSuffix
+ );
let endDate = null;
let isEndDateValid = false;
- endDate = helper.relDateStringToDate(strEndDate, renderInfo.dateFormat);
+ endDate = helper.getDateByDurationToToday(
+ strEndDate,
+ renderInfo.dateFormat
+ );
if (endDate) {
isEndDateValid = true;
} else {
@@ -1289,6 +1482,12 @@ export function getRenderInfoFromYaml(
renderInfo.accum = retAccum;
// console.log(renderInfo.accum);
+ // stack
+ if (typeof yaml.stack === "boolean") {
+ renderInfo.stack = yaml.stack;
+ }
+ // console.log(renderInfo.stack);
+
// penalty
let retPenalty = getNumberArrayFromInput(
"penalty",
@@ -1317,6 +1516,31 @@ export function getRenderInfoFromYaml(
renderInfo.valueShift = retValueShift;
// console.log(renderInfo.valueShift);
+ // shiftOnlyValueLargerThan
+ let retShiftOnlyValueLargerThan = getNumberArrayFromInput(
+ "shiftOnlyValueLargerThan",
+ yaml.shiftOnlyValueLargerThan,
+ numDatasets,
+ null,
+ true
+ );
+ if (typeof retShiftOnlyValueLargerThan === "string") {
+ return retShiftOnlyValueLargerThan;
+ }
+ renderInfo.shiftOnlyValueLargerThan = retShiftOnlyValueLargerThan;
+ // console.log(renderInfo.shiftOnlyValueLargerThan);
+
+ // textValueMap
+ if (typeof yaml.textValueMap !== "undefined") {
+ let keys = getAvailableKeysOfClass(yaml.textValueMap);
+ // console.log(texts);
+ for (let key of keys) {
+ let text = key.trim();
+ renderInfo.textValueMap[text] = yaml.textValueMap[text];
+ }
+ }
+ // console.log(renderInfo.textValueMap);
+
// fixedScale
if (typeof yaml.fixedScale === "number") {
renderInfo.fixedScale = yaml.fixedScale;
@@ -1327,6 +1551,19 @@ export function getRenderInfoFromYaml(
renderInfo.fitPanelWidth = yaml.fitPanelWidth;
}
+ // aspectRatio
+ if (typeof yaml.aspectRatio === "string") {
+ // yaml.fitPanelWidth
+ let ratioRegEx = /([0-9]*):([0-9]*)/;
+ let parts = yaml.aspectRatio.match(ratioRegEx);
+ parts.shift();
+ parts = parts.map((i: string)=>parseInt(i,10));
+ if (parts.length==2) {
+ renderInfo.aspectRatio = new AspectRatio(parts[0], parts[1]);
+ renderInfo.dataAreaSize = renderInfo.aspectRatio.recalculateSize(renderInfo.dataAreaSize)
+ }
+ }
+
// margin
let retMargin = getNumberArrayFromInput("margin", yaml.margin, 4, 10, true);
if (typeof retMargin === "string") {
@@ -1343,6 +1580,64 @@ export function getRenderInfoFromYaml(
);
// console.log(renderInfo.margin);
+ // customDataset related parameters
+ for (let datasetKey of yamlCustomDatasetKeys) {
+ let customDataset = new CustomDatasetInfo();
+ let yamlCustomDataset = yaml[datasetKey];
+
+ let keysOfCustomDatasetInfo = getAvailableKeysOfClass(customDataset);
+ let keysFoundInYAML = getAvailableKeysOfClass(yamlCustomDataset);
+ // console.log(keysOfCustomDatasetInfo);
+ // console.log(keysFoundInYAML);
+ for (let key of keysFoundInYAML) {
+ if (!keysOfCustomDatasetInfo.includes(key)) {
+ errorMessage = "'" + key + "' is not an available key";
+ return errorMessage;
+ }
+ }
+
+ // id
+ let customDatasetId = -1;
+ let strCustomDatasetId = datasetKey.replace("dataset", "");
+ if (strCustomDatasetId === "") {
+ customDatasetId = 0;
+ } else {
+ customDatasetId = parseFloat(strCustomDatasetId);
+ }
+ customDataset.id = customDatasetId;
+
+ // name
+ customDataset.name = getStringFromInput(
+ yamlCustomDataset?.name,
+ customDataset.name
+ );
+
+ // xData
+ let retXData = getStringArray("xData", yamlCustomDataset?.xData);
+ if (typeof retXData === "string") {
+ return retXData;
+ }
+ customDataset.xData = retXData;
+ // console.log(customDataset.xData);
+ let numXData = customDataset.xData.length;
+
+ // yData
+ let retYData = getStringArray("yData", yamlCustomDataset?.yData);
+ if (typeof retYData === "string") {
+ return retYData;
+ }
+ customDataset.yData = retYData;
+ // console.log(customDataset.yData);
+ if (customDataset.yData.length !== numXData) {
+ let errorMessage =
+ "Number of elements in xData and yData not matched";
+ return errorMessage;
+ }
+
+ renderInfo.customDataset.push(customDataset);
+ } // customDataset related parameters
+ // console.log(renderInfo.customDataset);
+
// line related parameters
for (let lineKey of yamlLineKeys) {
let line = new LineInfo();
@@ -1563,6 +1858,14 @@ export function getRenderInfoFromYaml(
bar.yAxisLocation = retYAxisLocation;
// console.log(bar.yAxisLocation);
+ // xAxisPadding
+ let retXAxisPadding = getStringFromInput(
+ yamlBar?.xAxisPadding,
+ null
+ );
+ bar.xAxisPadding = retXAxisPadding;
+ // console.log(bar.xAxisPadding);
+
renderInfo.bar.push(bar);
} // bar related parameters
// console.log(renderInfo.bar);
@@ -1584,9 +1887,7 @@ export function getRenderInfoFromYaml(
}
// title
- if (typeof yamlPie?.title === "string") {
- pie.title = yamlPie.title;
- }
+ pie.title = getStringFromInput(yamlPie?.title, pie.title);
// console.log(pie.title);
// data
@@ -1603,7 +1904,7 @@ export function getRenderInfoFromYaml(
"dataColor",
yamlPie?.dataColor,
numData,
- "none",
+ null,
validateColor,
true
);
@@ -1613,15 +1914,114 @@ export function getRenderInfoFromYaml(
pie.dataColor = retDataColor;
// console.log(pie.dataColor);
+ // dataName
+ let retDataName = getStringArrayFromInput(
+ "dataName",
+ yamlPie?.dataName,
+ numData,
+ "",
+ null,
+ true
+ );
+ if (typeof retDataName === "string") {
+ return retDataName; // errorMessage
+ }
+ pie.dataName = retDataName;
+ // console.log(pie.dataName);
+
+ // label
+ let retLabel = getStringArrayFromInput(
+ "label",
+ yamlPie?.label,
+ numData,
+ "",
+ null,
+ true
+ );
+ if (typeof retLabel === "string") {
+ return retLabel; // errorMessage
+ }
+ pie.label = retLabel;
+ // console.log(pie.label);
+
+ // hideLabelLessThan
+ if (typeof yamlPie?.hideLabelLessThan === "number") {
+ pie.hideLabelLessThan = yamlPie.hideLabelLessThan;
+ }
+ // console.log(pie.hideLabelLessThan);
+
+ // extLabel
+ let retExtLabel = getStringArrayFromInput(
+ "extLabel",
+ yamlPie?.extLabel,
+ numData,
+ "",
+ null,
+ true
+ );
+ if (typeof retExtLabel === "string") {
+ return retExtLabel; // errorMessage
+ }
+ pie.extLabel = retExtLabel;
+ // console.log(pie.extLabel);
+
+ // showExtLabelOnlyIfNoLabel
+ if (typeof yamlPie?.showExtLabelOnlyIfNoLabel === "boolean") {
+ pie.showExtLabelOnlyIfNoLabel = yamlPie.showExtLabelOnlyIfNoLabel;
+ }
+ // console.log(pie.showExtLabelOnlyIfNoLabel);
+
// ratioInnerRadius
if (typeof yamlPie?.ratioInnerRadius === "number") {
pie.ratioInnerRadius = yamlPie.ratioInnerRadius;
}
// console.log(pie.ratioInnerRadius);
+ // showLegend
+ if (typeof yamlPie?.showLegend === "boolean") {
+ pie.showLegend = yamlPie.showLegend;
+ }
+
+ // legendPosition
+ pie.legendPosition = getStringFromInput(
+ yamlPie?.legendPosition,
+ "right"
+ );
+
+ // legendOrient
+ let defaultLegendOrientation = "horizontal";
+ if (pie.legendPosition === "top" || pie.legendPosition === "bottom") {
+ defaultLegendOrientation = "horizontal";
+ } else if (
+ pie.legendPosition === "left" ||
+ pie.legendPosition === "right"
+ ) {
+ defaultLegendOrientation = "vertical";
+ } else {
+ defaultLegendOrientation = "horizontal";
+ }
+ pie.legendOrientation = getStringFromInput(
+ yamlPie?.legendOrientation,
+ defaultLegendOrientation
+ );
+ // console.log(pie.legendPosition);
+ // console.log(pie.legendOrientation);
+
+ // legendBgColor
+ pie.legendBgColor = getStringFromInput(
+ yamlPie?.legendBgColor,
+ pie.legendBgColor
+ );
+
+ // legendBorderColor
+ pie.legendBorderColor = getStringFromInput(
+ yamlPie?.legendBorderColor,
+ pie.legendBorderColor
+ );
+
renderInfo.pie.push(pie);
} // pie related parameters
- // console.log(renderInfo.pi);
+ // console.log(renderInfo.pie);
// summary related parameters
for (let summaryKey of yamlSummaryKeys) {
@@ -1640,12 +2040,13 @@ export function getRenderInfoFromYaml(
}
// template
- if (typeof yamlSummary?.template === "string") {
- summary.template = yamlSummary.template;
- }
- if (typeof yamlSummary?.style === "string") {
- summary.style = yamlSummary.style;
- }
+ summary.template = getStringFromInput(
+ yamlSummary?.template,
+ summary.template
+ );
+
+ // style
+ summary.style = getStringFromInput(yamlSummary?.style, summary.style);
renderInfo.summary.push(summary);
} // summary related parameters
@@ -1666,6 +2067,10 @@ export function getRenderInfoFromYaml(
}
}
+ // mode
+ month.mode = getStringFromInput(yamlMonth?.mode, month.mode);
+ // console.log(month.mode);
+
// dataset
let retDataset = getNumberArray("dataset", yamlMonth?.dataset);
if (typeof retDataset === "string") {
@@ -1682,9 +2087,20 @@ export function getRenderInfoFromYaml(
let numDataset = month.dataset.length;
// startWeekOn
- if (typeof yamlMonth?.startWeekOn === "string") {
- month.startWeekOn = yamlMonth.startWeekOn;
+ let startWeekOnValue = getStringFromInput(
+ yamlMonth?.startWeekOn,
+ month.startWeekOn
+ );
+ // Validate day abbreviation - supports both abbreviations and full names
+ if (startWeekOnValue) {
+ const normalized = startWeekOnValue.toLowerCase().trim();
+ const validDays = ["sun", "sunday", "mon", "monday", "tue", "tuesday", "wed", "wednesday", "thu", "thursday", "fri", "friday", "sat", "saturday"];
+ if (!validDays.includes(normalized)) {
+ errorMessage = `Invalid startWeekOn value: "${startWeekOnValue}". Must be one of: Sun, Mon, Tue, Wed, Thu, Fri, Sat (or full day names like Sunday, Monday, etc.)`;
+ return errorMessage;
+ }
}
+ month.startWeekOn = startWeekOnValue;
// console.log(month.startWeekOn);
// showCircle
@@ -1713,6 +2129,33 @@ export function getRenderInfoFromYaml(
}
// console.log(month.threshold);
+ // thresholdType
+ let retThresholdType = getStringArray("thresholdType", yamlMonth?.thresholdType);
+ if (typeof retThresholdType === "string") {
+ return retThresholdType;
+ }
+ month.thresholdType = retThresholdType;
+ if (month.thresholdType.length === 0) {
+ for (let indDataset = 0; indDataset < numDataset; indDataset++) {
+ month.thresholdType.push(ThresholdType.GreaterThan);
+ }
+ }
+ if (month.thresholdType.length !== month.dataset.length) {
+ const errorMessage =
+ "The number of inputs of thresholdType and dataset not matched";
+ return errorMessage;
+ }
+ for(let ind in month.thresholdType) {
+ if(!Object.values(ThresholdType).includes(month.thresholdType[ind].toLowerCase() as ThresholdType)) {
+ const errorMessage =
+ "thresholdType should be either 'GreaterThan' or 'LessThan'";
+ return errorMessage;
+ } else {
+ month.thresholdType[ind] = month.thresholdType[ind].toLowerCase();
+ }
+ }
+ // console.log(month.thresholdType);
+
// yMin
let retYMin = getNumberArray("yMin", yamlMonth?.yMin);
if (typeof retYMin === "string") {
@@ -1750,9 +2193,7 @@ export function getRenderInfoFromYaml(
// console.log(month.yMax);
// color
- if (typeof yamlMonth?.color === "string") {
- month.color = yamlMonth.color;
- }
+ month.color = getStringFromInput(yamlMonth?.color, month.color);
// console.log(month.color);
// dimNotInMonth
@@ -1786,9 +2227,10 @@ export function getRenderInfoFromYaml(
// console.log(month.showSelectedRing);
// circleColor
- if (typeof yamlMonth?.circleColor === "string") {
- month.circleColor = yamlMonth.circleColor;
- }
+ month.circleColor = getStringFromInput(
+ yamlMonth?.circleColor,
+ month.circleColor
+ );
// console.log(month.circleColor);
// circleColorByValue
@@ -1797,42 +2239,85 @@ export function getRenderInfoFromYaml(
}
// console.log(month.circleColorByValue);
- // headerYearColor
- if (typeof yamlMonth?.headerYearColor === "string") {
- month.headerYearColor = yamlMonth.headerYearColor;
+ // circleColorByStreak
+ if (typeof yamlMonth?.circleColorByStreak === "boolean") {
+ month.circleColorByStreak = yamlMonth.circleColorByStreak;
}
+ //console.log(month.circleColorByStreak);
+
+ // headerYearColor
+ month.headerYearColor = getStringFromInput(
+ yamlMonth?.headerYearColor,
+ month.headerYearColor
+ );
// console.log(month.headerYearColor);
// headerMonthColor
- if (typeof yamlMonth?.headerMonthColor === "string") {
- month.headerMonthColor = yamlMonth.headerMonthColor;
- }
+ month.headerMonthColor = getStringFromInput(
+ yamlMonth?.headerMonthColor,
+ month.headerMonthColor
+ );
// console.log(month.headerMonthColor);
// dividingLineColor
- if (typeof yamlMonth?.dividingLineColor === "string") {
- month.dividingLineColor = yamlMonth.dividingLineColor;
- }
+ month.dividingLineColor = getStringFromInput(
+ yamlMonth?.dividingLineColor,
+ month.dividingLineColor
+ );
// console.log(month.dividingLineColor);
// todayRingColor
- if (typeof yamlMonth?.todayRingColor === "string") {
- month.todayRingColor = yamlMonth.todayRingColor;
- }
+ month.todayRingColor = getStringFromInput(
+ yamlMonth?.todayRingColor,
+ month.todayRingColor
+ );
// console.log(month.todayRingColor);
// selectedRingColor
- if (typeof yamlMonth?.selectedRingColor === "string") {
- month.selectedRingColor = yamlMonth.selectedRingColor;
- }
+ month.selectedRingColor = getStringFromInput(
+ yamlMonth?.selectedRingColor,
+ month.selectedRingColor
+ );
// console.log(month.selectedRingColor);
// initMonth
- if (typeof yamlMonth?.initMonth === "string") {
- month.initMonth = yamlMonth.initMonth;
- }
+ month.initMonth = getStringFromInput(
+ yamlMonth?.initMonth,
+ month.initMonth
+ );
// console.log(month.initMonth);
+ // showAnnotation
+ if (typeof yamlMonth?.showAnnotation === "boolean") {
+ month.showAnnotation = yamlMonth.showAnnotation;
+ }
+ // console.log(month.showAnnotation);
+
+ // annotation
+ let retAnnotation = getStringArray("annotation", yamlMonth?.annotation);
+ if (typeof retAnnotation === "string") {
+ return retAnnotation;
+ }
+ month.annotation = retAnnotation;
+ if (month.annotation.length === 0) {
+ for (let indDataset = 0; indDataset < numDataset; indDataset++) {
+ month.annotation.push(null);
+ }
+ }
+ if (month.annotation.length !== month.dataset.length) {
+ const errorMessage =
+ "The number of inputs of annotation and dataset not matched";
+ return errorMessage;
+ }
+ // console.log(month.annotation);
+
+ // showAnnotationOfAllTargets
+ if (typeof yamlMonth?.showAnnotationOfAllTargets === "boolean") {
+ month.showAnnotationOfAllTargets =
+ yamlMonth.showAnnotationOfAllTargets;
+ }
+ // console.log(month.showAnnotationOfAllTargets);
+
renderInfo.month.push(month);
} // Month related parameters
// console.log(renderInfo.month);
@@ -1853,6 +2338,22 @@ export function getRenderInfoFromYaml(
}
}
+ // startWeekOn
+ let heatmapStartWeekOnValue = getStringFromInput(
+ yamlHeatmap?.startWeekOn,
+ heatmap.startWeekOn
+ );
+ // Validate day abbreviation - supports both abbreviations and full names
+ if (heatmapStartWeekOnValue) {
+ const normalized = heatmapStartWeekOnValue.toLowerCase().trim();
+ const validDays = ["sun", "sunday", "mon", "monday", "tue", "tuesday", "wed", "wednesday", "thu", "thursday", "fri", "friday", "sat", "saturday"];
+ if (!validDays.includes(normalized)) {
+ errorMessage = `Invalid startWeekOn value: "${heatmapStartWeekOnValue}". Must be one of: Sun, Mon, Tue, Wed, Thu, Fri, Sat (or full day names like Sunday, Monday, etc.)`;
+ return errorMessage;
+ }
+ }
+ heatmap.startWeekOn = heatmapStartWeekOnValue;
+
renderInfo.heatmap.push(heatmap);
}
// console.log(renderInfo.heatmap);
@@ -1874,21 +2375,21 @@ export function getRenderInfoFromYaml(
}
// title
- if (typeof yamlBullet?.title === "string") {
- bullet.title = yamlBullet.title;
- }
+ bullet.title = getStringFromInput(yamlBullet?.title, bullet.title);
// console.log(bullet.title);
// dataset
- if (typeof yamlBullet?.dataset === "string") {
- bullet.dataset = yamlBullet.dataset;
- }
+ bullet.dataset = getStringFromInput(
+ yamlBullet?.dataset,
+ bullet.dataset
+ );
// console.log(bullet.dataset);
// orientation
- if (typeof yamlBullet?.orientation === "string") {
- bullet.orientation = yamlBullet.orientation;
- }
+ bullet.orientation = getStringFromInput(
+ yamlBullet?.orientation,
+ bullet.orientation
+ );
// console.log(bullet.orientation);
// range
@@ -1942,21 +2443,21 @@ export function getRenderInfoFromYaml(
// console.log(bullet.rangeColor);
// actual value, can possess template variable
- if (typeof yamlBullet?.value === "string") {
- bullet.value = yamlBullet.value;
- }
+ bullet.value = getStringFromInput(yamlBullet?.value, bullet.value);
// console.log(bullet.value);
// value unit
- if (typeof yamlBullet?.valueUnit === "string") {
- bullet.valueUnit = yamlBullet.valueUnit;
- }
+ bullet.valueUnit = getStringFromInput(
+ yamlBullet?.valueUnit,
+ bullet.valueUnit
+ );
// console.log(bullet.valueUnit);
// value color
- if (typeof yamlBullet?.valueColor === "string") {
- bullet.valueColor = yamlBullet.valueColor;
- }
+ bullet.valueColor = getStringFromInput(
+ yamlBullet?.valueColor,
+ bullet.valueColor
+ );
// console.log(bullet.valueColor);
// show mark
@@ -1972,9 +2473,10 @@ export function getRenderInfoFromYaml(
// console.log(bullet.markValue);
// mark color
- if (typeof yamlBullet?.markerColor === "string") {
- bullet.markerColor = yamlBullet.markerColor;
- }
+ bullet.markerColor = getStringFromInput(
+ yamlBullet?.markerColor,
+ bullet.markerColor
+ );
// console.log(bullet.markValue);
renderInfo.bullet.push(bullet);
diff --git a/src/pie.ts b/src/pie.ts
index 7c872864..c1ddf957 100644
--- a/src/pie.ts
+++ b/src/pie.ts
@@ -9,12 +9,41 @@ import {
Size,
Transform,
ChartElements,
- OutputType,
+ GraphType,
ValueType,
} from "./data";
import * as helper from "./helper";
import * as d3 from "d3";
import * as expr from "./expr";
+import { pie } from "d3";
+
+function setChartScale(
+ _canvas: HTMLElement,
+ chartElements: ChartElements,
+ renderInfo: RenderInfo
+) {
+ let canvas = d3.select(_canvas);
+ let svg = chartElements.svg;
+ let svgWidth = parseFloat(svg.attr("width"));
+ let svgHeight = parseFloat(svg.attr("height"));
+ svg.attr("width", null)
+ .attr("height", null)
+ .attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`)
+ .attr("preserveAspectRatio", "xMidYMid meet");
+
+ if (renderInfo.fitPanelWidth) {
+ canvas.style("width", "100%");
+ } else {
+ canvas.style(
+ "width",
+ (svgWidth * renderInfo.fixedScale).toString() + "px"
+ );
+ canvas.style(
+ "height",
+ (svgHeight * renderInfo.fixedScale).toString() + "px"
+ );
+ }
+}
function createAreas(
chartElements: ChartElements,
@@ -93,6 +122,255 @@ function renderTitle(
if (!pieInfo.title) return;
let titleSize = helper.measureTextSize(pieInfo.title, "tracker-title");
+
+ // Append title
+ let title = chartElements.graphArea
+ .append("text")
+ .text(pieInfo.title) // pivot at center
+ .attr("id", "title")
+ .attr(
+ "transform",
+ "translate(" +
+ renderInfo.dataAreaSize.width / 2.0 +
+ "," +
+ titleSize.height / 2.0 +
+ ")"
+ )
+ .attr("height", titleSize.height) // for later use
+ .attr("class", "tracker-title");
+ chartElements["title"] = title;
+
+ // Expand parent areas
+ helper.expandArea(chartElements.svg, 0, titleSize.height);
+ helper.expandArea(chartElements.graphArea, 0, titleSize.height);
+
+ // Move sibling areas
+ helper.moveArea(chartElements.dataArea, 0, titleSize.height);
+
+ return;
+}
+
+function renderLegend(
+ canvas: HTMLElement,
+ chartElements: ChartElements,
+ renderInfo: RenderInfo,
+ pieInfo: PieInfo
+) {
+ // console.log("renderLegend");
+ // console.log(piInfo.legendPosition);
+ // console.log(piInfo.legendOrientation);
+
+ // Get chart elements
+ let svg = chartElements.svg;
+ let graphArea = chartElements.graphArea;
+ let dataArea = chartElements.dataArea;
+ let title = chartElements.title;
+
+ // Get element width and height
+ let titleHeight = 0.0;
+ if (title) {
+ titleHeight = parseFloat(title.attr("height"));
+ }
+
+ // Get names and their dimension
+ let names = pieInfo.dataName;
+ let nameSizes = names.map(function (n) {
+ return helper.measureTextSize(n, "tracker-legend-label");
+ });
+ let indMaxName = 0;
+ let maxNameWidth = 0.0;
+ for (let ind = 0; ind < names.length; ind++) {
+ if (nameSizes[ind].width > maxNameWidth) {
+ maxNameWidth = nameSizes[ind].width;
+ indMaxName = ind;
+ }
+ }
+ let maxName = names[indMaxName];
+ let characterWidth = maxNameWidth / maxName.length;
+ let nameHeight = nameSizes[indMaxName].height;
+ let numNames = names.length;
+
+ let xSpacing = 2 * characterWidth;
+ let ySpacing = nameHeight;
+ let markerWidth = 2 * characterWidth;
+
+ // Get legend width and height
+ let legendWidth = 0;
+ let legendHeight = 0;
+ if (pieInfo.legendOrientation === "vertical") {
+ legendWidth = xSpacing * 3 + markerWidth + maxNameWidth;
+ legendHeight = (numNames + 1) * ySpacing;
+ } else if (pieInfo.legendOrientation === "horizontal") {
+ legendWidth =
+ (2 * xSpacing + markerWidth) * numNames +
+ xSpacing +
+ d3.sum(nameSizes, function (s, i) {
+ return s.width;
+ });
+ legendHeight = ySpacing + nameHeight;
+ }
+ // console.log(
+ // `maxName: ${maxName}, characterWidth: ${characterWidth}, maxNameWidth: ${maxNameWidth}`
+ // );
+ // console.log(`xSpacing:${xSpacing}, numNames: ${numNames}, markerWidth: ${markerWidth}`);
+ // console.log(`legendWidth: ${legendWidth}, legendHeight: ${legendHeight}`);
+
+ // Calcualte lengendX and legendY
+ let legendX = 0.0; // relative to graphArea
+ let legendY = 0.0;
+ if (pieInfo.legendPosition === "top") {
+ // below title
+ legendX = renderInfo.dataAreaSize.width / 2.0 - legendWidth / 2.0;
+ legendY = titleHeight;
+ // Expand svg
+ helper.expandArea(svg, 0, legendHeight + ySpacing);
+ // Move dataArea down
+ helper.moveArea(dataArea, 0, legendHeight + ySpacing);
+ } else if (pieInfo.legendPosition === "bottom") {
+ // bellow x-axis label
+ legendX = renderInfo.dataAreaSize.width / 2.0 - legendWidth / 2.0;
+ legendY = titleHeight + renderInfo.dataAreaSize.height + ySpacing;
+ // Expand svg
+ helper.expandArea(svg, 0, legendHeight + ySpacing);
+ } else if (pieInfo.legendPosition === "left") {
+ legendX = 0;
+ legendY =
+ titleHeight +
+ renderInfo.dataAreaSize.height / 2.0 -
+ legendHeight / 2.0;
+ // Expand svg
+ helper.expandArea(svg, legendWidth + xSpacing, 0);
+ // Move dataArea right
+ helper.moveArea(dataArea, legendWidth + xSpacing, 0);
+ } else if (pieInfo.legendPosition === "right") {
+ legendX = renderInfo.dataAreaSize.width + xSpacing;
+ legendY =
+ titleHeight +
+ renderInfo.dataAreaSize.height / 2.0 -
+ legendHeight / 2.0;
+ // Expand svg
+ helper.expandArea(svg, legendWidth + xSpacing, 0);
+ } else {
+ return;
+ }
+ // console.log(`legendX: ${legendX}, legendY: ${legendY}`);
+
+ let legend = chartElements.graphArea
+ .append("g")
+ .attr("id", "legend")
+ .attr("transform", "translate(" + legendX + "," + legendY + ")");
+ // console.log('legendX: %d, legendY: %d', legendX, legendY);
+
+ let legendBg = legend
+ .append("rect")
+ .attr("class", "tracker-legend")
+ .attr("width", legendWidth)
+ .attr("height", legendHeight);
+ if (pieInfo.legendBgColor) {
+ legendBg.style("fill", pieInfo.legendBgColor);
+ }
+ if (pieInfo.legendBorderColor) {
+ legendBg.style("stroke", pieInfo.legendBorderColor);
+ }
+
+ let markerRadius = 5.0;
+ let firstMarkerX = xSpacing;
+ let firstMarkerY = nameHeight;
+ let firstLabelX = firstMarkerX + xSpacing + markerWidth; // xSpacing + 2 * xSpaing
+ let firstLabelY = firstMarkerY;
+
+ if (pieInfo.legendOrientation === "vertical") {
+ // points
+ legend
+ .selectAll("markers")
+ .data(names)
+ .enter()
+ .append("circle")
+ .attr("cx", firstMarkerX + markerWidth / 2.0)
+ .attr("cy", function (name: string, i: number) {
+ return firstMarkerY + i * ySpacing;
+ })
+ .attr("r", function (name: string, i: number) {
+ return markerRadius;
+ })
+ .style("fill", function (name: string, i: number) {
+ return pieInfo.dataColor[i];
+ });
+
+ // names
+ let nameLabels = legend
+ .selectAll("labels")
+ .data(names)
+ .enter()
+ .append("text")
+ .attr("x", firstLabelX)
+ .attr("y", function (name: string, i: number) {
+ return firstLabelY + i * ySpacing;
+ })
+ .text(function (name: string, i: number) {
+ return name;
+ })
+ .style("alignment-baseline", "middle")
+ .attr("class", "tracker-legend-label");
+
+ nameLabels.style("fill", function (name: string, i: number) {
+ return pieInfo.dataColor[i];
+ });
+ } else if (pieInfo.legendOrientation === "horizontal") {
+ let currRenderPosX = 0.0;
+ let currRenderPosX2 = 0.0;
+
+ // points
+ currRenderPosX = 0.0;
+ legend
+ .selectAll("markers")
+ .data(names)
+ .enter()
+ .append("circle")
+ .attr("cx", function (name: string, i: number) {
+ if (i === 0) {
+ currRenderPosX = firstMarkerX + markerWidth / 2.0;
+ } else {
+ currRenderPosX +=
+ nameSizes[i].width + xSpacing + markerWidth + xSpacing;
+ }
+ return currRenderPosX;
+ })
+ .attr("cy", firstMarkerY)
+ .attr("r", function (name: string, i: number) {
+ return markerRadius;
+ })
+ .style("fill", function (name: string, i: number) {
+ return pieInfo.dataColor[i];
+ });
+
+ // names
+ currRenderPosX = 0.0;
+ let nameLabels = legend
+ .selectAll("labels")
+ .data(names)
+ .enter()
+ .append("text")
+ .attr("x", function (name: string, i: number) {
+ if (i === 0) {
+ currRenderPosX = firstLabelX;
+ } else {
+ currRenderPosX +=
+ nameSizes[i].width + xSpacing + markerWidth + xSpacing;
+ }
+ return currRenderPosX;
+ })
+ .attr("y", firstLabelY)
+ .text(function (name: string, i: number) {
+ return name;
+ })
+ .style("alignment-baseline", "middle")
+ .attr("class", "tracker-legend-label");
+
+ nameLabels.style("fill", function (name: string, i: number) {
+ return pieInfo.dataColor[i];
+ });
+ }
}
function renderPie(
@@ -103,14 +381,74 @@ function renderPie(
) {
// console.log("renderPie");
// console.log(renderInfo);
+ let errorMessage = "";
- let radius = renderInfo.dataAreaSize.width * 0.5 * 0.8;
+ let radius = renderInfo.dataAreaSize.width * 0.5;
+ let outterRadius = radius * 0.7;
+ let innerRadius = outterRadius * pieInfo.ratioInnerRadius;
+
+ // values
+ let values: Array = [];
+ for (let strExpr of pieInfo.data) {
+ let retValue = expr.resolveValue(strExpr, renderInfo);
+ if (typeof retValue === "string") {
+ errorMessage = retValue;
+ break;
+ } else if (typeof retValue === "number") {
+ values.push(retValue);
+ }
+ }
+ if (errorMessage !== "") {
+ return errorMessage;
+ }
+ // console.log(values);
- // data
- let data = pieInfo.data.map(function (s) {
- let value = expr.resolve(s, renderInfo);
- return value;
+ // labels
+ let labels: Array = [];
+ for (let strExpr of pieInfo.label) {
+ let retLabel = expr.resolveTemplate(strExpr, renderInfo);
+ // console.log(retLabel);
+ if (retLabel.startsWith("Error")) {
+ errorMessage = retLabel;
+ break;
+ }
+ labels.push(retLabel);
+ }
+ if (errorMessage !== "") {
+ return errorMessage;
+ }
+ // console.log(labels);
+
+ // hideLabelLessThan
+ let hideLabelLessThan = pieInfo.hideLabelLessThan;
+
+ // label sizes
+ let labelSizes = labels.map(function (n) {
+ return helper.measureTextSize(n, "tracker-tick-label");
+ });
+
+ // extLabel
+ let extLabels: Array = [];
+ for (let strExpr of pieInfo.extLabel) {
+ let retExtLabel = expr.resolveTemplate(strExpr, renderInfo);
+ if (retExtLabel.startsWith("Error")) {
+ errorMessage = retExtLabel;
+ break;
+ }
+ extLabels.push(retExtLabel);
+ }
+ if (errorMessage !== "") {
+ return errorMessage;
+ }
+ // console.log(extLabels);
+
+ // extLabel sizes
+ let extLabelSizes = extLabels.map(function (n) {
+ return helper.measureTextSize(n, "tracker-pie-label");
});
+ // console.log(extLabelSizes);
+
+ let showExtLabelOnlyIfNoLabel = pieInfo.showExtLabelOnlyIfNoLabel;
// scale
let colorScale = d3.scaleOrdinal().range(pieInfo.dataColor);
@@ -128,18 +466,22 @@ function renderPie(
});
let pie = d3.pie();
+ let pieValues = pie(values);
+ pieValues.forEach(function (value: any, i: number) {value.input_index = i})
let sectors = sectorsGroup
.selectAll("sector")
- .data(pie(data))
+ .data(pieValues)
.enter()
.append("g")
.attr("class", "sector");
- let arc = d3
+ let arc = d3.arc().innerRadius(innerRadius).outerRadius(outterRadius);
+
+ var hiddenArc = d3
.arc()
- .innerRadius(radius * pieInfo.ratioInnerRadius)
- .outerRadius(radius);
+ .innerRadius(radius * 0.9)
+ .outerRadius(radius * 0.9);
let sectorPaths = sectors
.append("path")
@@ -147,6 +489,183 @@ function renderPie(
return colorScale(i.toString());
})
.attr("d", arc);
+
+ function isLabelHidden(arcObj: any) {
+ // console.log(`start/end: ${arcObj.startAngle}/${arcObj.endAngle}`);
+ let fraction = (arcObj.endAngle - arcObj.startAngle) / (2.0 * Math.PI);
+ if (fraction < hideLabelLessThan) {
+ return true;
+ }
+ return false;
+ }
+
+ // label elements
+ let labelElements = sectorsGroup
+ .selectAll("label")
+ .data(pie(values))
+ .enter()
+ .append("text")
+ .text(function (arcObj: any, i: number) {
+ if (isLabelHidden(arcObj)) {
+ return "";
+ }
+ return labels[i];
+ })
+ .attr("transform", function (d: any) {
+ return (
+ "translate(" +
+ arc.centroid(d)[0] +
+ "," +
+ arc.centroid(d)[1] +
+ ")"
+ );
+ })
+ .style("text-anchor", "middle")
+ .attr("class", "tracker-pie-label");
+
+ function getMidAngle(arcObj: any) {
+ return arcObj.startAngle + (arcObj.endAngle - arcObj.startAngle) / 2;
+ }
+
+ function externalLabelText(arcObj: any, i: number) {
+ if (showExtLabelOnlyIfNoLabel) {
+ if (labels[i] === "" || isLabelHidden(arcObj)) {
+ return extLabels[i];
+ }
+ return "";
+ } else {
+ return extLabels[i];
+ }
+ }
+
+ // external label elements
+ let prevBB : DOMRect = null;
+ let extlabelPos : any = {};
+ let extLabelElements = sectorsGroup
+ .selectAll("extLabel")
+ .data(pieValues)
+ .enter()
+ .append("text")
+ // Sort external labels based on y value such that we can move down overlapping labels
+ .sort(function (arcObj1: any, arcObj2: any) {
+ return Math.cos(getMidAngle(arcObj2)) - Math.cos(getMidAngle(arcObj1));
+ })
+ .text(function (arcObj: any, i: number) {
+ i = arcObj.input_index;
+ return externalLabelText(arcObj, i)
+ })
+ .attr("transform", function (arcObj: any, i: number) {
+ i = arcObj.input_index;
+ // If external label is empty, directly return.
+ if (externalLabelText(arcObj, i).length == 0) {
+ return;
+ }
+ let posLabel = hiddenArc.centroid(arcObj);
+ let midAngle = getMidAngle(arcObj);
+
+ posLabel[0] =
+ (radius * 0.99 - extLabelSizes[i].width) *
+ (midAngle < Math.PI ? 1 : -1);
+
+ var yshift = 0;
+ let thisBB = new DOMRect(posLabel[0], posLabel[1], extLabelSizes[i].width, extLabelSizes[i].height);
+
+ if (prevBB !== null) {
+ // Check whether there are overlaps
+ if (!(thisBB.right < prevBB.left || prevBB.right < thisBB.left
+ || prevBB.bottom < thisBB.top)) {
+ // Since y is sorted from low to high, we expect to shift this item further down
+ yshift = prevBB.bottom - thisBB.top;
+ // console.log("has overlap", yshift);
+ }
+ }
+ if (yshift != 0) {
+ thisBB = new DOMRect(posLabel[0], posLabel[1] + yshift, extLabelSizes[i].width, extLabelSizes[i].height);
+ }
+ prevBB = thisBB;
+ // Save external label position for connection line plotting
+ extlabelPos[i] = [posLabel[0], posLabel[1] + yshift]
+ return "translate(" + posLabel[0] + "," + (posLabel[1] + yshift) + ")";
+ })
+ .style("text-anchor", function (arcObj: any) {
+ let midAngle = getMidAngle(arcObj);
+ return midAngle < Math.PI ? "start" : "end";
+ })
+ .attr("class", "tracker-pie-label");
+
+
+ function getPointsForConnectionLines(arcObj: any, i: number) {
+ let labelWidth = labelSizes[i].width;
+ let extLabelWidth = extLabelSizes[i].width;
+ let labelHidden = isLabelHidden(arcObj);
+ let midAngle = getMidAngle(arcObj);
+
+ let posLabel = arc.centroid(arcObj); // line insertion in the slice
+ let posMiddle = hiddenArc.centroid(arcObj); // line break: we use the other arc generator that has been built only for that
+ let posExtLabel = extlabelPos[i] || hiddenArc.centroid(arcObj); // Label position = almost the same as posB
+ posMiddle[1] = posExtLabel[1];
+ // console.log(labels[i]);
+ // console.log(`label/middle/extLabel: ${posLabel}/${posMiddle}/${posExtLabel}`);
+
+ let distMiddleToLabel = Math.sqrt(
+ (posMiddle[0] - posLabel[0]) ** 2 +
+ (posMiddle[1] - posLabel[1]) ** 2
+ );
+
+ if (labels[i] !== "" && !labelHidden) {
+ // shift posLabel, toward the middle point
+ posLabel[0] =
+ posLabel[0] +
+ ((posMiddle[0] - posLabel[0]) * labelWidth) / distMiddleToLabel;
+ posLabel[1] =
+ posLabel[1] +
+ ((posMiddle[1] - posLabel[1]) * labelWidth) / distMiddleToLabel;
+
+ // shift posExtLabel
+ posExtLabel[0] =posExtLabel[0] + (- 3) *
+ (midAngle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
+ }
+
+ distMiddleToLabel = Math.sqrt(
+ (posMiddle[0] - posLabel[0]) ** 2 +
+ (posMiddle[1] - posLabel[1]) ** 2
+ );
+
+ let distExtLabelToLabel = Math.sqrt(
+ (posExtLabel[0] - posLabel[0]) ** 2 +
+ (posExtLabel[1] - posLabel[1]) ** 2
+ );
+
+ if (distMiddleToLabel > distExtLabelToLabel) {
+ // console.log("two points");
+ return [posLabel, posExtLabel];
+ }
+ return [posLabel, posMiddle, posExtLabel];
+ }
+
+ // Add lines between sectors and external labels
+ let lines = sectorsGroup
+ .selectAll("line")
+ .data(pieValues)
+ .enter()
+ .append("polyline")
+ .attr("stroke", "black")
+ .style("fill", "none")
+ .attr("stroke-width", 1)
+ .attr("points", function (arcObj: any, i: number) {
+ if (showExtLabelOnlyIfNoLabel) {
+ if (labels[i] === "" || isLabelHidden(arcObj)) {
+ if (extLabels[i] !== "") {
+ return getPointsForConnectionLines(arcObj, i);
+ }
+ }
+ } else {
+ if (extLabels[i] !== "") {
+ return getPointsForConnectionLines(arcObj, i);
+ }
+ }
+ })
+ .attr("class", "tracker-axis");
}
export function renderPieChart(
@@ -158,12 +677,26 @@ export function renderPieChart(
// console.log(renderInfo);
if (!renderInfo || !pieInfo) return;
- return "Under construction";
-
+ // return "Under construction";
+
let chartElements: ChartElements = {};
chartElements = createAreas(chartElements, canvas, renderInfo, pieInfo);
+ // Set default dataColor if no dataColor provided
+ let defaultDataColor = d3.schemeSpectral[pieInfo.dataColor.length];
+ for (let i = 0; i < pieInfo.dataColor.length; i++) {
+ if (pieInfo.dataColor[i] === null) {
+ pieInfo.dataColor[i] = defaultDataColor[i];
+ }
+ }
+
renderTitle(canvas, chartElements, renderInfo, pieInfo);
renderPie(canvas, chartElements, renderInfo, pieInfo);
+
+ if (pieInfo.showLegend) {
+ renderLegend(canvas, chartElements, renderInfo, pieInfo);
+ }
+
+ setChartScale(canvas, chartElements, renderInfo);
}
diff --git a/src/rendering.ts b/src/rendering.ts
index 27e1d24a..8f4a711e 100644
--- a/src/rendering.ts
+++ b/src/rendering.ts
@@ -1,5 +1,5 @@
import * as d3 from "d3";
-import { Moment } from "moment";
+import { Moment, Duration } from "moment";
import {
Datasets,
DataPoint,
@@ -8,7 +8,7 @@ import {
Size,
Transform,
ChartElements,
- OutputType,
+ GraphType,
ValueType,
CommonChartInfo,
LineInfo,
@@ -25,107 +25,194 @@ import * as month from "./month";
import * as heatmap from "./heatmap";
import * as bullet from "./bullet";
import * as helper from "./helper";
-
-function getXTickInterval(datasets: Datasets) {
- let tickInterval;
- let days = datasets.getDates().length;
-
- if (days <= 15) {
- // number of ticks: 0-15
- tickInterval = d3.timeDay;
- } else if (days <= 4 * 15) {
- // number of ticks: 4-15
- tickInterval = d3.timeDay.every(4);
- } else if (days <= 7 * 15) {
- // number of ticks: 8-15
- tickInterval = d3.timeWeek;
- } else if (days <= 15 * 30) {
- // number of ticks: 4-15
- tickInterval = d3.timeMonth;
- } else if (days <= 15 * 60) {
- // number of ticks: 8-15
- tickInterval = d3.timeMonth.every(2);
+import { sprintf } from "sprintf-js";
+
+function getXTickValues(
+ dates: Moment[],
+ interval: Duration
+): [Array, d3.TimeInterval] {
+ // The input interval could be null,
+ // generate tick values even if interval is null
+
+ // console.log(interval);
+
+ let tickValues: Array = [];
+ let tickInterval = null;
+
+ // y values are time values
+ if (interval) {
+ let firstDate = dates[0];
+ let lastDate = dates[dates.length - 1];
+ tickValues = d3.timeDay.range(
+ firstDate.toDate(),
+ lastDate.toDate(),
+ interval.asDays()
+ );
} else {
- tickInterval = d3.timeYear;
+ let days = dates.length;
+ if (days <= 15) {
+ // number of ticks: 0-15
+ tickInterval = d3.timeDay;
+ } else if (days <= 4 * 15) {
+ // number of ticks: 4-15
+ tickInterval = d3.timeDay.every(4);
+ } else if (days <= 7 * 15) {
+ // number of ticks: 8-15
+ tickInterval = d3.timeWeek;
+ } else if (days <= 15 * 30) {
+ // number of ticks: 4-15
+ tickInterval = d3.timeMonth;
+ } else if (days <= 15 * 60) {
+ // number of ticks: 8-15
+ tickInterval = d3.timeMonth.every(2);
+ } else {
+ tickInterval = d3.timeYear;
+ }
}
- return tickInterval;
+ return [tickValues, tickInterval];
}
-function getXTickFormat(datasets: Datasets) {
- let tickFormat;
- let days = datasets.getDates().length;
-
- if (days <= 15) {
- // number of ticks: 0-15
- tickFormat = d3.timeFormat("%y-%m-%d");
- } else if (days <= 4 * 15) {
- // number of ticks: 4-15
- tickFormat = d3.timeFormat("%y-%m-%d");
- } else if (days <= 7 * 15) {
- // number of ticks: 8-15
- tickFormat = d3.timeFormat("%y-%m-%d");
- } else if (days <= 15 * 30) {
- // number of ticks: 4-15
- tickFormat = d3.timeFormat("%y %b");
- } else if (days <= 15 * 60) {
- // number of ticks: 8-15
- tickFormat = d3.timeFormat("%y %b");
+function getXTickLabelFormat(dates: Moment[], inTickLabelFormat: string) {
+ if (inTickLabelFormat) {
+ function fnTickLabelFormat(date: Date): string {
+ return helper.dateToStr(window.moment(date), inTickLabelFormat);
+ }
+ return fnTickLabelFormat;
} else {
- tickFormat = d3.timeFormat("%Y");
- }
+ let tickLabelFormat = null;
+ let days = dates.length;
+
+ if (days <= 15) {
+ // number of ticks: 0-15
+ tickLabelFormat = d3.timeFormat("%y-%m-%d");
+ } else if (days <= 4 * 15) {
+ // number of ticks: 4-15
+ tickLabelFormat = d3.timeFormat("%y-%m-%d");
+ } else if (days <= 7 * 15) {
+ // number of ticks: 8-15
+ tickLabelFormat = d3.timeFormat("%y-%m-%d");
+ } else if (days <= 15 * 30) {
+ // number of ticks: 4-15
+ tickLabelFormat = d3.timeFormat("%y %b");
+ } else if (days <= 15 * 60) {
+ // number of ticks: 8-15
+ tickLabelFormat = d3.timeFormat("%y %b");
+ } else {
+ tickLabelFormat = d3.timeFormat("%Y");
+ }
- return tickFormat;
+ return tickLabelFormat;
+ }
}
-function getYTickValues(yLower: number, yUpper: number) {
- // currently used for time value tick only, value in seconds
+function getYTickValues(
+ yLower: number,
+ yUpper: number,
+ interval: number | Duration,
+ isTimeValue = false
+) {
+ // The input interval could be null,
+ // generate tick values for time values even if interval is null
+
+ // console.log(interval);
+ // console.log(isTimeValue);
+
const absExtent = Math.abs(yUpper - yLower);
- let tickValues = [];
- if (absExtent > 5 * 60 * 60) {
- // extent over than 5 hours
- // tick on the hour
- yLower = Math.floor(yLower / 3600) * 3600;
- yUpper = Math.ceil(yUpper / 3600) * 3600;
-
- tickValues = d3.range(yLower, yUpper, 3600);
+ let tickValues: Array = [];
+
+ if (!isTimeValue) {
+ // y values are numbers
+ if (interval && typeof interval === "number") {
+ // !==null && !== 0
+ tickValues = d3.range(yLower, yUpper, interval);
+ }
} else {
- // tick on the half hour
- yLower = Math.floor(yLower / 1800) * 1800;
- yUpper = Math.ceil(yUpper / 1800) * 1800;
+ // y values are time values
+ if (interval && window.moment.isDuration(interval)) {
+ let intervalInSeconds = Math.abs(interval.asSeconds());
+ tickValues = d3.range(yLower, yUpper, intervalInSeconds);
+ } else {
+ // auto interval for time values
+ if (absExtent > 5 * 60 * 60) {
+ // extent over than 5 hours
+ // tick on the hour
+ yLower = Math.floor(yLower / 3600) * 3600;
+ yUpper = Math.ceil(yUpper / 3600) * 3600;
+
+ tickValues = d3.range(yLower, yUpper, 3600);
+ } else {
+ // tick on the half hour
+ yLower = Math.floor(yLower / 1800) * 1800;
+ yUpper = Math.ceil(yUpper / 1800) * 1800;
- tickValues = d3.range(yLower, yUpper, 1800);
+ tickValues = d3.range(yLower, yUpper, 1800);
+ }
+ }
}
+ if (tickValues.length === 0) return null;
return tickValues;
}
-function getYTickFormat(yLower: number, yUpper: number, skip: boolean = true) {
- // currently used for time value tick only
+function getYTickLabelFormat(
+ yLower: number,
+ yUpper: number,
+ inTickLabelFormat: string,
+ isTimeValue = false
+) {
// return a function convert value to time string
- function tickFormat(value: number): string {
- const absExtent = Math.abs(yUpper - yLower);
- let dayStart = window.moment("00:00", "HH:mm", true);
- let tickTime = dayStart.add(value, "seconds");
- let format = tickTime.format("HH:mm");
- if (skip && absExtent > 12 * 60 * 60) {
- let devHour = (value - yLower) / 3600;
- let interleave = devHour % 2;
- if (value <= yLower) {
- format = "";
- } else if (value >= yUpper) {
- format = "";
- } else if (interleave > 1.0) {
- format = tickTime.format("HH:mm");
- } else {
- format = "";
+
+ if (!isTimeValue) {
+ if (inTickLabelFormat) {
+ function tickFormat(value: number): string {
+ let strValue = sprintf("%" + inTickLabelFormat, value);
+ return strValue;
}
+
+ return tickFormat;
}
+ return d3.tickFormat(yLower, yUpper, 10);
+ } else {
+ // values in seconds
+ if (inTickLabelFormat) {
+ function fnTickLabelFormat(value: number): string {
+ let dayStart = window.moment("00:00", "HH:mm", true);
+ let tickTime = dayStart.add(value, "seconds");
+ let format = tickTime.format(inTickLabelFormat);
+
+ let devHour = (value - yLower) / 3600;
+ let interleave = devHour % 2;
+
+ return format;
+ }
+ return fnTickLabelFormat;
+ } else {
+ function fnTickLabelFormat(value: number): string {
+ const absExtent = Math.abs(yUpper - yLower);
+ let dayStart = window.moment("00:00", "HH:mm", true);
+ let tickTime = dayStart.add(value, "seconds");
+ let format = tickTime.format("HH:mm");
+ // console.log(`yLower/yUpper: ${yLower}/${yUpper}`)
+ // console.log(`value/extent/inter:${value}/${absExtent}/${(value-yLower)/3600}`);
+
+ // auto interleave if extent over 12 hours
+ if (absExtent > 12 * 60 * 60) {
+ let devHour = (value - yLower) / 3600;
+ let interleave = devHour % 2;
+ if (value < yLower || value > yUpper || interleave < 1.0) {
+ format = "";
+ }
+ }
- return format;
+ return format;
+ }
+
+ return fnTickLabelFormat;
+ }
}
- return tickFormat;
+ return null;
}
export function render(canvas: HTMLElement, renderInfo: RenderInfo) {
@@ -136,8 +223,12 @@ export function render(canvas: HTMLElement, renderInfo: RenderInfo) {
for (let dataset of renderInfo.datasets) {
if (dataset.getQuery().usedAsXDataset) continue;
// valueShift
- if (renderInfo.valueShift[dataset.getId()] !== null) {
- dataset.shift(renderInfo.valueShift[dataset.getId()]);
+ let shiftAmount = renderInfo.valueShift[dataset.getId()];
+ if (shiftAmount !== null && shiftAmount !== 0) {
+ dataset.shift(
+ shiftAmount,
+ renderInfo.shiftOnlyValueLargerThan[dataset.getId()]
+ );
}
// penalty
if (renderInfo.penalty[dataset.getId()] !== null) {
@@ -148,6 +239,18 @@ export function render(canvas: HTMLElement, renderInfo: RenderInfo) {
dataset.accumulateValues();
}
}
+ // stack
+ if (renderInfo.stack) {
+ // Traverse the datasets, and add up the values from each dataset.
+ let lastDataset = null;
+ for (let dataset of renderInfo.datasets) {
+ if (dataset.getQuery().usedAsXDataset) continue;
+ if (lastDataset) {
+ dataset.shiftByDataset(lastDataset);
+ }
+ lastDataset = dataset;
+ }
+ }
for (let lineInfo of renderInfo.line) {
let ret = renderLineChart(canvas, renderInfo, lineInfo);
@@ -204,19 +307,48 @@ function renderXAxis(
let datasets = renderInfo.datasets;
let xDomain = d3.extent(datasets.getDates());
+ if (chartInfo instanceof BarInfo && chartInfo.xAxisPadding !== null) {
+ let xAxisPaddingDuration = helper.parseDurationString(
+ chartInfo.xAxisPadding
+ );
+ if (xAxisPaddingDuration !== null) {
+ xDomain = [
+ xDomain[0].clone().subtract(xAxisPaddingDuration.asHours(), 'hours'),
+ xDomain[1].clone().add(xAxisPaddingDuration.asHours(), 'hours'),
+ ];
+ }
+ }
+ // console.log(xDomain);
let xScale = d3
.scaleTime()
.domain(xDomain)
.range([0, renderInfo.dataAreaSize.width]);
chartElements["xScale"] = xScale;
- let tickInterval = getXTickInterval(datasets);
- let tickFormat = getXTickFormat(datasets);
+ let tickIntervalInDuration = helper.parseDurationString(
+ chartInfo.xAxisTickInterval
+ );
+
+ let [tickValues, tickInterval] = getXTickValues(
+ datasets.getDates(),
+ tickIntervalInDuration
+ );
+ let tickFormat = getXTickLabelFormat(
+ datasets.getDates(),
+ chartInfo.xAxisTickLabelFormat
+ );
+
+ let xAxisGen = d3.axisBottom(xScale);
+
+ if (tickValues && tickValues.length !== 0) {
+ xAxisGen.tickValues(tickValues);
+ } else if (tickInterval) {
+ xAxisGen.ticks(tickInterval);
+ }
+ if (tickFormat) {
+ xAxisGen.tickFormat(tickFormat);
+ }
- let xAxisGen = d3
- .axisBottom(xScale)
- .ticks(tickInterval)
- .tickFormat(tickFormat);
let xAxis = chartElements.dataArea // axis includes ticks
.append("g")
.attr("id", "xAxis")
@@ -368,7 +500,7 @@ function renderYAxis(
yUpper = yMax + yExtent * 0.2;
}
// if it is bar chart, zero must be contained in the range
- if (chartInfo.GetChartType() === OutputType.Bar) {
+ if (chartInfo.GetGraphType() === GraphType.Bar) {
if (yUpper < 0.0) {
yUpper = 0;
}
@@ -413,10 +545,26 @@ function renderYAxis(
}
let yAxisUnitText = "";
+ let yAxisTickInterval = null;
+ let yAxisTickLabelFormat = null;
if (yAxisLocation === "left") {
yAxisUnitText = chartInfo.yAxisUnit[0];
+ yAxisTickInterval = chartInfo.yAxisTickInterval[0]; // string
+ yAxisTickLabelFormat = chartInfo.yAxisTickLabelFormat[0];
} else if (yAxisLocation === "right") {
yAxisUnitText = chartInfo.yAxisUnit[1];
+ yAxisTickInterval = chartInfo.yAxisTickInterval[1]; // string
+ yAxisTickLabelFormat = chartInfo.yAxisTickLabelFormat[1];
+ }
+ // get interval from string
+ let tickInterval = null;
+ if (valueIsTime) {
+ tickInterval = helper.parseDurationString(yAxisTickInterval);
+ } else {
+ tickInterval = parseFloat(yAxisTickInterval);
+ if (!Number.isNumber(tickInterval) || Number.isNaN(tickInterval)) {
+ tickInterval = null;
+ }
}
let yAxisGen;
@@ -425,10 +573,25 @@ function renderYAxis(
} else if (yAxisLocation === "right") {
yAxisGen = d3.axisRight(yScale);
}
- if (yAxisGen && valueIsTime) {
- let tickFormat = getYTickFormat(yLower, yUpper);
- let tickValues = getYTickValues(yLower, yUpper);
- yAxisGen.tickValues(tickValues).tickFormat(tickFormat);
+ if (yAxisGen) {
+ let tickLabelFormat = getYTickLabelFormat(
+ yLower,
+ yUpper,
+ yAxisTickLabelFormat,
+ valueIsTime
+ );
+ if (tickLabelFormat) {
+ yAxisGen.tickFormat(tickLabelFormat);
+ }
+ let tickValues = getYTickValues(
+ yLower,
+ yUpper,
+ tickInterval,
+ valueIsTime
+ );
+ if (tickValues) {
+ yAxisGen.tickValues(tickValues);
+ }
}
let yAxis = chartElements.dataArea
@@ -466,22 +629,20 @@ function renderYAxis(
}
// Get max tick label width
- let yTickFormat = d3.tickFormat(yLower, yUpper, 10);
- if (valueIsTime) {
- yTickFormat = getYTickFormat(yLower, yUpper, false);
+ let maxTickLabelWidth = 0;
+ for (let label of yAxisTickLabels) {
+ // console.log(label.textContent);
+ if (label.textContent) {
+ let labelSize = helper.measureTextSize(
+ label.textContent,
+ "tracker-axis-label"
+ );
+ if (labelSize.width > maxTickLabelWidth) {
+ maxTickLabelWidth = labelSize.width;
+ }
+ }
}
- let yLowerLabelSize = helper.measureTextSize(
- yTickFormat(yLower),
- "tracker-axis-label"
- );
- let yUpperLabelSize = helper.measureTextSize(
- yTickFormat(yUpper),
- "tracker-axis-label"
- );
- let maxTickLabelWidth = Math.max(
- yLowerLabelSize.width,
- yUpperLabelSize.width
- );
+ // console.log(maxTickLabelWidth);
if (yAxisUnitText !== "") {
yAxisLabelText += " (" + yAxisUnitText + ")";
@@ -649,73 +810,113 @@ function renderPoints(
}
if (lineInfo.allowInspectData) {
- let tooltip = chartElements.svg.append("g").style("opacity", 0);
- let tooltipBg = tooltip
- .append("rect")
- .attr("width", renderInfo.tooltipSize.width)
- .attr("height", renderInfo.tooltipSize.height)
- .attr("class", "tracker-tooltip");
- let tooltipLabel = tooltip
- .append("text")
- .attr("width", renderInfo.tooltipSize.width)
- .attr("height", renderInfo.tooltipSize.height)
- .attr("class", "tracker-tooltip-label");
- let tooltipLabelDate = tooltipLabel
- .append("tspan")
- .attr("x", 4)
- .attr("y", (renderInfo.tooltipSize.height / 5) * 2);
- let tooltipLabelValue = tooltipLabel
- .append("tspan")
- .attr("x", 4)
- .attr("y", (renderInfo.tooltipSize.height / 5) * 4);
-
- dots.on("mouseenter", function (event: any) {
- // Date
- tooltipLabelDate.text("date:" + d3.select(this).attr("date"));
- // Value
- let valueType = d3.select(this).attr("valueType");
- let strValue = d3.select(this).attr("value");
- if (valueType === "Time") {
- let dayStart = window.moment("00:00", "HH:mm", true);
- let tickTime = dayStart.add(
- parseFloat(strValue),
- "seconds"
- );
- let dateValue = tickTime.format("HH:mm");
- tooltipLabelValue.text("value:" + dateValue);
- } else {
- tooltipLabelValue.text("value:" + strValue);
- }
-
- const [x, y] = d3.pointer(event);
- if (x < renderInfo.dataAreaSize.width / 2) {
- tooltip.attr(
- "transform",
- "translate(" +
- (x + renderInfo.tooltipSize.width * 1.3) +
- "," +
- (y - renderInfo.tooltipSize.height * 1.0) +
- ")"
- );
- } else {
- tooltip.attr(
- "transform",
- "translate(" +
- (x - renderInfo.tooltipSize.width * 0.0) +
- "," +
- (y - renderInfo.tooltipSize.height * 1.0) +
- ")"
- );
- }
-
- tooltip.transition().duration(200).style("opacity", 1);
- }).on("mouseleave", function () {
- tooltip.transition().duration(500).style("opacity", 0);
- });
+ renderTooltip(dots, chartElements, renderInfo);
}
}
}
+function renderTooltip(
+ targetElements: any,
+ chartElements: ChartElements,
+ renderInfo: RenderInfo
+) {
+ let tooltip = chartElements.dataArea.append("svg").style("opacity", 0);
+ let tooltipBg = tooltip.append("rect").attr("x", 0).attr("y", 0);
+ let tooltipLabel = tooltip.append("text");
+ let tooltipLabelDate = tooltipLabel
+ .append("tspan")
+ .attr("class", "tracker-tooltip-label");
+ let tooltipLabelValue = tooltipLabel
+ .append("tspan")
+ .attr("class", "tracker-tooltip-label");
+
+ let xSpacing = 3;
+ let ySpacing = 3;
+
+ targetElements
+ .on("mouseenter", function (event: any) {
+ const [x, y] = d3.pointer(event);
+ let tooltipBgWidth = 0;
+ let tooltipBgHeight = 0;
+ // Date
+ let labelDateText = "date: " + d3.select(this).attr("date");
+ // labelDateText = x.toString();// debug
+ let labelDateSize = helper.measureTextSize(
+ labelDateText,
+ "tracker-tooltip-label"
+ );
+ tooltipLabelDate.text(labelDateText);
+ if (labelDateSize.width > tooltipBgWidth) {
+ tooltipBgWidth = labelDateSize.width;
+ }
+ tooltipBgHeight += labelDateSize.height;
+ tooltipLabelDate.attr("x", xSpacing).attr("y", tooltipBgHeight);
+
+ // Value
+ let labelValueText = "value: ";
+ let valueType = d3.select(this).attr("valueType");
+ let strValue = d3.select(this).attr("value");
+ // strValue += y.toString();//debug
+ if (valueType === "Time") {
+ let dayStart = window.moment("00:00", "HH:mm", true);
+ let tickTime = dayStart.add(parseFloat(strValue), "seconds");
+ let dateValue = tickTime.format("HH:mm");
+ labelValueText += dateValue;
+ tooltipLabelValue.text(labelValueText);
+ } else {
+ labelValueText += strValue;
+ tooltipLabelValue.text(labelValueText);
+ }
+ let labelValueSize = helper.measureTextSize(
+ labelValueText,
+ "tracker-tooltip-label"
+ );
+ if (labelValueSize.width > tooltipBgWidth) {
+ tooltipBgWidth = labelValueSize.width;
+ }
+ tooltipBgHeight += ySpacing + labelValueSize.height;
+ tooltipLabelValue.attr("x", xSpacing).attr("y", tooltipBgHeight);
+
+ tooltipBgWidth += 2 * xSpacing;
+ tooltipBgHeight += 2 * ySpacing;
+ tooltipLabel
+ .attr("width", tooltipBgWidth)
+ .attr("height", tooltipBgHeight);
+
+ tooltipBg
+ .attr("width", tooltipBgWidth)
+ .attr("height", tooltipBgHeight)
+ .attr("class", "tracker-tooltip");
+
+ let tooltipPosX = x;
+ let tooltipPosY = y;
+ let tooltipXOffset = 12;
+ let tooltipYOffset = 12;
+ if (
+ x + tooltipXOffset + tooltipBgWidth >
+ renderInfo.dataAreaSize.width
+ ) {
+ // move tooltip to left
+ tooltipPosX = x - tooltipBgWidth - tooltipXOffset;
+ } else {
+ // default at the right side
+ tooltipPosX = x + tooltipXOffset;
+ }
+ if (y - tooltipYOffset - tooltipBgHeight < 0) {
+ // down side
+ tooltipPosY = y + tooltipYOffset;
+ } else {
+ // default move to up side
+ tooltipPosY = y - tooltipYOffset - tooltipBgHeight;
+ }
+ tooltip.attr("x", tooltipPosX).attr("y", tooltipPosY);
+ tooltip.transition().duration(200).style("opacity", 1);
+ })
+ .on("mouseleave", function () {
+ tooltip.transition().duration(500).style("opacity", 0);
+ });
+}
+
function renderBar(
chartElements: ChartElements,
renderInfo: RenderInfo,
@@ -734,10 +935,17 @@ function renderBar(
let barGap = 1;
let barSetWidth = renderInfo.dataAreaSize.width / dataset.getLength();
let barWidth = barSetWidth;
+ let currentDiaplayInd = currBarSet;
+ let totalDiaplaySet = totalNumOfBarSets;
if (barSetWidth - barGap > 0) {
barWidth = barSetWidth - barGap;
}
- barWidth = barWidth / totalNumOfBarSets;
+ if (!renderInfo.stack) {
+ barWidth = barWidth / totalNumOfBarSets;
+ } else {
+ currentDiaplayInd = 0;
+ totalDiaplaySet = 1;
+ }
let portionLeft = (currBarSet + 1) / totalNumOfBarSets;
@@ -758,13 +966,13 @@ function renderBar(
.enter()
.append("rect")
.attr("x", function (p: DataPoint, i: number) {
- if (i === 0) {
- let portionVisible = currBarSet + 1 - totalNumOfBarSets / 2.0;
+ if (i === 0 && barInfo.xAxisPadding === null) {
+ let portionVisible = currentDiaplayInd + 1 - totalDiaplaySet / 2.0;
if (portionVisible < 1.0) {
return (
chartElements.xScale(p.date) -
barSetWidth / 2.0 +
- currBarSet * barWidth +
+ currentDiaplayInd * barWidth +
portionVisible * barWidth
);
}
@@ -772,24 +980,24 @@ function renderBar(
return (
chartElements.xScale(p.date) -
barSetWidth / 2.0 +
- currBarSet * barWidth
+ currentDiaplayInd * barWidth
);
})
.attr("y", function (p: DataPoint) {
return yScale(Math.max(p.value, 0));
})
.attr("width", function (p: DataPoint, i: number) {
- if (i === 0) {
- let portionVisible = currBarSet + 1 - totalNumOfBarSets / 2.0;
+ if (i === 0 && barInfo.xAxisPadding === null) {
+ let portionVisible = currentDiaplayInd + 1 - totalDiaplaySet / 2.0;
if (portionVisible < 0.0) {
return 0.0;
} else if (portionVisible < 1.0) {
return barWidth * portionVisible;
}
return barWidth;
- } else if (i === dataset.getLength() - 1) {
+ } else if (i === dataset.getLength() - 1 && barInfo.xAxisPadding === null) {
let portionVisible =
- 1.0 - (currBarSet + 1 - totalNumOfBarSets / 2.0);
+ 1.0 - (currentDiaplayInd + 1 - totalDiaplaySet / 2.0);
if (portionVisible < 0.0) {
return 0.0;
} else if (portionVisible < 1.0) {
@@ -816,8 +1024,8 @@ function renderLegend(
renderInfo: RenderInfo,
chartInfo: CommonChartInfo
) {
- // console.log(renderInfo.legendPosition);
- // console.log(renderInfo.legendOrientation);
+ // console.log(chartInfo.legendPosition);
+ // console.log(chartInfo.legendOrientation);
// Get chart elements
let svg = chartElements.svg;
@@ -970,7 +1178,7 @@ function renderLegend(
let firstLabelY = firstMarkerY;
if (chartInfo.legendOrientation === "vertical") {
- if (chartInfo.GetChartType() === OutputType.Line) {
+ if (chartInfo.GetGraphType() === GraphType.Line) {
// lines
legend
.selectAll("markers")
@@ -1023,7 +1231,7 @@ function renderLegend(
if (xDatasetIds.includes(i)) return;
return (chartInfo as LineInfo).pointColor[i];
});
- } else if (chartInfo.GetChartType() === OutputType.Bar) {
+ } else if (chartInfo.GetGraphType() === GraphType.Bar) {
// bars
legend
.selectAll("markers")
@@ -1067,21 +1275,19 @@ function renderLegend(
.style("alignment-baseline", "middle")
.attr("class", "tracker-legend-label");
- if (chartInfo.GetChartType() === OutputType.Line) {
+ if (chartInfo.GetGraphType() === GraphType.Line) {
nameLabels.style("fill", function (name: string, i: number) {
if (xDatasetIds.includes(i)) return;
return (chartInfo as LineInfo).lineColor[i];
});
- } else if (chartInfo.GetChartType() === OutputType.Bar) {
+ } else if (chartInfo.GetGraphType() === GraphType.Bar) {
nameLabels.style("fill", function (name: string, i: number) {
if (xDatasetIds.includes(i)) return;
return (chartInfo as BarInfo).barColor[i];
});
}
} else if (chartInfo.legendOrientation === "horizontal") {
- let currRenderPosX = 0.0;
- let currRenderPosX2 = 0.0;
- if (chartInfo.GetChartType() === OutputType.Line) {
+ if (chartInfo.GetGraphType() === GraphType.Line) {
// lines
legend
.selectAll("markers")
@@ -1089,36 +1295,30 @@ function renderLegend(
.enter()
.append("line")
.attr("x1", function (name: string, i: number) {
- let numElemsExcluded = xDatasetIds.filter((id) => {
- return id < i;
- }).length;
- i = i - numElemsExcluded;
- if (i === 0) {
- currRenderPosX = firstMarkerX;
- } else {
- currRenderPosX +=
- nameSizes[i].width +
- xSpacing +
- markerWidth +
- xSpacing;
+ let posX = xSpacing;
+ for (let [ind, size] of nameSizes.entries()) {
+ if (xDatasetIds.includes(ind)) continue;
+ if (ind < i) {
+ posX +=
+ markerWidth + xSpacing + size.width + xSpacing;
+ } else {
+ break;
+ }
}
- return currRenderPosX;
+ return posX;
})
.attr("x2", function (name: string, i: number) {
- let numElemsExcluded = xDatasetIds.filter((id) => {
- return id < i;
- }).length;
- i = i - numElemsExcluded;
- if (i === 0) {
- currRenderPosX2 = firstMarkerX + markerWidth;
- } else {
- currRenderPosX2 +=
- nameSizes[i].width +
- xSpacing +
- markerWidth +
- xSpacing;
+ let posX = xSpacing + markerWidth;
+ for (let [ind, size] of nameSizes.entries()) {
+ if (xDatasetIds.includes(ind)) continue;
+ if (ind < i) {
+ posX +=
+ xSpacing + size.width + xSpacing + markerWidth;
+ } else {
+ break;
+ }
}
- return currRenderPosX2;
+ return posX;
})
.attr("y1", firstMarkerY)
.attr("y2", firstMarkerY)
@@ -1128,27 +1328,27 @@ function renderLegend(
});
// points
- currRenderPosX = 0.0;
legend
.selectAll("markers")
.data(names)
.enter()
.append("circle")
.attr("cx", function (name: string, i: number) {
- let numElemsExcluded = xDatasetIds.filter((id) => {
- return id < i;
- }).length;
- i = i - numElemsExcluded;
- if (i === 0) {
- currRenderPosX = firstMarkerX + markerWidth / 2.0;
- } else {
- currRenderPosX +=
- nameSizes[i].width +
- xSpacing +
- markerWidth +
- xSpacing;
+ let posX = xSpacing + markerWidth / 2.0;
+ for (let [ind, size] of nameSizes.entries()) {
+ if (xDatasetIds.includes(ind)) continue;
+ if (ind < i) {
+ posX +=
+ markerWidth / 2.0 +
+ xSpacing +
+ size.width +
+ xSpacing +
+ markerWidth / 2.0;
+ } else {
+ break;
+ }
}
- return currRenderPosX;
+ return posX;
})
.attr("cy", firstMarkerY)
.attr("r", function (name: string, i: number) {
@@ -1162,9 +1362,8 @@ function renderLegend(
if (xDatasetIds.includes(i)) return;
return (chartInfo as LineInfo).pointColor[i];
});
- } else if (chartInfo.GetChartType() === OutputType.Bar) {
+ } else if (chartInfo.GetGraphType() === GraphType.Bar) {
// bars
- currRenderPosX = 0.0;
legend
.selectAll("markers")
.data(
@@ -1175,20 +1374,17 @@ function renderLegend(
.enter()
.append("rect")
.attr("x", function (name: string, i: number) {
- let numElemsExcluded = xDatasetIds.filter((id) => {
- return id < i;
- }).length;
- i = i - numElemsExcluded;
- if (i === 0) {
- currRenderPosX = firstMarkerX;
- } else {
- currRenderPosX +=
- nameSizes[i].width +
- xSpacing +
- markerWidth +
- xSpacing;
+ let posX = xSpacing;
+ for (let [ind, size] of nameSizes.entries()) {
+ if (xDatasetIds.includes(ind)) continue;
+ if (ind < i) {
+ posX +=
+ markerWidth + xSpacing + size.width + xSpacing;
+ } else {
+ break;
+ }
}
- return currRenderPosX;
+ return posX;
})
.attr("y", firstMarkerY - nameHeight / 2.0)
.attr("width", markerWidth)
@@ -1200,24 +1396,22 @@ function renderLegend(
}
// names
- currRenderPosX = 0.0;
let nameLabels = legend
.selectAll("labels")
.data(names)
.enter()
.append("text")
.attr("x", function (name: string, i: number) {
- let numElemsExcluded = xDatasetIds.filter((id) => {
- return id < i;
- }).length;
- i = i - numElemsExcluded;
- if (i === 0) {
- currRenderPosX = firstLabelX;
- } else {
- currRenderPosX +=
- nameSizes[i].width + xSpacing + markerWidth + xSpacing;
+ let posX = xSpacing + markerWidth + xSpacing;
+ for (let [ind, size] of nameSizes.entries()) {
+ if (xDatasetIds.includes(ind)) continue;
+ if (ind < i) {
+ posX += size.width + xSpacing + markerWidth + xSpacing;
+ } else {
+ break;
+ }
}
- return currRenderPosX;
+ return posX;
})
.attr("y", firstLabelY)
.text(function (name: string, i: number) {
@@ -1227,12 +1421,12 @@ function renderLegend(
.style("alignment-baseline", "middle")
.attr("class", "tracker-legend-label");
- if (chartInfo.GetChartType() === OutputType.Line) {
+ if (chartInfo.GetGraphType() === GraphType.Line) {
nameLabels.style("fill", function (name: string, i: number) {
if (xDatasetIds.includes(i)) return;
return (chartInfo as LineInfo).lineColor[i];
});
- } else if (chartInfo.GetChartType() === OutputType.Bar) {
+ } else if (chartInfo.GetGraphType() === GraphType.Bar) {
nameLabels.style("fill", function (name: string, i: number) {
if (xDatasetIds.includes(i)) return;
return (chartInfo as BarInfo).barColor[i];
@@ -1463,15 +1657,30 @@ function renderBarChart(
let datasetOnLeftYAxis = [];
let datasetOnRightYAxis = [];
let xDatasetIds = renderInfo.datasets.getXDatasetIds();
- for (let ind = 0; ind < barInfo.yAxisLocation.length; ind++) {
- if (xDatasetIds.includes(ind)) continue;
- let yAxisLocation = barInfo.yAxisLocation[ind];
- if (yAxisLocation.toLowerCase() === "left") {
- datasetOnLeftYAxis.push(ind);
- } else if (yAxisLocation.toLocaleLowerCase() === "right") {
- // right
- datasetOnRightYAxis.push(ind);
+ if (renderInfo.stack) {
+ for (let ind = barInfo.yAxisLocation.length - 1; ind >= 0; ind--) {
+ if (xDatasetIds.includes(ind)) continue;
+ let yAxisLocation = barInfo.yAxisLocation[ind];
+ if (yAxisLocation.toLowerCase() === "left") {
+ datasetOnLeftYAxis.push(ind);
+ } else if (yAxisLocation.toLocaleLowerCase() === "right") {
+ // right
+ datasetOnRightYAxis.push(ind);
+ }
+ }
+
+ } else {
+ for (let ind = 0; ind < barInfo.yAxisLocation.length; ind++) {
+ if (xDatasetIds.includes(ind)) continue;
+ let yAxisLocation = barInfo.yAxisLocation[ind];
+ if (yAxisLocation.toLowerCase() === "left") {
+ datasetOnLeftYAxis.push(ind);
+ } else if (yAxisLocation.toLocaleLowerCase() === "right") {
+ // right
+ datasetOnRightYAxis.push(ind);
+ }
}
+
}
let retRenderLeftYAxis = renderYAxis(
diff --git a/src/settings.ts b/src/settings.ts
index 81375be5..bd23a2e4 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -27,7 +27,7 @@ export class TrackerSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName("Default folder location")
.setDesc(
- "Files in this folder will be parsed and used as input data of the tracker plugin.\nYou can also override it using 'folder' argument int the tracker codeblock."
+ "Files in this folder will be parsed and used as input data of the tracker plugin.\nYou can also override it using 'folder' argument in the tracker codeblock."
)
.addText((text) =>
text
@@ -42,7 +42,7 @@ export class TrackerSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName("Default date format")
.setDesc(
- "This format is used to parse the date in your diary title.\nYou can also override it using 'date-format' argument in the tracker codeblock."
+ "This format is used to parse the date in your diary title.\nYou can also override it using 'dateFormat' argument in the tracker codeblock."
)
.addText((text) =>
text
diff --git a/src/summary.ts b/src/summary.ts
index 0a138650..053155cd 100644
--- a/src/summary.ts
+++ b/src/summary.ts
@@ -15,6 +15,7 @@ export function renderSummary(
// console.log(renderInfo);
if (!renderInfo || !summaryInfo) return;
+ // console.log(summaryInfo.template);
let outputSummary = "";
if (checkSummaryTemplateValid(summaryInfo.template)) {
outputSummary = summaryInfo.template;
@@ -22,12 +23,20 @@ export function renderSummary(
return "Invalid summary template";
}
- outputSummary = expr.resolveTemplate(outputSummary, renderInfo);
+ let retResolvedTemplate = expr.resolveTemplate(outputSummary, renderInfo);
+ // console.log(retResolvedTemplate);
+ if (retResolvedTemplate.startsWith("Error:")) {
+ return retResolvedTemplate;
+ }
+ outputSummary = retResolvedTemplate;
+
if (outputSummary !== "") {
let textBlock = d3.select(canvas).append("div");
- if (outputSummary.includes("\n")) {
- let outputLines = outputSummary.split("\n");
+ if (outputSummary.includes("\n") || outputSummary.includes("\\n")) {
+ let outputLines = outputSummary.split(/(\n|\\n)/);
+ // console.log(outputLines);
for (let outputLine of outputLines) {
+ if (outputLine !== "\n" && outputLine !== "\\n")
textBlock.append("div").text(outputLine);
}
} else {
diff --git a/styles.css b/styles.css
index ca9163a8..9cd8b4ca 100644
--- a/styles.css
+++ b/styles.css
@@ -159,4 +159,24 @@
fill: none;
stroke: "red";
stroke-width: 2px;
+}
+
+.tracker-month-label {
+ fill: var(--color-axis-label);
+ text-anchor: middle;
+ font-size: 14px;
+ stroke: none;
+}
+
+.tracker-month-annotation {
+ fill: var(--color-axis-label);
+ text-anchor: middle;
+ font-size: 10px;
+ stroke: none;
+}
+
+.tracker-pie-label {
+ fill: var(--color-tick-label);
+ stroke: none;
+ font-size: 12px;
}
\ No newline at end of file
diff --git a/test/date-comparison-methods.test.ts b/test/date-comparison-methods.test.ts
new file mode 100644
index 00000000..d078a6b5
--- /dev/null
+++ b/test/date-comparison-methods.test.ts
@@ -0,0 +1,86 @@
+/**
+ * Test to compare different date comparison methods
+ *
+ * Question: Is valueOf() the best method, or should we use isBefore/isAfter?
+ */
+
+import * as helper from '../src/helper';
+import moment from 'moment';
+
+describe('Date Comparison Methods', () => {
+ beforeAll(() => {
+ if (typeof (global as any).window === 'undefined') {
+ (global as any).window = {};
+ }
+ (global as any).window.moment = moment;
+ });
+
+ describe('valueOf() vs isBefore/isAfter', () => {
+ it('should produce same results with valueOf() and isBefore/isAfter', () => {
+ const startDate = helper.strToDate('20240201', 'YYYYMMDD');
+ const endDate = helper.strToDate('20240203', 'YYYYMMDD');
+ const testDate = helper.strToDate('20240202', 'YYYYMMDD');
+
+ // Method 1: valueOf() (current implementation)
+ const inRangeValueOf =
+ testDate.valueOf() >= startDate.valueOf() &&
+ testDate.valueOf() <= endDate.valueOf();
+
+ // Method 2: isBefore/isAfter with 'day' granularity
+ const inRangeMoment =
+ !testDate.isBefore(startDate, 'day') &&
+ !testDate.isAfter(endDate, 'day');
+
+ // Method 3: isSameOrAfter/isSameOrBefore
+ const inRangeSame =
+ testDate.isSameOrAfter(startDate, 'day') &&
+ testDate.isSameOrBefore(endDate, 'day');
+
+ expect(inRangeValueOf).toBe(true);
+ expect(inRangeMoment).toBe(true);
+ expect(inRangeSame).toBe(true);
+ });
+
+ it('should handle boundary dates correctly with both methods', () => {
+ const startDate = helper.strToDate('20240201', 'YYYYMMDD');
+ const endDate = helper.strToDate('20240203', 'YYYYMMDD');
+
+ // Test start date boundary
+ const onStartDate = helper.strToDate('20240201', 'YYYYMMDD');
+ expect(onStartDate.valueOf() >= startDate.valueOf()).toBe(true);
+ expect(!onStartDate.isBefore(startDate, 'day')).toBe(true);
+ expect(onStartDate.isSameOrAfter(startDate, 'day')).toBe(true);
+
+ // Test end date boundary
+ const onEndDate = helper.strToDate('20240203', 'YYYYMMDD');
+ expect(onEndDate.valueOf() <= endDate.valueOf()).toBe(true);
+ expect(!onEndDate.isAfter(endDate, 'day')).toBe(true);
+ expect(onEndDate.isSameOrBefore(endDate, 'day')).toBe(true);
+ });
+
+ it('should handle dates normalized to start of day', () => {
+ // strToDate normalizes to startOf('day'), so all dates should be at midnight
+ const date1 = helper.strToDate('20240201', 'YYYYMMDD');
+ const date2 = helper.strToDate('20240201', 'YYYYMMDD');
+
+ // Both should be at midnight (00:00:00)
+ expect(date1.hours()).toBe(0);
+ expect(date1.minutes()).toBe(0);
+ expect(date1.seconds()).toBe(0);
+ expect(date1.valueOf()).toBe(date2.valueOf());
+ });
+ });
+
+ describe('Performance and clarity', () => {
+ it('should note that isBefore/isAfter is more explicit about day granularity', () => {
+ // isBefore/isAfter with 'day' is more explicit about what we're comparing
+ // valueOf() works but is less clear about granularity
+ const date1 = helper.strToDate('20240201', 'YYYYMMDD');
+ const date2 = helper.strToDate('20240201', 'YYYYMMDD');
+
+ // Both methods work, but isBefore with 'day' is more explicit
+ expect(date1.isBefore(date2, 'day')).toBe(false);
+ expect(date1.valueOf() < date2.valueOf()).toBe(false);
+ });
+ });
+});
diff --git a/test/date-format-yyyymmdd.test.ts b/test/date-format-yyyymmdd.test.ts
new file mode 100644
index 00000000..94dae533
--- /dev/null
+++ b/test/date-format-yyyymmdd.test.ts
@@ -0,0 +1,206 @@
+/**
+ * Tests for Issue #324: YYYYMMDD date format not working properly for startDate/endDate
+ *
+ * Bug: When dateFormat is set to YYYYMMDD, the startDate and endDate settings
+ * become ineffective and all files are included regardless of the date range.
+ */
+
+import * as helper from '../src/helper';
+import { RenderInfo } from '../src/data';
+import moment from 'moment';
+
+describe('Issue #324: YYYYMMDD date format with startDate/endDate', () => {
+ // Set up window.moment for tests
+ beforeAll(() => {
+ if (typeof (global as any).window === 'undefined') {
+ (global as any).window = {};
+ }
+ (global as any).window.moment = moment;
+ });
+
+ describe('strToDate function with YYYYMMDD format', () => {
+ it('should parse YYYYMMDD format correctly', () => {
+ const date = helper.strToDate('20240201', 'YYYYMMDD');
+ expect(date.isValid()).toBe(true);
+ expect(date.format('YYYY-MM-DD')).toBe('2024-02-01');
+ });
+
+ it('should parse YYYY-MM-DD format correctly (baseline)', () => {
+ const date = helper.strToDate('2024-02-01', 'YYYY-MM-DD');
+ expect(date.isValid()).toBe(true);
+ expect(date.format('YYYY-MM-DD')).toBe('2024-02-01');
+ });
+
+ it('should parse dates with YYYYMMDD format consistently', () => {
+ const date1 = helper.strToDate('20240201', 'YYYYMMDD');
+ const date2 = helper.strToDate('20240203', 'YYYYMMDD');
+
+ expect(date1.isValid()).toBe(true);
+ expect(date2.isValid()).toBe(true);
+
+ // Verify date comparison works
+ expect(date1.isBefore(date2)).toBe(true);
+ expect(date2.isAfter(date1)).toBe(true);
+ });
+ });
+
+ describe('Date range filtering with YYYYMMDD format', () => {
+ it('should correctly compare dates parsed with YYYYMMDD format', () => {
+ const startDate = helper.strToDate('20240201', 'YYYYMMDD');
+ const endDate = helper.strToDate('20240203', 'YYYYMMDD');
+ const testDate1 = helper.strToDate('20240131', 'YYYYMMDD'); // Before range
+ const testDate2 = helper.strToDate('20240202', 'YYYYMMDD'); // In range
+ const testDate3 = helper.strToDate('20240204', 'YYYYMMDD'); // After range
+
+ // These comparisons should work correctly
+ expect(testDate1.isBefore(startDate)).toBe(true);
+ expect(testDate2.isSameOrAfter(startDate) && testDate2.isSameOrBefore(endDate)).toBe(true);
+ expect(testDate3.isAfter(endDate)).toBe(true);
+ });
+
+ it('should correctly identify dates within range using isBefore/isAfter (fixed comparison)', () => {
+ const startDate = helper.strToDate('20240201', 'YYYYMMDD');
+ const endDate = helper.strToDate('20240203', 'YYYYMMDD');
+ const testDate1 = helper.strToDate('20240131', 'YYYYMMDD'); // Before
+ const testDate2 = helper.strToDate('20240202', 'YYYYMMDD'); // In range
+ const testDate3 = helper.strToDate('20240204', 'YYYYMMDD'); // After
+
+ // Test the fixed comparison logic using isBefore/isAfter (more reliable)
+ const beforeRange = testDate1.isBefore(startDate, 'day');
+ const inRange = !testDate2.isBefore(startDate, 'day') && !testDate2.isAfter(endDate, 'day');
+ const afterRange = testDate3.isAfter(endDate, 'day');
+
+ expect(beforeRange).toBe(true);
+ expect(inRange).toBe(true);
+ expect(afterRange).toBe(true);
+ });
+
+ it('should correctly compare dates with YYYY-MM-DD format (baseline)', () => {
+ const startDate = helper.strToDate('2024-02-01', 'YYYY-MM-DD');
+ const endDate = helper.strToDate('2024-02-03', 'YYYY-MM-DD');
+ const testDate1 = helper.strToDate('2024-01-31', 'YYYY-MM-DD');
+ const testDate2 = helper.strToDate('2024-02-02', 'YYYY-MM-DD');
+ const testDate3 = helper.strToDate('2024-02-04', 'YYYY-MM-DD');
+
+ const beforeRange = testDate1 < startDate;
+ const inRange = !(testDate2 < startDate) && !(testDate2 > endDate);
+ const afterRange = testDate3 > endDate;
+
+ expect(beforeRange).toBe(true);
+ expect(inRange).toBe(true);
+ expect(afterRange).toBe(true);
+ });
+ });
+
+ describe('Reproducing the bug: date comparison with YYYYMMDD', () => {
+ it('should correctly filter dates when using YYYYMMDD format', () => {
+ // Simulate the scenario from the issue
+ const startDateStr = '20240201';
+ const endDateStr = '20240203';
+ const dateFormat = 'YYYYMMDD';
+
+ const startDate = helper.strToDate(startDateStr, dateFormat);
+ const endDate = helper.strToDate(endDateStr, dateFormat);
+
+ // Test dates from different years (as mentioned in issue comments)
+ const datesToTest = [
+ { str: '20180101', expected: 'before' }, // 2018 - before range
+ { str: '20190101', expected: 'before' }, // 2019 - before range
+ { str: '20240201', expected: 'in' }, // Start date
+ { str: '20240202', expected: 'in' }, // In range
+ { str: '20240203', expected: 'in' }, // End date
+ { str: '20240204', expected: 'after' }, // After range
+ ];
+
+ datesToTest.forEach(({ str, expected }) => {
+ const testDate = helper.strToDate(str, dateFormat);
+ expect(testDate.isValid()).toBe(true);
+
+ // Apply the fixed filtering logic as in main.ts (using isBefore/isAfter)
+ let shouldSkip = false;
+ if (startDate !== null && startDate.isValid()) {
+ if (testDate.isBefore(startDate, 'day')) {
+ shouldSkip = true;
+ }
+ }
+ if (endDate !== null && endDate.isValid()) {
+ if (testDate.isAfter(endDate, 'day')) {
+ shouldSkip = true;
+ }
+ }
+
+ if (expected === 'before' || expected === 'after') {
+ expect(shouldSkip).toBe(true);
+ } else {
+ expect(shouldSkip).toBe(false);
+ }
+ });
+ });
+
+ it('BUG REPRODUCTION: should fail when dates have different format contexts', () => {
+ // This test reproduces the actual bug scenario
+ // When startDate/endDate are parsed with YYYYMMDD format,
+ // and file dates are also parsed with YYYYMMDD format,
+ // the comparison might fail due to format context issues
+
+ const dateFormat = 'YYYYMMDD';
+
+ // Parse startDate and endDate (as done in parsing.ts)
+ const startDate = helper.strToDate('20240201', dateFormat);
+ const endDate = helper.strToDate('20240203', dateFormat);
+
+ // Parse file dates (as done when reading files)
+ const fileDate1 = helper.strToDate('20180101', dateFormat); // Before range
+ const fileDate2 = helper.strToDate('20240202', dateFormat); // In range
+ const fileDate3 = helper.strToDate('20240204', dateFormat); // After range
+
+ // Check if dates are valid
+ expect(startDate.isValid()).toBe(true);
+ expect(endDate.isValid()).toBe(true);
+ expect(fileDate1.isValid()).toBe(true);
+ expect(fileDate2.isValid()).toBe(true);
+ expect(fileDate3.isValid()).toBe(true);
+
+ // The bug: comparison might fail if dates have different internal format storage
+ // Check the creation data format
+ const startFormat = startDate.creationData().format;
+ const fileFormat1 = fileDate1.creationData().format;
+
+ // If formats differ, comparison might fail
+ // This is the suspected bug
+ const formatsMatch = startFormat.toString() === fileFormat1.toString();
+
+ // Test the actual comparison operators used in main.ts
+ const beforeRange = fileDate1 < startDate;
+ const inRange = !(fileDate2 < startDate) && !(fileDate2 > endDate);
+ const afterRange = fileDate3 > endDate;
+
+ // These should all work correctly
+ expect(beforeRange).toBe(true);
+ expect(inRange).toBe(true);
+ expect(afterRange).toBe(true);
+ });
+ });
+
+ describe('dateToStr and strToDate roundtrip with YYYYMMDD', () => {
+ it('should correctly roundtrip dates with YYYYMMDD format', () => {
+ const originalDate = moment('2024-02-01');
+ const dateStr = helper.dateToStr(originalDate, 'YYYYMMDD');
+ expect(dateStr).toBe('20240201');
+
+ const parsedDate = helper.strToDate(dateStr, 'YYYYMMDD');
+ expect(parsedDate.isValid()).toBe(true);
+ expect(parsedDate.format('YYYY-MM-DD')).toBe('2024-02-01');
+ });
+
+ it('should correctly roundtrip dates with YYYY-MM-DD format (baseline)', () => {
+ const originalDate = moment('2024-02-01');
+ const dateStr = helper.dateToStr(originalDate, 'YYYY-MM-DD');
+ expect(dateStr).toBe('2024-02-01');
+
+ const parsedDate = helper.strToDate(dateStr, 'YYYY-MM-DD');
+ expect(parsedDate.isValid()).toBe(true);
+ expect(parsedDate.format('YYYY-MM-DD')).toBe('2024-02-01');
+ });
+ });
+});
diff --git a/test/edge-cases-date-filtering.test.ts b/test/edge-cases-date-filtering.test.ts
new file mode 100644
index 00000000..f7a88772
--- /dev/null
+++ b/test/edge-cases-date-filtering.test.ts
@@ -0,0 +1,365 @@
+/**
+ * Edge case tests for date filtering
+ *
+ * Tests various edge cases that might not be covered by the main test cases
+ */
+
+import { getRenderInfoFromYaml } from '../src/parsing';
+import { getDateFromFilename } from '../src/collecting';
+import { RenderInfo } from '../src/data';
+import moment from 'moment';
+
+// Mock Tracker plugin
+class MockTracker {
+ app: any;
+ settings: any;
+
+ constructor() {
+ this.app = {
+ vault: {
+ getAbstractFileByPath: (path: string) => {
+ const { TFolder } = require('./mocks/obsidian');
+ return new TFolder(path, path);
+ },
+ getConfig: (key: string) => {
+ if (key === 'tabSize') return 4;
+ return null;
+ }
+ }
+ };
+ this.settings = {
+ folder: '/',
+ dateFormat: 'YYYY-MM-DD'
+ };
+ }
+}
+
+describe('Edge Cases: Date Filtering', () => {
+ let mockPlugin: MockTracker;
+
+ beforeAll(() => {
+ if (typeof (global as any).window === 'undefined') {
+ (global as any).window = {};
+ }
+ (global as any).window.moment = moment;
+ mockPlugin = new MockTracker();
+ });
+
+ describe('Boundary conditions', () => {
+ it('should include files with dates exactly equal to startDate', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test
+dateFormat: YYYYMMDD
+startDate: 20240201
+endDate: 20240203
+line:
+ title: Test
+`.trim();
+
+ const renderInfo = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+ if (typeof renderInfo === 'string') {
+ fail(`Parsing failed: ${renderInfo}`);
+ return;
+ }
+
+ const file = { basename: '20240201', name: '20240201.md', path: 'test/20240201.md' };
+ const fileDate = getDateFromFilename(file as any, renderInfo);
+
+ const startDate = renderInfo.startDate!;
+ const endDate = renderInfo.endDate!;
+
+ // File date equals startDate - should be included
+ expect(fileDate.valueOf() >= startDate.valueOf()).toBe(true);
+ expect(fileDate.valueOf() <= endDate.valueOf()).toBe(true);
+ });
+
+ it('should include files with dates exactly equal to endDate', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test
+dateFormat: YYYYMMDD
+startDate: 20240201
+endDate: 20240203
+line:
+ title: Test
+`.trim();
+
+ const renderInfo = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+ if (typeof renderInfo === 'string') {
+ fail(`Parsing failed: ${renderInfo}`);
+ return;
+ }
+
+ const file = { basename: '20240203', name: '20240203.md', path: 'test/20240203.md' };
+ const fileDate = getDateFromFilename(file as any, renderInfo);
+
+ const startDate = renderInfo.startDate!;
+ const endDate = renderInfo.endDate!;
+
+ // File date equals endDate - should be included
+ expect(fileDate.valueOf() >= startDate.valueOf()).toBe(true);
+ expect(fileDate.valueOf() <= endDate.valueOf()).toBe(true);
+ });
+
+ it('should exclude files with dates exactly one day before startDate', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test
+dateFormat: YYYYMMDD
+startDate: 20240201
+endDate: 20240203
+line:
+ title: Test
+`.trim();
+
+ const renderInfo = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+ if (typeof renderInfo === 'string') {
+ fail(`Parsing failed: ${renderInfo}`);
+ return;
+ }
+
+ const file = { basename: '20240131', name: '20240131.md', path: 'test/20240131.md' };
+ const fileDate = getDateFromFilename(file as any, renderInfo);
+
+ const startDate = renderInfo.startDate!;
+
+ // File date is before startDate - should be excluded
+ expect(fileDate.valueOf() < startDate.valueOf()).toBe(true);
+ });
+
+ it('should exclude files with dates exactly one day after endDate', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test
+dateFormat: YYYYMMDD
+startDate: 20240201
+endDate: 20240203
+line:
+ title: Test
+`.trim();
+
+ const renderInfo = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+ if (typeof renderInfo === 'string') {
+ fail(`Parsing failed: ${renderInfo}`);
+ return;
+ }
+
+ const file = { basename: '20240204', name: '20240204.md', path: 'test/20240204.md' };
+ const fileDate = getDateFromFilename(file as any, renderInfo);
+
+ const endDate = renderInfo.endDate!;
+
+ // File date is after endDate - should be excluded
+ expect(fileDate.valueOf() > endDate.valueOf()).toBe(true);
+ });
+ });
+
+ describe('Missing date boundaries', () => {
+ it('should include all files when startDate is missing', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test
+dateFormat: YYYYMMDD
+endDate: 20240203
+line:
+ title: Test
+`.trim();
+
+ const renderInfo = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+ if (typeof renderInfo === 'string') {
+ fail(`Parsing failed: ${renderInfo}`);
+ return;
+ }
+
+ expect(renderInfo.startDate).toBeNull();
+ expect(renderInfo.endDate).not.toBeNull();
+
+ // Files before endDate should be included
+ const file = { basename: '20240101', name: '20240101.md', path: 'test/20240101.md' };
+ const fileDate = getDateFromFilename(file as any, renderInfo);
+ const endDate = renderInfo.endDate!;
+
+ expect(fileDate.valueOf() <= endDate.valueOf()).toBe(true);
+ });
+
+ it('should include all files when endDate is missing', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test
+dateFormat: YYYYMMDD
+startDate: 20240201
+line:
+ title: Test
+`.trim();
+
+ const renderInfo = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+ if (typeof renderInfo === 'string') {
+ fail(`Parsing failed: ${renderInfo}`);
+ return;
+ }
+
+ expect(renderInfo.startDate).not.toBeNull();
+ expect(renderInfo.endDate).toBeNull();
+
+ // Files after startDate should be included
+ const file = { basename: '20241231', name: '20241231.md', path: 'test/20241231.md' };
+ const fileDate = getDateFromFilename(file as any, renderInfo);
+ const startDate = renderInfo.startDate!;
+
+ expect(fileDate.valueOf() >= startDate.valueOf()).toBe(true);
+ });
+
+ it('should include all files when both startDate and endDate are missing', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test
+dateFormat: YYYYMMDD
+line:
+ title: Test
+`.trim();
+
+ const renderInfo = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+ if (typeof renderInfo === 'string') {
+ fail(`Parsing failed: ${renderInfo}`);
+ return;
+ }
+
+ expect(renderInfo.startDate).toBeNull();
+ expect(renderInfo.endDate).toBeNull();
+
+ // Any file should be processable
+ const file = { basename: '20240101', name: '20240101.md', path: 'test/20240101.md' };
+ const fileDate = getDateFromFilename(file as any, renderInfo);
+
+ expect(fileDate.isValid()).toBe(true);
+ });
+ });
+
+ describe('Year/month boundaries', () => {
+ it('should handle dates at year boundary correctly', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test
+dateFormat: YYYYMMDD
+startDate: 20231231
+endDate: 20240102
+line:
+ title: Test
+`.trim();
+
+ const renderInfo = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+ if (typeof renderInfo === 'string') {
+ fail(`Parsing failed: ${renderInfo}`);
+ return;
+ }
+
+ const files = [
+ { basename: '20231230', expected: 'excluded' }, // Before
+ { basename: '20231231', expected: 'included' }, // Start
+ { basename: '20240101', expected: 'included' }, // Year boundary
+ { basename: '20240102', expected: 'included' }, // End
+ { basename: '20240103', expected: 'excluded' }, // After
+ ];
+
+ const startDate = renderInfo.startDate!;
+ const endDate = renderInfo.endDate!;
+
+ for (const { basename, expected } of files) {
+ const file = { basename, name: `${basename}.md`, path: `test/${basename}.md` };
+ const fileDate = getDateFromFilename(file as any, renderInfo);
+
+ const inRange =
+ fileDate.valueOf() >= startDate.valueOf() &&
+ fileDate.valueOf() <= endDate.valueOf();
+
+ if (expected === 'included') {
+ expect(inRange).toBe(true);
+ } else {
+ expect(inRange).toBe(false);
+ }
+ }
+ });
+
+ it('should handle dates at month boundary correctly', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test
+dateFormat: YYYYMMDD
+startDate: 20240131
+endDate: 20240202
+line:
+ title: Test
+`.trim();
+
+ const renderInfo = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+ if (typeof renderInfo === 'string') {
+ fail(`Parsing failed: ${renderInfo}`);
+ return;
+ }
+
+ const files = [
+ { basename: '20240130', expected: 'excluded' }, // Before
+ { basename: '20240131', expected: 'included' }, // Start (last day of Jan)
+ { basename: '20240201', expected: 'included' }, // Month boundary
+ { basename: '20240202', expected: 'included' }, // End
+ { basename: '20240203', expected: 'excluded' }, // After
+ ];
+
+ const startDate = renderInfo.startDate!;
+ const endDate = renderInfo.endDate!;
+
+ for (const { basename, expected } of files) {
+ const file = { basename, name: `${basename}.md`, path: `test/${basename}.md` };
+ const fileDate = getDateFromFilename(file as any, renderInfo);
+
+ const inRange =
+ fileDate.valueOf() >= startDate.valueOf() &&
+ fileDate.valueOf() <= endDate.valueOf();
+
+ if (expected === 'included') {
+ expect(inRange).toBe(true);
+ } else {
+ expect(inRange).toBe(false);
+ }
+ }
+ });
+ });
+
+ describe('Invalid date handling', () => {
+ it('should handle files with invalid date formats gracefully', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test
+dateFormat: YYYYMMDD
+startDate: 20240201
+endDate: 20240203
+line:
+ title: Test
+`.trim();
+
+ const renderInfo = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+ if (typeof renderInfo === 'string') {
+ fail(`Parsing failed: ${renderInfo}`);
+ return;
+ }
+
+ // File with invalid date format
+ const file = { basename: 'invalid-date', name: 'invalid-date.md', path: 'test/invalid-date.md' };
+ const fileDate = getDateFromFilename(file as any, renderInfo);
+
+ // Invalid dates should be marked as invalid
+ expect(fileDate.isValid()).toBe(false);
+ });
+ });
+});
diff --git a/test/frontmatter-exists.test.ts b/test/frontmatter-exists.test.ts
new file mode 100644
index 00000000..afbdc30f
--- /dev/null
+++ b/test/frontmatter-exists.test.ts
@@ -0,0 +1,275 @@
+/**
+ * Automated tests for frontmatter.exists searchType
+ *
+ * Run with: npm test
+ */
+
+import { describe, it, expect, beforeEach } from '@jest/globals';
+import { SearchType, Query, RenderInfo, DataMap, XValueMap } from '../src/data';
+import { collectDataFromFrontmatterExists } from '../src/collecting';
+import type { CachedMetadata } from 'obsidian';
+
+// Mock Obsidian types
+type MockCachedMetadata = {
+ frontmatter?: Record;
+};
+
+describe('frontmatter.exists searchType', () => {
+ let query: Query;
+ let renderInfo: RenderInfo;
+ let dataMap: DataMap;
+ let xValueMap: XValueMap;
+
+ beforeEach(() => {
+ // Create a query for frontmatter.exists
+ query = new Query(0, SearchType.FrontmatterExists, 'meditation');
+
+ // Create minimal renderInfo
+ renderInfo = new RenderInfo([query]);
+ renderInfo.xDataset = [-1]; // Use filename as date
+ renderInfo.constValue = [1.0];
+ renderInfo.dateFormat = 'YYYY-MM-DD';
+
+ // Initialize data structures
+ dataMap = new Map();
+ xValueMap = new Map();
+ xValueMap.set(-1, '2024-12-01'); // Set xValue for the test date
+ });
+
+ it('should count non-empty string values', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: {
+ meditation: 'yes'
+ }
+ };
+
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ query,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(true);
+ expect(query.getNumTargets()).toBe(1);
+ expect(dataMap.has('2024-12-01')).toBe(true);
+ });
+
+ it('should NOT count empty strings', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: {
+ meditation: ''
+ }
+ };
+
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ query,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(false);
+ expect(query.getNumTargets()).toBe(0);
+ });
+
+ it('should NOT count whitespace-only strings', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: {
+ meditation: ' '
+ }
+ };
+
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ query,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(false);
+ expect(query.getNumTargets()).toBe(0);
+ });
+
+ it('should count non-empty arrays', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: {
+ tags: ['meditation', 'exercise']
+ }
+ };
+
+ const tagsQuery = new Query(0, SearchType.FrontmatterExists, 'tags');
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ tagsQuery,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(true);
+ expect(tagsQuery.getNumTargets()).toBe(1);
+ });
+
+ it('should NOT count empty arrays', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: {
+ tags: []
+ }
+ };
+
+ const tagsQuery = new Query(0, SearchType.FrontmatterExists, 'tags');
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ tagsQuery,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(false);
+ expect(tagsQuery.getNumTargets()).toBe(0);
+ });
+
+ it('should count boolean true', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: {
+ completed: true
+ }
+ };
+
+ const boolQuery = new Query(0, SearchType.FrontmatterExists, 'completed');
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ boolQuery,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(true);
+ expect(boolQuery.getNumTargets()).toBe(1);
+ });
+
+ it('should count boolean false (it exists!)', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: {
+ notDone: false
+ }
+ };
+
+ const boolQuery = new Query(0, SearchType.FrontmatterExists, 'notDone');
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ boolQuery,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(true);
+ expect(boolQuery.getNumTargets()).toBe(1);
+ });
+
+ it('should count number zero (it exists!)', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: {
+ score: 0
+ }
+ };
+
+ const numQuery = new Query(0, SearchType.FrontmatterExists, 'score');
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ numQuery,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(true);
+ expect(numQuery.getNumTargets()).toBe(1);
+ });
+
+ it('should NOT count null values', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: {
+ noScore: null
+ }
+ };
+
+ const nullQuery = new Query(0, SearchType.FrontmatterExists, 'noScore');
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ nullQuery,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(false);
+ expect(nullQuery.getNumTargets()).toBe(0);
+ });
+
+ it('should NOT count undefined/missing fields', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: {
+ otherField: 'value'
+ }
+ };
+
+ const missingQuery = new Query(0, SearchType.FrontmatterExists, 'meditation');
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ missingQuery,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(false);
+ expect(missingQuery.getNumTargets()).toBe(0);
+ });
+
+ it('should handle missing frontmatter', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: undefined
+ };
+
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ query,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(false);
+ expect(query.getNumTargets()).toBe(0);
+ });
+
+ it('should handle nested frontmatter fields', () => {
+ const fileCache: MockCachedMetadata = {
+ frontmatter: {
+ nested: {
+ field: 'value'
+ }
+ }
+ };
+
+ const nestedQuery = new Query(0, SearchType.FrontmatterExists, 'nested.field');
+ const result = collectDataFromFrontmatterExists(
+ fileCache as CachedMetadata,
+ nestedQuery,
+ renderInfo,
+ dataMap,
+ xValueMap
+ );
+
+ expect(result).toBe(true);
+ expect(nestedQuery.getNumTargets()).toBe(1);
+ });
+});
diff --git a/test/mocks/d3.ts b/test/mocks/d3.ts
new file mode 100644
index 00000000..b5cfd5fb
--- /dev/null
+++ b/test/mocks/d3.ts
@@ -0,0 +1,2 @@
+// Mock for d3 library
+export default {};
diff --git a/test/mocks/obsidian.ts b/test/mocks/obsidian.ts
new file mode 100644
index 00000000..1e1617b2
--- /dev/null
+++ b/test/mocks/obsidian.ts
@@ -0,0 +1,93 @@
+// Mock for Obsidian API
+// This provides window.moment which is used throughout the codebase
+
+import moment from 'moment';
+
+// Create a global window object if it doesn't exist
+if (typeof (global as any).window === 'undefined') {
+ (global as any).window = {};
+}
+
+// Mock window.moment with the actual moment library
+(global as any).window.moment = moment;
+
+// Export minimal Obsidian types for TypeScript
+export class TFile {
+ basename: string;
+ name: string;
+ path: string;
+ extension: string;
+ stat: any;
+
+ constructor(basename: string, path: string = '') {
+ this.basename = basename;
+ this.name = basename;
+ this.path = path || basename;
+ this.extension = '';
+ this.stat = {};
+ }
+}
+
+export class TFolder {
+ name: string;
+ path: string;
+
+ constructor(name: string, path: string = '') {
+ this.name = name;
+ this.path = path || name;
+ }
+}
+
+export function normalizePath(path: string): string {
+ return path.replace(/\\/g, '/');
+}
+
+export interface CachedMetadata {
+ frontmatter?: Record;
+ tags?: Array<{ tag: string }>;
+}
+
+// Simple YAML parser for testing (handles basic key-value pairs and nested objects)
+export function parseYaml(yamlText: string): any {
+ const result: any = {};
+ const lines = yamlText.split('\n');
+ const stack: Array<{ obj: any; indent: number }> = [{ obj: result, indent: -1 }];
+
+ for (const line of lines) {
+ const trimmed = line.trim();
+ if (!trimmed || trimmed.startsWith('#')) continue;
+
+ // Calculate indentation (spaces before first non-space char)
+ const indent = line.length - line.trimStart().length;
+
+ // Pop stack until we find the parent at this indentation level
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
+ stack.pop();
+ }
+
+ const colonIndex = trimmed.indexOf(':');
+ if (colonIndex === -1) continue;
+
+ const key = trimmed.substring(0, colonIndex).trim();
+ let value: any = trimmed.substring(colonIndex + 1).trim();
+
+ // Remove quotes if present
+ if ((value.startsWith('"') && value.endsWith('"')) ||
+ (value.startsWith("'") && value.endsWith("'"))) {
+ value = value.slice(1, -1);
+ }
+
+ // If value is empty, it's likely a nested object
+ const currentObj = stack[stack.length - 1].obj;
+ if (value === '') {
+ // Create nested object
+ currentObj[key] = {};
+ stack.push({ obj: currentObj[key], indent: indent });
+ } else {
+ // Simple key-value pair
+ currentObj[key] = value;
+ }
+ }
+
+ return result;
+}
diff --git a/test/parsing-yyyymmdd.test.ts b/test/parsing-yyyymmdd.test.ts
new file mode 100644
index 00000000..34d6f873
--- /dev/null
+++ b/test/parsing-yyyymmdd.test.ts
@@ -0,0 +1,154 @@
+/**
+ * Tests for Issue #324: YYYYMMDD date format parsing in getRenderInfoFromYaml
+ *
+ * This test verifies that startDate and endDate are correctly parsed from YAML
+ * when dateFormat is YYYYMMDD.
+ */
+
+import { getRenderInfoFromYaml } from '../src/parsing';
+import { RenderInfo } from '../src/data';
+import moment from 'moment';
+
+// Mock Tracker plugin
+class MockTracker {
+ app: any;
+ settings: any;
+
+ constructor() {
+ this.app = {
+ vault: {
+ getAbstractFileByPath: (path: string) => {
+ // Return a mock folder for any path
+ const { TFolder } = require('./mocks/obsidian');
+ return new TFolder(path, path);
+ },
+ getConfig: (key: string) => {
+ if (key === 'tabSize') return 4;
+ return null;
+ }
+ }
+ };
+ this.settings = {
+ folder: '/',
+ dateFormat: 'YYYY-MM-DD'
+ };
+ }
+}
+
+describe('Issue #324: YYYYMMDD date format parsing', () => {
+ let mockPlugin: MockTracker;
+
+ beforeAll(() => {
+ if (typeof (global as any).window === 'undefined') {
+ (global as any).window = {};
+ }
+ (global as any).window.moment = moment;
+ mockPlugin = new MockTracker();
+ });
+
+ it('should parse startDate and endDate with YYYYMMDD format', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test-issue-324
+dateFormat: YYYYMMDD
+startDate: 20240201
+endDate: 20240203
+line:
+ title: Test
+`.trim();
+
+ const result = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+
+ // Log the actual result to see what we're getting
+ if (typeof result === 'string') {
+ console.log('Error message:', result);
+ }
+
+ expect(typeof result).toBe('object');
+ expect(result).not.toBeInstanceOf(String); // Should not be an error message
+
+ const renderInfo = result as RenderInfo;
+
+ // Check that dateFormat is set correctly
+ expect(renderInfo.dateFormat).toBe('YYYYMMDD');
+
+ // Check that startDate and endDate are parsed correctly
+ expect(renderInfo.startDate).not.toBeNull();
+ expect(renderInfo.startDate?.isValid()).toBe(true);
+ expect(renderInfo.startDate?.format('YYYY-MM-DD')).toBe('2024-02-01');
+
+ expect(renderInfo.endDate).not.toBeNull();
+ expect(renderInfo.endDate?.isValid()).toBe(true);
+ expect(renderInfo.endDate?.format('YYYY-MM-DD')).toBe('2024-02-03');
+ });
+
+ it('should parse startDate and endDate with YYYY-MM-DD format (baseline)', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test-issue-324
+dateFormat: YYYY-MM-DD
+startDate: 2024-02-01
+endDate: 2024-02-03
+line:
+ title: Test
+`.trim();
+
+ const result = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+
+ expect(typeof result).toBe('object');
+ expect(result).not.toBeInstanceOf(String);
+
+ const renderInfo = result as RenderInfo;
+
+ expect(renderInfo.dateFormat).toBe('YYYY-MM-DD');
+ expect(renderInfo.startDate).not.toBeNull();
+ expect(renderInfo.startDate?.isValid()).toBe(true);
+ expect(renderInfo.startDate?.format('YYYY-MM-DD')).toBe('2024-02-01');
+
+ expect(renderInfo.endDate).not.toBeNull();
+ expect(renderInfo.endDate?.isValid()).toBe(true);
+ expect(renderInfo.endDate?.format('YYYY-MM-DD')).toBe('2024-02-03');
+ });
+
+ it('should handle missing startDate and endDate', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test-issue-324
+dateFormat: YYYYMMDD
+line:
+ title: Test
+`.trim();
+
+ const result = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+
+ expect(typeof result).toBe('object');
+ expect(result).not.toBeInstanceOf(String);
+
+ const renderInfo = result as RenderInfo;
+
+ expect(renderInfo.dateFormat).toBe('YYYYMMDD');
+ expect(renderInfo.startDate).toBeNull();
+ expect(renderInfo.endDate).toBeNull();
+ });
+
+ it('should return error for invalid startDate format', () => {
+ const yamlText = `
+searchType: tag
+searchTarget: weight
+folder: test-issue-324
+dateFormat: YYYYMMDD
+startDate: invalid-date
+endDate: 20240203
+line:
+ title: Test
+`.trim();
+
+ const result = getRenderInfoFromYaml(yamlText, mockPlugin as any);
+
+ expect(typeof result).toBe('string'); // Should be an error message
+ expect(result).toContain('Invalid startDate');
+ });
+});
diff --git a/test/setup.ts b/test/setup.ts
new file mode 100644
index 00000000..1912fc45
--- /dev/null
+++ b/test/setup.ts
@@ -0,0 +1,11 @@
+// Jest setup file
+// This runs before each test file
+
+// Ensure window.moment is available globally
+import moment from 'moment';
+
+if (typeof (global as any).window === 'undefined') {
+ (global as any).window = {};
+}
+
+(global as any).window.moment = moment;
diff --git a/tsconfig.json b/tsconfig.json
index 38b2e68a..287beba4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,17 +4,19 @@
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
- "target": "es6",
+ "target": "ES2018",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"allowSyntheticDefaultImports": true,
+
"lib": [
"dom",
"es5",
"scripthost",
- "es2015"
+ "es2015",
+ "ESNext.String"
]
},
"include": [
diff --git a/versions.json b/versions.json
index 28f318cb..8ce04870 100644
--- a/versions.json
+++ b/versions.json
@@ -1,19 +1,45 @@
-{
- "1.0.0": "0.9.12",
- "1.0.1": "0.9.12",
- "1.0.2": "0.9.12",
- "1.1.0": "0.9.12",
- "1.2.0": "0.9.12",
- "1.2.1": "0.9.12",
- "1.3.0": "0.9.12",
- "1.4.0": "0.9.12",
- "1.4.1": "0.9.12",
- "1.5.0": "0.9.12",
- "1.5.1": "0.9.12",
- "1.6.0": "0.9.12",
- "1.6.1": "0.9.12",
- "1.7.0": "0.9.12",
- "1.8.0": "0.9.12",
- "1.8.1": "0.9.12",
- "1.8.2": "0.9.12"
-}
+{
+ "1.0.0": "0.9.12",
+ "1.0.1": "0.9.12",
+ "1.0.2": "0.9.12",
+ "1.1.0": "0.9.12",
+ "1.2.0": "0.9.12",
+ "1.2.1": "0.9.12",
+ "1.3.0": "0.9.12",
+ "1.4.0": "0.9.12",
+ "1.4.1": "0.9.12",
+ "1.5.0": "0.9.12",
+ "1.5.1": "0.9.12",
+ "1.6.0": "0.9.12",
+ "1.6.1": "0.9.12",
+ "1.7.0": "0.9.12",
+ "1.8.0": "0.9.12",
+ "1.8.1": "0.9.12",
+ "1.8.2": "0.9.12",
+ "1.9.0": "0.9.12",
+ "1.9.1": "0.9.12",
+ "1.9.2": "0.9.12",
+ "1.10.0": "0.9.12",
+ "1.10.1": "0.9.12",
+ "1.10.2": "0.9.12",
+ "1.10.3": "0.9.12",
+ "1.10.4": "0.9.12",
+ "1.10.5": "0.9.12",
+ "1.10.6": "0.9.12",
+ "1.10.7": "0.9.12",
+ "1.10.8": "0.9.12",
+ "1.10.9": "0.9.12",
+ "1.11.0": "0.9.12",
+ "1.12.0": "0.9.12",
+ "1.13.0": "0.9.12",
+ "1.13.1": "0.9.12",
+ "1.13.2": "0.9.12",
+ "1.13.3": "0.9.12",
+ "1.14.0": "0.9.12",
+ "1.15.0": "0.9.12",
+ "1.15.1": "0.9.12",
+ "1.16.0": "0.9.12",
+ "1.17.0": "0.9.12",
+ "1.18.0": "0.9.12",
+ "1.19.0": "0.9.12"
+}