Configuration management for the Brazen user scripts framework
Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/418665/1875152/Brazen%20Framework%20-%20Configuration%20Manager.js
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
GM_setValue blobs for settings in apps. Prefer defining fields here so the framework can apply/save/reset consistently and include values in backups.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'),
]),
]),
]
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.
Each field has:
field.key) used for persistence, lookups, docking relationships, and backup/restore.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.')
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):
add*Field(...) returns the field object so you can chain configuration (setDefault, setHelpText, setRows, etc.).field.attachStorage('gm') when a value should live in GM storage.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, 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)
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):
REGEX_LINE_BREAK; trim empty lines.sortRules: true — locale-aware sort.onTranslateFromUI(values) — normalize (optional).onOptimize(values) — store result in field.optimized (filters read this at runtime).On interface refresh (updateUserInterface):
onFormatForUI(value) → join with \n for textarea display.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 → 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 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.
addBookmarksFieldWhat it does: A self-managed bookmarks field backed by GM storage, rendered via the View Layer bookmarks panel.
Typical use cases:
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) |
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:
field.element.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 |
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.
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()local driver stores (LocalStore)._syncDriverStores('local') — merge disk into fields; rulesets run onOptimize.onChange on settings store → updateInterface().visibilitychange — if tab visible and local id changed, re-sync + onExternalConfigurationChange.| 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:
update() + re-run compliance (no disk).save().revertChanges() + compliance.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.
onExternalConfigurationChange(handler) — (manager) => void when another tab saved. Framework registers compliance re-run.
generateValidationCallbackgenerateValidationCallback(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.
init() builds UI.createElement inside tab panels.addFlagField + ruleset).addLedgerField, addBookmarksField) use dedicated driver keys — no ad-hoc GM_setValue in apps.#bv-ui userScript reference if tabs need updateInterface from custom widgets.Next in stack: Item Attributes Resolver (before Framework core).
addFlagField, addTextField, addNumberField, addColorField, addRangeField, addSelectField, addCheckboxesGroup, addRadiosGroup, addRulesetField, addActionField, addBookmarksField, addLedgerField, addTagRulesetFieldcreateElement(name)getValue, getField, getFieldOrFail, hasFieldinitialize, update, save, revertChanges, updateInterfacebackup, restore, onExternalConfigurationChangegenerateValidationCallback