Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b8461c3eb0 | |||
| b031cd7e04 | |||
| 254fcc1b41 |
@@ -0,0 +1,60 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project are documented in this file.
|
||||
Format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [1.1.0] - 2026-09-07
|
||||
|
||||
### Added
|
||||
|
||||
- **Settings page now visually groups commands by category.** Each category
|
||||
(Better Formatting, Line Operations, etc.) renders as a collapsible card with
|
||||
a bordered/shaded header and an "N/M enabled" badge, instead of a flat list
|
||||
separated only by bold text headings. The sentence-matching regex setting
|
||||
moved into the Sentence Navigator category card, since it configures that
|
||||
category's commands. Toggling a setting no longer collapses open categories.
|
||||
(`src/settings.ts`, `styles.css`)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Heading toggle corrupted lines starting with a tag (`#tag ...`).** `HEADING_REGEX`
|
||||
(`src/Constants.ts`) matched any run of leading `#` characters as a heading marker,
|
||||
even without a following space. Obsidian tags like `#project meeting notes` were
|
||||
therefore misread as an existing H1, so running *Toggle Heading - H1* on such a
|
||||
line stripped the `#` instead of adding a heading (and running any other heading
|
||||
level inserted a stray heading marker before the tag). The regex now requires the
|
||||
`#` run to be followed by whitespace or end-of-line before it counts as a heading,
|
||||
matching the ATX heading rule already used by *Toggle Heading (strip formatting)*.
|
||||
(`src/Constants.ts`, `src/ToggleHeading.ts`)
|
||||
|
||||
- **Command enable/disable toggles in Settings required an Obsidian restart.**
|
||||
`registerCommands()` only ran once in `onload()`, checking `enabledCommands` at
|
||||
registration time. Flipping a toggle in the settings tab saved the new state but
|
||||
had no effect on the already-registered commands until the plugin was reloaded,
|
||||
contradicting the setting's own description ("Disabled commands will not appear
|
||||
in the command palette"). Commands are now always registered and gated through
|
||||
`checkCallback`/`editorCheckCallback`, so palette visibility and hotkeys respond
|
||||
immediately to settings changes. Command IDs are unchanged, so existing hotkey
|
||||
bindings are unaffected. (`src/main.ts`)
|
||||
|
||||
- **"New Adjacent File" failed after the first use in a folder.** The command
|
||||
always targeted a literal `Untitled.md`; `vault.create()` throws if that path
|
||||
already exists (very likely, since it's also Obsidian's own default new-note
|
||||
name), surfacing a raw error `Notice` on every subsequent use. It now finds the
|
||||
next available `Untitled N.md` name in the folder, mirroring Obsidian's built-in
|
||||
new-note behavior. (`src/FileHelper.ts`)
|
||||
|
||||
- **Selection-tracking listeners went stale for editors opened after startup.**
|
||||
`registerSelectionChangeListeners()` attached `keydown`/`click`/`dblclick`
|
||||
listeners to every `.cm-content` element found via a single `querySelectorAll`
|
||||
at layout-ready. Editors created afterward (new panes, splits, tabs) got their
|
||||
own `.cm-content` element that was never instrumented, silently degrading the
|
||||
manual-vs-programmatic selection heuristic used by *Select Next/Previous
|
||||
Occurrence*. Listeners are now delegated from `document` in the capture phase,
|
||||
so any editor present now or opened later is covered. (`src/main.ts`)
|
||||
|
||||
### Chore
|
||||
|
||||
- Reinstalled `node_modules` to pull the `@esbuild/win32-x64` binary; the checked
|
||||
in lockfile/install had resolved only `@esbuild/linux-x64`, so `npm run build`
|
||||
and `npm run dev` failed outright on this machine.
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "bindthem",
|
||||
"name": "BindThem",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Enhanced editor shortcuts and formatting tools. Combines smart text wrapping (bold, italic, code, etc.), directional copy/move, heading toggles (H1-H6), line operations, case transformation, multi-cursor support, and file utilities into one unified plugin.",
|
||||
"author": "Olivier Legendre",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bindthem",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "A merged plugin combining obsidian-tweaks, obsidian-editor-shortcuts, and heading-toggler functionalities.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
|
||||
+6
-2
@@ -37,6 +37,10 @@ export const LIST_CHARACTER_REGEX = /^\s*(-|\+|\*|\d+\.|>) (\[.\] )?$/
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Regex for matching heading markers at the start of a line
|
||||
* Regex for matching heading markers at the start of a line.
|
||||
* A valid ATX heading marker is 1-6 `#` characters immediately followed
|
||||
* by whitespace or end of line. This lookahead excludes tag-like
|
||||
* prefixes such as `#project` (no space) from being misdetected as
|
||||
* headings — matches[1] is undefined when there is no valid marker.
|
||||
*/
|
||||
export const HEADING_REGEX = /^(#*)( *)(.*)/
|
||||
export const HEADING_REGEX = /^(#{1,6}(?=[ \t]|$))?( *)(.*)/
|
||||
+17
-2
@@ -41,7 +41,9 @@ export class FileHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new file in the same directory as the current file
|
||||
* Create a new file in the same directory as the current file.
|
||||
* If "Untitled.md" already exists, finds the next available
|
||||
* "Untitled N.md" name (mirrors Obsidian's own new-note behavior).
|
||||
*/
|
||||
public async newAdjacentFile(editor: Editor, view: MarkdownView | MarkdownFileInfo): Promise<void> {
|
||||
const activeFile = this.app.workspace.getActiveFile()
|
||||
@@ -51,7 +53,7 @@ export class FileHelper {
|
||||
}
|
||||
|
||||
const parentPath = activeFile.parent?.path ?? ''
|
||||
const newFilePath = parentPath + '/' + 'Untitled.md'
|
||||
const newFilePath = this.getAvailablePath(parentPath, 'Untitled', 'md')
|
||||
|
||||
try {
|
||||
const newFile = await this.app.vault.create(newFilePath, '')
|
||||
@@ -62,4 +64,17 @@ export class FileHelper {
|
||||
new Notice(String(e))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first unused "<basename>.md" / "<basename> N.md" path in a folder
|
||||
*/
|
||||
private getAvailablePath(parentPath: string, basename: string, extension: string): string {
|
||||
let candidate = `${parentPath}/${basename}.${extension}`
|
||||
let n = 1
|
||||
while (this.app.vault.getAbstractFileByPath(candidate) !== null) {
|
||||
candidate = `${parentPath}/${basename} ${n}.${extension}`
|
||||
n++
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export class ToggleHeading {
|
||||
}
|
||||
const to: EditorPosition = {
|
||||
line: line,
|
||||
ch: matches[1].length + matches[2].length,
|
||||
ch: (matches[1]?.length ?? 0) + matches[2].length,
|
||||
}
|
||||
|
||||
const replacementStr = heading === Heading.NORMAL ? '' : headingStr + ' '
|
||||
@@ -61,7 +61,7 @@ export class ToggleHeading {
|
||||
const text = editor.getLine(line)
|
||||
const matches = HEADING_REGEX.exec(text)!
|
||||
|
||||
return matches[1].length as Heading
|
||||
return (matches[1]?.length ?? 0) as Heading
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+53
-28
@@ -98,13 +98,33 @@ export default class BindThemPlugin extends Plugin {
|
||||
editorCallback?: (editor: Editor, view: MarkdownView | MarkdownFileInfo) => void;
|
||||
callback?: () => void;
|
||||
}): void {
|
||||
if (this.isCommandEnabled(options.id)) {
|
||||
const { id, name, icon, editorCallback, callback } = options;
|
||||
|
||||
// Commands are always registered; enabled state is enforced via
|
||||
// checkCallback so toggling a command in settings takes effect
|
||||
// immediately (hides it from the palette and disables its hotkey)
|
||||
// without requiring a plugin reload.
|
||||
if (editorCallback) {
|
||||
this.addCommand({
|
||||
id: options.id,
|
||||
name: options.name,
|
||||
icon: options.icon,
|
||||
editorCallback: options.editorCallback,
|
||||
callback: options.callback,
|
||||
id,
|
||||
name,
|
||||
icon,
|
||||
editorCheckCallback: (checking, editor, view) => {
|
||||
if (!this.isCommandEnabled(id)) return false;
|
||||
if (!checking) editorCallback(editor, view);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
} else if (callback) {
|
||||
this.addCommand({
|
||||
id,
|
||||
name,
|
||||
icon,
|
||||
checkCallback: (checking) => {
|
||||
if (!this.isCommandEnabled(id)) return false;
|
||||
if (!checking) callback();
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -903,31 +923,36 @@ export default class BindThemPlugin extends Plugin {
|
||||
* - Programmatic changes → within-word matching
|
||||
*/
|
||||
private registerSelectionChangeListeners(): void {
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
const MODIFIER_KEYS = [
|
||||
'Control', 'Shift', 'Alt', 'Meta', 'CapsLock', 'Fn',
|
||||
]
|
||||
const MODIFIER_KEYS = [
|
||||
'Control', 'Shift', 'Alt', 'Meta', 'CapsLock', 'Fn',
|
||||
]
|
||||
|
||||
const handleSelectionChange = (evt: Event) => {
|
||||
if (
|
||||
evt instanceof KeyboardEvent &&
|
||||
MODIFIER_KEYS.includes(evt.key)
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (!getIsProgrammaticSelectionChange()) {
|
||||
setIsManualSelection(true)
|
||||
}
|
||||
setIsProgrammaticSelectionChange(false)
|
||||
const handleSelectionChange = (evt: Event) => {
|
||||
if (
|
||||
evt instanceof KeyboardEvent &&
|
||||
MODIFIER_KEYS.includes(evt.key)
|
||||
) {
|
||||
return
|
||||
}
|
||||
const target = evt.target as HTMLElement | null
|
||||
if (!target?.closest('.cm-content')) {
|
||||
return
|
||||
}
|
||||
if (!getIsProgrammaticSelectionChange()) {
|
||||
setIsManualSelection(true)
|
||||
}
|
||||
setIsProgrammaticSelectionChange(false)
|
||||
}
|
||||
|
||||
// Observe CodeMirror 6 editors (new Obsidian editor)
|
||||
document.querySelectorAll('.cm-content').forEach((el) => {
|
||||
this.registerDomEvent(el as HTMLElement, 'keydown', handleSelectionChange)
|
||||
this.registerDomEvent(el as HTMLElement, 'click', handleSelectionChange)
|
||||
this.registerDomEvent(el as HTMLElement, 'dblclick', handleSelectionChange)
|
||||
})
|
||||
})
|
||||
// Delegate from `document` in the capture phase instead of
|
||||
// enumerating `.cm-content` elements once at layout-ready:
|
||||
// editors opened in new panes/splits/tabs after startup get
|
||||
// their own `.cm-content` element and would otherwise never
|
||||
// be instrumented. Capture phase guarantees we observe the
|
||||
// event even if CodeMirror stops propagation during bubbling.
|
||||
this.registerDomEvent(document, 'keydown', handleSelectionChange, true)
|
||||
this.registerDomEvent(document, 'click', handleSelectionChange, true)
|
||||
this.registerDomEvent(document, 'dblclick', handleSelectionChange, true)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
+109
-48
@@ -1,4 +1,4 @@
|
||||
import { App, PluginSettingTab, Setting } from 'obsidian';
|
||||
import { App, PluginSettingTab, Setting, TextComponent } from 'obsidian';
|
||||
import BindThemPlugin from './main';
|
||||
import { DEFAULT_SENTENCE_REGEX } from './Constants';
|
||||
|
||||
@@ -164,6 +164,13 @@ export const DEFAULT_SETTINGS: BindThemSettings = {
|
||||
export class BindThemSettingTab extends PluginSettingTab {
|
||||
plugin: BindThemPlugin;
|
||||
|
||||
/**
|
||||
* Refresh functions for each rendered category body. Bulk actions call
|
||||
* these directly instead of re-running `display()`, so toggling a
|
||||
* setting never collapses categories the user has open.
|
||||
*/
|
||||
private categoryRefreshers: Array<() => void> = [];
|
||||
|
||||
constructor(app: App, plugin: BindThemPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
@@ -172,6 +179,7 @@ export class BindThemSettingTab extends PluginSettingTab {
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
this.categoryRefreshers = [];
|
||||
|
||||
// Hotkeys link at the top
|
||||
containerEl.createEl('a', {
|
||||
@@ -184,14 +192,10 @@ export class BindThemSettingTab extends PluginSettingTab {
|
||||
}, (el) => {
|
||||
el.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const hotkeysTab = (this.app as unknown as { setting: { openTabById: (id: string) => unknown } }).setting.openTabById('hotkeys');
|
||||
if (hotkeysTab) {
|
||||
(hotkeysTab as unknown as { searchComponent: { setValue: (v: string) => void } }).searchComponent.setValue('BindThem');
|
||||
}
|
||||
this.openHotkeysSearch();
|
||||
});
|
||||
});
|
||||
|
||||
// General settings
|
||||
new Setting(containerEl)
|
||||
.setName('Debug mode')
|
||||
.setDesc('Enable debug logging to console')
|
||||
@@ -202,36 +206,16 @@ export class BindThemSettingTab extends PluginSettingTab {
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Sentence regex')
|
||||
.setDesc('Regular expression used to match sentences')
|
||||
.addText(text => text
|
||||
.setValue(this.plugin.settings.sentenceRegexSource)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.sentenceRegexSource = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Reset sentence regex')
|
||||
.addButton(button => button
|
||||
.setButtonText('Reset')
|
||||
.onClick(async () => {
|
||||
this.plugin.settings.sentenceRegexSource = DEFAULT_SENTENCE_REGEX;
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
}));
|
||||
|
||||
// Commands section header
|
||||
new Setting(containerEl)
|
||||
.setName('Command availability')
|
||||
.setDesc('Toggle which commands are available in Obsidian. Disabled commands will not appear in the command palette.')
|
||||
.setDesc('Toggle which commands are available in Obsidian. Disabled commands will not appear in the command palette. Select a category below to see its commands.')
|
||||
.setHeading();
|
||||
|
||||
// Category toggle buttons
|
||||
// Global toggle-all buttons
|
||||
new Setting(containerEl)
|
||||
.setName('Enable/disable all')
|
||||
.setDesc('Toggle all commands on or off')
|
||||
.setDesc('Toggle every command in every category on or off')
|
||||
.addButton(button => button
|
||||
.setButtonText('Enable all')
|
||||
.onClick(async () => {
|
||||
@@ -239,7 +223,7 @@ export class BindThemSettingTab extends PluginSettingTab {
|
||||
this.plugin.settings.enabledCommands[cmdId] = true;
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
this.refreshAllCategories();
|
||||
}))
|
||||
.addButton(button => button
|
||||
.setButtonText('Disable all')
|
||||
@@ -249,12 +233,13 @@ export class BindThemSettingTab extends PluginSettingTab {
|
||||
this.plugin.settings.enabledCommands[cmdId] = false;
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
this.refreshAllCategories();
|
||||
}));
|
||||
|
||||
// Render each category
|
||||
// Render each category as a collapsible, visually grouped card
|
||||
const categoryListEl = containerEl.createDiv({ cls: 'bindthem-category-list' });
|
||||
for (const [categoryKey, categoryName] of Object.entries(COMMAND_CATEGORIES)) {
|
||||
this.renderCategory(containerEl, categoryKey, categoryName);
|
||||
this.renderCategory(categoryListEl, categoryKey, categoryName);
|
||||
}
|
||||
|
||||
// About section
|
||||
@@ -265,35 +250,78 @@ export class BindThemSettingTab extends PluginSettingTab {
|
||||
.setHeading();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the Hotkeys settings tab and pre-fills its search with "BindThem".
|
||||
* `app.setting` (the settings modal controller) isn't part of Obsidian's
|
||||
* public API types, so the shape is narrowed once here rather than cast
|
||||
* inline at each property access.
|
||||
*/
|
||||
private openHotkeysSearch(): void {
|
||||
const appWithSettingModal = this.app as unknown as {
|
||||
setting: {
|
||||
openTabById: (id: string) => { searchComponent: { setValue: (v: string) => void } } | null;
|
||||
};
|
||||
};
|
||||
const hotkeysTab = appWithSettingModal.setting.openTabById('hotkeys');
|
||||
if (hotkeysTab) {
|
||||
hotkeysTab.searchComponent.setValue('BindThem');
|
||||
}
|
||||
}
|
||||
|
||||
private refreshAllCategories(): void {
|
||||
for (const refresh of this.categoryRefreshers) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private renderCategory(containerEl: HTMLElement, categoryKey: string, categoryName: string): void {
|
||||
const commands = COMMANDS[categoryKey as keyof typeof COMMANDS];
|
||||
if (!commands || commands.length === 0) return;
|
||||
|
||||
// Category header with toggle all
|
||||
const categorySetting = new Setting(containerEl)
|
||||
.setName(categoryName)
|
||||
.setHeading();
|
||||
const details = containerEl.createEl('details', { cls: 'bindthem-category' });
|
||||
const summary = details.createEl('summary', { cls: 'bindthem-category-summary' });
|
||||
summary.createSpan({ text: categoryName, cls: 'bindthem-category-name' });
|
||||
const badge = summary.createSpan({ cls: 'bindthem-category-count' });
|
||||
|
||||
// Check if all commands in this category are enabled
|
||||
const allEnabled = commands.every(cmd => this.plugin.settings.enabledCommands[cmd.id] !== false);
|
||||
const content = details.createDiv({ cls: 'bindthem-category-content' });
|
||||
|
||||
// Add toggle all button for this category
|
||||
categorySetting.addToggle(toggle => {
|
||||
toggle
|
||||
.setValue(allEnabled)
|
||||
const refresh = () => this.renderCategoryBody(content, badge, categoryKey, commands);
|
||||
refresh();
|
||||
this.categoryRefreshers.push(refresh);
|
||||
}
|
||||
|
||||
private renderCategoryBody(
|
||||
content: HTMLElement,
|
||||
badge: HTMLElement,
|
||||
categoryKey: string,
|
||||
commands: CommandDefinition[]
|
||||
): void {
|
||||
content.empty();
|
||||
|
||||
const enabledCount = commands.filter(cmd => this.plugin.settings.enabledCommands[cmd.id] !== false).length;
|
||||
badge.setText(`${enabledCount}/${commands.length} enabled`);
|
||||
|
||||
// Sentence regex configures the Sentence Navigator commands, so it
|
||||
// lives inside that category's card instead of the general section.
|
||||
if (categoryKey === 'sentenceNavigator') {
|
||||
this.renderSentenceRegexSetting(content);
|
||||
}
|
||||
|
||||
new Setting(content)
|
||||
.setName('Enable all in this category')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(enabledCount === commands.length)
|
||||
.setTooltip('Toggle all commands in this category')
|
||||
.onChange(async (value) => {
|
||||
for (const cmd of commands) {
|
||||
this.plugin.settings.enabledCommands[cmd.id] = value;
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
});
|
||||
});
|
||||
this.renderCategoryBody(content, badge, categoryKey, commands);
|
||||
}));
|
||||
|
||||
// Render individual commands
|
||||
for (const cmd of commands) {
|
||||
new Setting(containerEl)
|
||||
new Setting(content)
|
||||
.setName(cmd.name)
|
||||
.setDesc(cmd.description || `Command ID: ${cmd.id}`)
|
||||
.addToggle(toggle => toggle
|
||||
@@ -301,7 +329,40 @@ export class BindThemSettingTab extends PluginSettingTab {
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enabledCommands[cmd.id] = value;
|
||||
await this.plugin.saveSettings();
|
||||
const newCount = commands.filter(c => this.plugin.settings.enabledCommands[c.id] !== false).length;
|
||||
badge.setText(`${newCount}/${commands.length} enabled`);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the sentence-matching regex setting and its reset button.
|
||||
* Updates the text field directly on reset instead of re-rendering.
|
||||
*/
|
||||
private renderSentenceRegexSetting(containerEl: HTMLElement): void {
|
||||
let textComponent: TextComponent | undefined;
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Sentence regex')
|
||||
.setDesc('Regular expression used to match sentences')
|
||||
.addText(text => {
|
||||
textComponent = text;
|
||||
text
|
||||
.setValue(this.plugin.settings.sentenceRegexSource)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.sentenceRegexSource = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Reset sentence regex')
|
||||
.addButton(button => button
|
||||
.setButtonText('Reset')
|
||||
.onClick(async () => {
|
||||
this.plugin.settings.sentenceRegexSource = DEFAULT_SENTENCE_REGEX;
|
||||
await this.plugin.saveSettings();
|
||||
textComponent?.setValue(DEFAULT_SENTENCE_REGEX);
|
||||
}));
|
||||
}
|
||||
}
|
||||
+66
-2
@@ -3,6 +3,70 @@
|
||||
This CSS file will be included with your plugin, and
|
||||
available in the app when your plugin is enabled.
|
||||
|
||||
If your plugin does not need CSS, delete this file.
|
||||
|
||||
*/
|
||||
|
||||
/* ============================================================
|
||||
Settings tab — command category grouping
|
||||
============================================================ */
|
||||
|
||||
.bindthem-category-list {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.bindthem-category {
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
margin-bottom: 0.5em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bindthem-category > summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75em;
|
||||
padding: 0.65em 1em;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
background-color: var(--background-secondary);
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.bindthem-category > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bindthem-category > summary::before {
|
||||
content: '▸';
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
color: var(--text-muted);
|
||||
transition: transform 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.bindthem-category[open] > summary::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.bindthem-category-name {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.bindthem-category-count {
|
||||
font-weight: 400;
|
||||
font-size: var(--font-ui-smaller);
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bindthem-category-content {
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.bindthem-category-content .setting-item {
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.bindthem-category-content .setting-item:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"1.0.0": "0.15.0"
|
||||
"1.0.0": "0.15.0",
|
||||
"1.1.0": "0.15.0"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user