Brazen Framework - Configuration Manager

Configuration management for the Brazen user scripts framework

Questo script non dovrebbe essere installato direttamente. È una libreria per altri script da includere con la chiave // @require https://update.greasyfork.org/scripts/418665/1878437/Brazen%20Framework%20-%20Configuration%20Manager.js

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

Autore
brazenvoid
Versione
5.2.0
Creato il
15/12/2020
Aggiornato il
17/07/2026
Dimensione
98 KB
Licenza
GPL-3.0-only

Brazen Framework — Configuration Manager (developer guide)

Typed settings schema, pluggable storage drivers (localStorage + Tampermonkey GM), cross-tab sync, backup/restore, and DOM factories bound to View Layer widgets.

Greasy Fork: Configuration Manager · Requires: Utilities, View Layer · Used by: Framework core


When to use / when not to use

  • Use this module when your script needs user-configurable settings, persistence, backup/restore, and a consistent way to bind those settings to UI controls.
  • Don’t use ad-hoc GM_setValue blobs for settings in apps. Prefer defining fields here so the framework can apply/save/reset consistently and include values in backups.
  • Don’t make settings conditional on the current page at definition time. Define fields unconditionally (constructor), and gate behavior later in the framework lifecycle.

Quick start

Define fields in your app’s constructor, then mount them into the settings panel during UI composition.

// constructor (define schema)
this._configurationManager
  .addFlagField('enable-feature')
  .setTitle('Enable Feature')
  .setDefault(true)
  .setHelpText('Turns the feature on/off.')

this._configurationManager
  .addRulesetField('tag-blacklist')
  .setTitle('Tag Blacklist')
  .setRows(15)
  .setHelpText('One rule per line.')
  .setSortRules(true)

// UI composition (mount controls)
this._userInterface = [
  this._uiGen.createTabsSection(['Filters'], [
    this._uiGen.createTabPanel('Filters', true).append([
      this._configurationManager.createElement('enable-feature'),
      this._configurationManager.createElement('tag-blacklist'),
    ]),
  ]),
]

Constructor

new BrazenConfigurationManager(scriptPrefix, uiGenerator, tagSelectorGenerator)
Parameter Role
scriptPrefix Prefix for storage keys (per driver)
uiGenerator BrazenViewLayer instance
tagSelectorGenerator (tag: string) => string — used by addTagRulesetField / _optimizeTagRuleset

Framework exposes this as this._configurationManager. Register fields in the subclass constructor; call initialize() during init() before UI build.


Features

Field keys and titles (stable identity vs label)

Each field has:

  • A stable key (field.key) used for persistence, lookups, docking relationships, and backup/restore.
  • A display title (field.title) used only for UI labels.

The value you pass as the first argument to add*Field(key) is normalized via Utilities.toKebabCase(...) and stored as field.key.

To decouple label text from persistence, use kebab-case keys in code and set the UI label via setTitle(...):

const FILTER_TAG_BLACKLIST = 'tag-blacklist'

this._configurationManager.addRulesetField(FILTER_TAG_BLACKLIST).
  setTitle('Tag Blacklist').
  setRows(15).
  setHelpText('One rule per line.')

Defining a schema: field types and registration

What it does: You define a stable settings schema by adding typed fields. Fields become the single source of truth for UI binding, persistence, validation gating, and backup/restore.

How it works (developer-relevant):

  • Each add*Field(...) returns the field object so you can chain configuration (setDefault, setHelpText, setRows, etc.).
  • Fields default to local storage. Use field.attachStorage('gm') when a value should live in GM storage.
  • Some fields are self-managed (persist: false) and use driver primitives directly (bookmarks, ledger).
Constant add* method Stored value type UI
CONFIG_TYPE_FLAG addFlagField(name) boolean Checkbox
CONFIG_TYPE_TEXT addTextField(name) string Text input; empty → default on read (setDefault)
CONFIG_TYPE_NUMBER addNumberField(name, min, max) number Number input
CONFIG_TYPE_COLOR addColorField(name) color string Color input
CONFIG_TYPE_RANGE addRangeField(name, min, max) { minimum, maximum } Two number inputs
CONFIG_TYPE_SELECT addSelectField(name, pairs) option value See known limitation in spec
CONFIG_TYPE_CHECKBOXES_GROUP addCheckboxesGroup(name, pairs) string[] of checked data-values Checkbox group
CONFIG_TYPE_RADIOS_GROUP addRadiosGroup(name, pairs) selected data-value Radio group
CONFIG_TYPE_RULESET addRulesetField(name) string[] lines Textarea
CONFIG_TYPE_ACTION addActionField(name) n/a (persist: false) Form button or dock-only action
CONFIG_TYPE_BOOKMARKS addBookmarksField(name) array (self-managed on gm) Bookmarks panel
CONFIG_TYPE_LEDGER addLedgerField(name) self-managed id set (not in aggregate blob) No UI — GM-backed duplicate ledger; reload() migrates legacy index-only blobs to per-id entry keys + normalized { [primaryField]: ids }

All add*Field methods return the field object. Chain setHelpText, setDefault, setRows, bookmark/ledger/dock setters (setDockButton, applyDockTemplate, setDockSlideOut), then field.attachStorage('local'|'gm') as needed (default driver: local). Manager methods (onExternalConfigurationChange, setDockActive, …) return the manager.

this._configurationManager.addFlagField('Enable Feature').setHelpText('…').setDefault(true)
this._configurationManager.addRulesetField('Tag Blacklist').setRows(15).setHelpText('…').setSortRules(true)

Filename substitutions can use a controlled composer (read-only list + Subject → Alias add row):

```javascript
this._configurationManager.addRulesetField('filename-tag-substitutions').
    setSubstitutionComposer({
      normalize: (tag) => normalizeTag(tag),
      subjectPlaceholder: 'Subject',
      aliasPlaceholder: 'Alias',
    })

setSubstitutionComposer implies setReadOnly(true). Adds call setTagSubstitution; Save does not re-import the textarea when IndexedDB is active.


---

### Rulesets: the Apply/Save pipeline and optimized output

**What it does:** Ruleset fields turn user-edited textarea lines into a cleaned list of rules, optionally sorted/deduplicated, and optionally optimized into a runtime-friendly structure.

**Apply/Save pipeline** (`setFromUserInterface`, used by Apply/Save):

On **Apply/Save** (`setFromUserInterface`):

1. Split textarea by `REGEX_LINE_BREAK`; trim empty lines.
2. Optional `sortRules: true` — locale-aware sort (string lines only).
3. `onTranslateFromUI(values)` — normalize (optional; may return a non-array map, e.g. text sanitization).
4. Deduplicate when the value is an array (case-sensitive; first occurrence kept). Non-arrays pass through. Refreshes the textarea when array duplicates are removed.
5. `onOptimize(values)` — store result in `field.optimized` (filters read this at runtime).

**Registry fields** (`TAG_FIELD_SPEC`): textarea edits set `uiDirty`. While clean, Apply keeps the IDB/cache projection (does not read a stale textarea), and Save skips re-import so sidebar toggles are not wiped. Dirty textareas still import on Save as usual.

On **interface refresh** (`updateUserInterface`):

- `onFormatForUI(value)` → join with `\n` for textarea display; clears `uiDirty`.

`addTagRulesetField(key, useSelectors, rows, helpText?, formatter?, sortRules?)` wraps `addRulesetField` with default `onOptimize` → `_optimizeTagRuleset`.

**Per-rule API (programmatic edits):**

Every ruleset field exposes methods for programmatic add/remove without going through the textarea. Callers get the field via `getField(name)` and invoke these directly; side effects (`save()`, compliance refresh, UI refresh elsewhere) remain the caller's responsibility.

| Method | Role |
|--------|------|
| `onMatchRule(storedRule, target)` | Optional matcher callback; default is strict equality (`storedRule === target`) |
| `setRules(lines)` | Canonical write: `onTranslateFromUI` → optional `sortRules` (arrays only) → `value` → `onOptimize` → `updateUserInterface` |
| `findRuleIndex(target)` | Index of first matching rule, or `-1` |
| `hasRule(target)` | Membership test via `onMatchRule` |
| `addRule(rule, target?)` | Upsert: remove matches for `target` (defaults to `rule`), append `rule`, then `setRules` |
| `removeRule(target)` | Drop all matches, then `setRules` |
| `toggleRule(rule, target?)` | Add or remove via `hasRule` |
| `clearRules()` | `setRules([])` |

`setRules` does not deduplicate (preserves programmatic edits); `setFromUserInterface` (Apply/Save) still deduplicates array rulesets via `_deduplicateRulesetRules`.

**Tag rule syntax** (one rule per line):

- `&` — AND (all tags in segment must match)
- `|` — OR within a segment
- ` // ` — comment suffix (ignored after split)

Optimized output: array of arrays (each inner array = one conjunctive rule). Sorted shortest-first.

---

### Bookmarks: `addBookmarksField`

**What it does:** A self-managed bookmarks field backed by GM storage, rendered via the View Layer bookmarks panel.

**Typical use cases:**
- “Pages I visit often” lists, with optional “current page bookmarked?” UI signals.

```javascript
addBookmarksField('Bookmarks', 'Add pages you visit often.', {
  storageKey: 'myscript-bookmarks',  // default: {scriptPrefix}bookmarks
  storage: 'gm',                     // default
  normalizeBookmarks: (stored) => /* sanitize array */,
  showAddButton: true,
  pageMatch: { getCurrentUrl, normalizeUrl, onMatchChange, watchSelectors, isActive },
  onAdd, onSort, onRemove(id), onNavigate(url),
  getRowActions(bookmark),  // optional → HTMLElement[] | JQuery[]
})
Behaviour Detail
persist false — not in aggregate blob; dedicated GM key via GmStorageDriver
storage 'gm' by default
Field API reload(), save(), serializeForBackup(), applyFromBackup() (replace on restore)
widget BrazenBookmarksPanel from createBookmarksPanel
setFromUserInterface No-op — mutations call field.save() from site handlers
updateUserInterface reload() then widget.render(value)

UI binding: createElement (mounting fields)

What it does: Builds the View Layer DOM for a field, stores a reference on the field (field.element), and returns the mountable node.

this._configurationManager.createElement('My Setting')  // display name

Invokes the field's createElement() which:

  1. Builds View Layer DOM.
  2. Stores jQuery reference on field.element.
  3. Returns the mountable node (input group or section).

Fields without createElement called remain memory-only until mounted.

Each field implements:

Callback When
createElement() First mount
setFromUserInterface() Apply/Save — read DOM → field.value (+ ruleset optimize)
updateUserInterface() Load/reset/tab switch — write field.value → DOM

Reading values at runtime

this._configurationManager.getValue('My Setting')
this._configurationManager.getField('My Setting')       // ConfigurationField | null
this._configurationManager.getFieldOrFail('My Setting') // throws if missing
this._configurationManager.hasField('My Setting')

ConfigurationField properties: title, type, element, value, optimized?, helpText, minimum, maximum, options, persist, widget, ruleset callbacks.


Persistence (drivers, aggregate blobs, self-managed fields)

Each field has a storage driver key (local by default). Simple fields persist in a per-driver aggregate blob; self-managed types (persist: false, e.g. ledger, bookmarks) use driver primitives directly and participate in backup v2 when they implement serializeForBackup.

Store Driver Key Content
Settings local {scriptPrefix}settings JSON object: kebab keys → values (skips persist: false)
Sync id local {scriptPrefix}settings-id { id: number } — bumps on save
Settings gm {scriptPrefix}settings Same shape in GM namespace (lazy; only when a field uses gm)
Sync id gm {scriptPrefix}settings-id Same in GM namespace

initialize()

  1. Creates the local driver stores (LocalStore).
  2. _syncDriverStores('local') — merge disk into fields; rulesets run onOptimize.
  3. onChange on settings store → updateInterface().
  4. visibilitychange — if tab visible and meta.revisionId differs from the locally tracked cursor, re-sync + notify (local: false). Local writes advance the cursor immediately so same-tab minimize/maximize does not wipe unsaved panel edits.

Save / apply / restore

Method Behaviour
update() All mounted fields: setFromUserInterface() (rulesets: trim → optional sort → onTranslateFromUI → dedupe rules case-sensitively → onOptimize)
save() update() → write store → bump sync id
revertChanges() Re-read store into fields + updateInterface()
updateInterface() All mounted fields: updateUserInterface()

Framework button wiring:

  • Applyupdate() + re-run compliance (no disk).
  • Save — apply + save().
  • ResetrevertChanges() + compliance.

Backup format (v2)

backup() downloads {scriptPrefix}backup.json:

{
  "version": 2,
  "id": 123456789,
  "drivers": {
    "local": {
      "tag-blacklist": ["rule1", "rule2"],
      "pagination-threshold": 20
    },
    "gm": {
      "download-ledger": { "postIds": ["123", "456"] },
      "bookmarks": [{ "id": "…", "label": "…", "tags": "…", "url": "…" }]
    }
  }
}

Legacy flat backups (no version / drivers) still import as local values.

restore(response)fetch/Response with JSON body; each field's applyFromBackup handles semantics (ledger merges ids; bookmarks replace list); restores values + sync id; alerts success/failure. Legacy v1 backups without a bookmarks key leave local bookmarks untouched.

Cross-tab sync

onExternalConfigurationChange(handler)(manager) => void when another tab saved. Framework registers compliance re-run.

Revision cursor: BrazenStorageRepositories accepts optional onRevisionBump(revisionId). Configuration Manager wires it so every local meta.bumpRevision() advances _syncedRevisionId immediately. Same-tab visibilitychange after your own writes is then a no-op and does not wipe unsaved panel edits.

Tag runtime warm cache: After tag registry writes (toggleTagRule, discovery confirm, substitution toggles, …), _commitTagFieldChange re-warms TagRuntime (warmCache()) before notifyConfigurationChange('tags', true) so sidebar and discovery action icons read current attributes in the same refresh — no tab minimize/maximize needed.


Validation helper: generateValidationCallback

generateValidationCallback(configKey) → default enable checks:

Type Active when
flag / radios / select truthy value
checkboxes length > 0
number value > 0
range `minimum > 0 \
ruleset / text length > 0

Used by framework _addItemComplianceFilter when no custom validate callback is supplied.


Integration checklist (apps)

  1. Register all fields before init() builds UI.
  2. Use stable English labels — they appear in the panel and backup JSON keys.
  3. Mount with createElement inside tab panels.
  4. Pair enable flags with filters (addFlagField + ruleset).
  5. Self-managed fields (addLedgerField, addBookmarksField) use dedicated driver keys — no ad-hoc GM_setValue in apps.
  6. Set #bv-ui userScript reference if tabs need updateInterface from custom widgets.

Next in stack: Item Attributes Resolver (before Framework core).


Public API reference (index)

  • Field creation: addFlagField, addTextField, addNumberField, addColorField, addRangeField, addSelectField, addCheckboxesGroup, addRadiosGroup, addRulesetField, addActionField, addBookmarksField, addLedgerField, addTagRulesetField
  • Mounting fields: createElement(name)
  • Reading: getValue, getField, getFieldOrFail, hasField
  • Lifecycle: initialize, update, save, revertChanges, updateInterface
  • Sync/backups: backup, restore, onExternalConfigurationChange
  • Helpers: generateValidationCallback

Integration notes (grants, load order, pitfalls)