mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-01-23 02:36:05 +00:00
Merge branch 'master' into feat/operation-logs
* master: refactor(dialog): remove unused OnDestroy implementation from DialogAddNoteComponent fix(calendar): poll all calendar tasks and prevent auto-move of existing tasks docs: add info about how to translate stuff #5893 refactor(calendar): replace deprecated toPromise with firstValueFrom build: update links to match our new organization add QuestArc to community plugins list feat(calendar): implement polling for calendar task updates and enhance data retrieval logic fix(heatmap): use app theme class instead of prefers-color-scheme fix(focus-mode): start break from banner when manual break start enabled feat(i18n): connect Finnish and Swedish translation files refactor(focus-mode): split sessionComplete$ and breakComplete$ into single-responsibility effects Fixing Plugin API doc on persistence # Conflicts: # src/app/features/issue/store/poll-issue-updates.effects.ts # src/app/t.const.ts
This commit is contained in:
commit
a42c8a4cee
68 changed files with 12901 additions and 10684 deletions
50
docs/TRANSLATING.md
Normal file
50
docs/TRANSLATING.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Translation Guide
|
||||
|
||||
Super Productivity uses JSON files for translations, located in `src/assets/i18n/`.
|
||||
|
||||
## How to Contribute
|
||||
|
||||
1. Find your language file in `src/assets/i18n/` (e.g., `de.json` for German)
|
||||
2. Edit the JSON file directly
|
||||
3. Submit a pull request
|
||||
|
||||
## Important Notes
|
||||
|
||||
### Fallback Language
|
||||
|
||||
**English (`en.json`) is the fallback language.** If a translation is missing or empty, the app automatically displays the English text.
|
||||
|
||||
### Empty Values Are Intentional
|
||||
|
||||
When you see empty strings (`""`), this is **intentional** - it triggers the English fallback. Do not copy the English text into empty fields unless you're providing an actual translation.
|
||||
|
||||
```json
|
||||
{
|
||||
"SOME_KEY": ""
|
||||
}
|
||||
```
|
||||
|
||||
The above will display the English text for `SOME_KEY`.
|
||||
|
||||
### File Format
|
||||
|
||||
- Nested JSON structure
|
||||
- Keys use SCREAMING_SNAKE_CASE
|
||||
- Keep the structure intact - only change the string values
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"G": {
|
||||
"CANCEL": "Abbrechen",
|
||||
"SAVE": "Speichern"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
- Use `en.json` as reference for context
|
||||
- Keep translations concise (UI space is limited)
|
||||
- Test your translations locally if possible (`ng serve`)
|
||||
|
|
@ -10,12 +10,11 @@ For polling GitLab Issues, you need to provide an access token.
|
|||

|
||||
|
||||
## Project Access Token
|
||||
If you self-host GitLab or have the Premium/Ultimate license, it's possible to get a Project Access Token, which is scoped to a project.
|
||||
The scope is similar to the Personal Access token, but you also set a role. To learn what each role can do, see the <a href="https://docs.gitlab.com/ee/user/permissions.html#project-planning">Documentation</a>.
|
||||
|
||||
If you self-host GitLab or have the Premium/Ultimate license, it's possible to get a Project Access Token, which is scoped to a project.
|
||||
The scope is similar to the Personal Access token, but you also set a role. To learn what each role can do, see the <a href="https://docs.gitlab.com/ee/user/permissions.html#project-planning">Documentation</a>.
|
||||
|
||||

|
||||
|
||||
For GitHub Personal Access Token instructions, please visit the following link:
|
||||
[GitHub Access Token Instructions](https://github.com/johannesjo/super-productivity/blob/master/docs/github-access-token-instructions.md)
|
||||
|
||||
[GitHub Access Token Instructions](https://github.com/super-productivity/super-productivity/blob/master/docs/github-access-token-instructions.md)
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ cat /tmp/mas-profile.b64 | pbcopy
|
|||
|
||||
Then update the GitHub Actions secret:
|
||||
|
||||
1. Go to: https://github.com/johannesjo/super-productivity/settings/secrets/actions
|
||||
1. Go to: https://github.com/super-productivity/super-productivity/settings/secrets/actions
|
||||
2. Find `mas_provision_profile`
|
||||
3. Click **Update**
|
||||
4. Paste the base64-encoded content
|
||||
|
|
|
|||
|
|
@ -274,50 +274,58 @@ Plugins that render custom UI in a sandboxed iframe.
|
|||
- `updateTag(tagId, updates)` - Update tag
|
||||
|
||||
#### Simple Counters
|
||||
|
||||
Simple counters let you track lightweight metrics (e.g., daily clicks or habits) that persist and sync with your data. There are two levels: **basic** (key-value pairs for today's count) and **full model** (full CRUD on `SimpleCounter` entities with date-specific values).
|
||||
|
||||
##### Basic Counters
|
||||
|
||||
These treat counters as a simple `{ [id: string]: number }` map for today's values (auto-upserts via NgRx).
|
||||
|
||||
| Method | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `getAllCounters()` | Get all counters as `{ [id: string]: number }` | `const counters = await PluginAPI.getAllCounters(); console.log(counters['my-key']);` |
|
||||
| `getCounter(id)` | Get today's value for a counter (returns `null` if unset) | `const val = await PluginAPI.getCounter('daily-commits');` |
|
||||
| `setCounter(id, value)` | Set today's value (non-negative number; validates id regex `/^[A-Za-z0-9_-]+$/`) | `await PluginAPI.setCounter('daily-commits', 5);` |
|
||||
| `incrementCounter(id, incrementBy = 1)` | Increment and return new value (floors at 0) | `const newVal = await PluginAPI.incrementCounter('daily-commits', 2);` |
|
||||
| `decrementCounter(id, decrementBy = 1)` | Decrement and return new value (floors at 0) | `const newVal = await PluginAPI.decrementCounter('daily-commits');` |
|
||||
| `deleteCounter(id)` | Delete the counter | `await PluginAPI.deleteCounter('daily-commits');` |
|
||||
| Method | Description | Example |
|
||||
| --------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| `getAllCounters()` | Get all counters as `{ [id: string]: number }` | `const counters = await PluginAPI.getAllCounters(); console.log(counters['my-key']);` |
|
||||
| `getCounter(id)` | Get today's value for a counter (returns `null` if unset) | `const val = await PluginAPI.getCounter('daily-commits');` |
|
||||
| `setCounter(id, value)` | Set today's value (non-negative number; validates id regex `/^[A-Za-z0-9_-]+$/`) | `await PluginAPI.setCounter('daily-commits', 5);` |
|
||||
| `incrementCounter(id, incrementBy = 1)` | Increment and return new value (floors at 0) | `const newVal = await PluginAPI.incrementCounter('daily-commits', 2);` |
|
||||
| `decrementCounter(id, decrementBy = 1)` | Decrement and return new value (floors at 0) | `const newVal = await PluginAPI.decrementCounter('daily-commits');` |
|
||||
| `deleteCounter(id)` | Delete the counter | `await PluginAPI.deleteCounter('daily-commits');` |
|
||||
|
||||
**Example:**
|
||||
|
||||
```javascript
|
||||
// Track daily commits
|
||||
let commits = await PluginAPI.getCounter('daily-commits') ?? 0;
|
||||
let commits = (await PluginAPI.getCounter('daily-commits')) ?? 0;
|
||||
await PluginAPI.incrementCounter('daily-commits');
|
||||
PluginAPI.showSnack({ msg: `Commits today: ${await PluginAPI.getCounter('daily-commits')}`, type: 'INFO' });
|
||||
PluginAPI.showSnack({
|
||||
msg: `Commits today: ${await PluginAPI.getCounter('daily-commits')}`,
|
||||
type: 'INFO',
|
||||
});
|
||||
```
|
||||
|
||||
##### Full SimpleCounter Model
|
||||
|
||||
For advanced use: Full CRUD on counters with metadata (title, enabled state, date-specific values via `countOnDay: { [date: string]: number }`).
|
||||
|
||||
| Method | Description | Example |
|
||||
|--------|-------------|---------|
|
||||
| `getAllSimpleCounters()` | Get all as `SimpleCounter[]` | `const all = await PluginAPI.getAllSimpleCounters();` |
|
||||
| `getSimpleCounter(id)` | Get one by id (returns `undefined` if not found) | `const counter = await PluginAPI.getSimpleCounter('my-id');` |
|
||||
| `updateSimpleCounter(id, updates)` | Partial update (e.g., `{ title: 'New Title', countOnDay: { '2025-11-17': 10 } }`) | `await PluginAPI.updateSimpleCounter('my-id', { isEnabled: false });` |
|
||||
| `toggleSimpleCounter(id)` | Toggle `isOn` state (throws if not found) | `await PluginAPI.toggleSimpleCounter('my-id');` |
|
||||
| `setSimpleCounterEnabled(id, isEnabled)` | Set enabled state | `await PluginAPI.setSimpleCounterEnabled('my-id', true);` |
|
||||
| `deleteSimpleCounter(id)` | Delete by id | `await PluginAPI.deleteSimpleCounter('my-id');` |
|
||||
| `setSimpleCounterToday(id, value)` | Set today's value (YYYY-MM-DD) | `await PluginAPI.setSimpleCounterToday('my-id', 10);` |
|
||||
| `setSimpleCounterDate(id, date, value)` | Set value for specific date (validates YYYY-MM-DD) | `await PluginAPI.setSimpleCounterDate('my-id', '2025-11-16', 5);` |
|
||||
| Method | Description | Example |
|
||||
| ---------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| `getAllSimpleCounters()` | Get all as `SimpleCounter[]` | `const all = await PluginAPI.getAllSimpleCounters();` |
|
||||
| `getSimpleCounter(id)` | Get one by id (returns `undefined` if not found) | `const counter = await PluginAPI.getSimpleCounter('my-id');` |
|
||||
| `updateSimpleCounter(id, updates)` | Partial update (e.g., `{ title: 'New Title', countOnDay: { '2025-11-17': 10 } }`) | `await PluginAPI.updateSimpleCounter('my-id', { isEnabled: false });` |
|
||||
| `toggleSimpleCounter(id)` | Toggle `isOn` state (throws if not found) | `await PluginAPI.toggleSimpleCounter('my-id');` |
|
||||
| `setSimpleCounterEnabled(id, isEnabled)` | Set enabled state | `await PluginAPI.setSimpleCounterEnabled('my-id', true);` |
|
||||
| `deleteSimpleCounter(id)` | Delete by id | `await PluginAPI.deleteSimpleCounter('my-id');` |
|
||||
| `setSimpleCounterToday(id, value)` | Set today's value (YYYY-MM-DD) | `await PluginAPI.setSimpleCounterToday('my-id', 10);` |
|
||||
| `setSimpleCounterDate(id, date, value)` | Set value for specific date (validates YYYY-MM-DD) | `await PluginAPI.setSimpleCounterDate('my-id', '2025-11-16', 5);` |
|
||||
|
||||
**Example:**
|
||||
|
||||
```javascript
|
||||
// Create/update a habit counter
|
||||
await PluginAPI.updateSimpleCounter('habit-streak', {
|
||||
title: 'Daily Streak',
|
||||
type: 'ClickCounter',
|
||||
isEnabled: true,
|
||||
countOnDay: { '2025-11-17': 1 } // Today's count
|
||||
countOnDay: { '2025-11-17': 1 }, // Today's count
|
||||
});
|
||||
await PluginAPI.toggleSimpleCounter('habit-streak');
|
||||
const counter = await PluginAPI.getSimpleCounter('habit-streak');
|
||||
|
|
@ -445,11 +453,11 @@ You can persist data that will also be synced vai the `persistDataSynced` and `l
|
|||
|
||||
```javascript
|
||||
// Save plugin data
|
||||
await PluginAPI.persistDataSynced('myKey', { count: 42 });
|
||||
await PluginAPI.persistDataSynced(JSON.stringify({ count: 42 }));
|
||||
|
||||
// Load saved data
|
||||
const data = await PluginAPI.loadSyncedData('myKey');
|
||||
console.log(data); // { count: 42 }
|
||||
const data = await PluginAPI.loadSyncedData();
|
||||
console.log(data); // '{ count: 42 }'
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
|
@ -579,8 +587,8 @@ async function testAPI() {
|
|||
- **Plugin Boilerplate**: [boilerplate-solid-js](../packages/plugin-dev/boilerplate-solid-js)
|
||||
- **Example Plugins**: [plugin-dev](../packages/plugin-dev)
|
||||
- **Community Plugins**:
|
||||
- [counter-tester-plugin](https://github.com/Mustache-Games/counter-tester-plugin) by [Mustache Dev](https://github.com/Mustache-Games)
|
||||
- [sp-reporter](https://github.com/dougcooper/sp-reporter) by [dougcooper](https://github.com/dougcooper)
|
||||
- [counter-tester-plugin](https://github.com/Mustache-Games/counter-tester-plugin) by [Mustache Dev](https://github.com/Mustache-Games)
|
||||
- [sp-reporter](https://github.com/dougcooper/sp-reporter) by [dougcooper](https://github.com/dougcooper)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
@ -606,7 +614,7 @@ Happy plugin development! 🚀
|
|||
```md
|
||||
Can you you write me a plugin for Super Productivity that plays a beep sound every time i click on a header button (You need to add a header button via PluginAPI.registerHeaderButton).
|
||||
|
||||
Here are the docs: https://github.com/johannesjo/super-productivity/blob/master/docs/plugin-development.md
|
||||
Here are the docs: https://github.com/super-productivity/super-productivity/blob/master/docs/plugin-development.md
|
||||
|
||||
Don't use any PluginAPI methods that are not listed in the guide.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue