mirror of
https://github.com/shadiestgoat/obsidian-layout-manager.git
synced 2026-07-22 05:49:12 +00:00
Format
This commit is contained in:
parent
08071c1b75
commit
43b37c4a80
15 changed files with 517 additions and 503 deletions
|
|
@ -1,10 +1,10 @@
|
|||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"useTabs": false,
|
||||
"useTabs": true,
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"globals": "^16.0.0",
|
||||
"obsidian": "latest",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"svelte": "^5.26.2",
|
||||
"svelte-check": "^4.1.6",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { PlatformMode } from 'src/settings'
|
||||
import { PlatformMode } from 'src/settings'
|
||||
|
||||
let { value = $bindable() }: { value: PlatformMode } = $props()
|
||||
let { value = $bindable() }: { value: PlatformMode } = $props()
|
||||
</script>
|
||||
|
||||
<select class="dropdown" bind:value={value}>
|
||||
<option value={PlatformMode.BOTH}>Desktop & Mobile</option>
|
||||
<option value={PlatformMode.COMPUTER}>Desktop Only</option>
|
||||
<option value={PlatformMode.MOBILE}>Mobile Only</option>
|
||||
<select class="dropdown" bind:value>
|
||||
<option value={PlatformMode.BOTH}>Desktop & Mobile</option>
|
||||
<option value={PlatformMode.COMPUTER}>Desktop Only</option>
|
||||
<option value={PlatformMode.MOBILE}>Mobile Only</option>
|
||||
</select>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import type { KeyboardEventHandler, MouseEventHandler } from 'svelte/elements'
|
||||
|
||||
export function buttonInteraction(cb: () => void): {
|
||||
onkeypress: KeyboardEventHandler<HTMLElement>
|
||||
onclick: MouseEventHandler<HTMLElement>
|
||||
onkeypress: KeyboardEventHandler<HTMLElement>
|
||||
onclick: MouseEventHandler<HTMLElement>
|
||||
} {
|
||||
return {
|
||||
onclick: (e) => {
|
||||
return {
|
||||
onclick: (e) => {
|
||||
if (e.currentTarget.hasAttribute('disabled')) return
|
||||
cb()
|
||||
},
|
||||
onkeypress: (e) => {
|
||||
cb()
|
||||
},
|
||||
onkeypress: (e) => {
|
||||
if (e.currentTarget.hasAttribute('disabled')) return
|
||||
if (e.key == 'Enter' || e.key == ' ') {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e.key == 'Enter' || e.key == ' ') {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts">
|
||||
import type { ConfirmationModalCallback } from "src/modals"
|
||||
import { buttonInteraction } from "../interaction"
|
||||
import type { ConfirmationModalCallback } from 'src/modals'
|
||||
import { buttonInteraction } from '../interaction'
|
||||
|
||||
let { onSubmit, desc }: { onSubmit: ConfirmationModalCallback; desc: string } = $props()
|
||||
let { onSubmit, desc }: { onSubmit: ConfirmationModalCallback; desc: string } = $props()
|
||||
</script>
|
||||
|
||||
<div class="lm-modal-confirm">
|
||||
<p>{desc}</p>
|
||||
<button {...buttonInteraction(() => onSubmit(true))} class="mod-cta">Confirm</button>
|
||||
<button {...buttonInteraction(() => onSubmit(false))} >Cancel</button>
|
||||
<p>{desc}</p>
|
||||
<button {...buttonInteraction(() => onSubmit(true))} class="mod-cta">Confirm</button>
|
||||
<button {...buttonInteraction(() => onSubmit(false))}>Cancel</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
<script lang="ts">
|
||||
import type { NewLayoutCallback } from 'src/modals'
|
||||
import { PlatformMode } from 'src/settings'
|
||||
import PlatformDropdown from '../PlatformDropdown.svelte'
|
||||
import type { NewLayoutCallback } from 'src/modals'
|
||||
import { PlatformMode } from 'src/settings'
|
||||
import PlatformDropdown from '../PlatformDropdown.svelte'
|
||||
|
||||
let { onSubmit, otherNames }: { onSubmit: NewLayoutCallback; otherNames: string[] } = $props()
|
||||
let { onSubmit, otherNames }: { onSubmit: NewLayoutCallback; otherNames: string[] } = $props()
|
||||
|
||||
let name = $state('')
|
||||
let paths = $state('')
|
||||
let platMode = $state(PlatformMode.BOTH)
|
||||
let name = $state('')
|
||||
let paths = $state('')
|
||||
let platMode = $state(PlatformMode.BOTH)
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="lm-new-layout-container"
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
class="lm-new-layout-container"
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
onSubmit(name.trim(), paths.trim(), platMode)
|
||||
}}
|
||||
onSubmit(name.trim(), paths.trim(), platMode)
|
||||
}}
|
||||
>
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<!-- This is only rendered in modals, which should be autofocused since it IS a jump -->
|
||||
<input type="text" placeholder="Name" bind:value={name} autofocus />
|
||||
<textarea placeholder="Paths (glob)" bind:value={paths}></textarea>
|
||||
<PlatformDropdown bind:value={platMode} />
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<!-- This is only rendered in modals, which should be autofocused since it IS a jump -->
|
||||
<input type="text" placeholder="Name" bind:value={name} autofocus />
|
||||
<textarea placeholder="Paths (glob)" bind:value={paths}></textarea>
|
||||
<PlatformDropdown bind:value={platMode} />
|
||||
|
||||
<button disabled={!name || !paths || otherNames.contains(name)} type="submit" class="mod-cta"
|
||||
>Save</button
|
||||
>
|
||||
<button disabled={!name || !paths || otherNames.contains(name)} type="submit" class="mod-cta"
|
||||
>Save</button
|
||||
>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,23 @@
|
|||
<script lang="ts">
|
||||
let { onSubmit, options }: { onSubmit: (opt: string) => void, options: string[] } = $props()
|
||||
let { onSubmit, options }: { onSubmit: (opt: string) => void; options: string[] } = $props()
|
||||
|
||||
let name = $state("")
|
||||
let name = $state('')
|
||||
</script>
|
||||
|
||||
<form class="lm-new-layout-container" onsubmit={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
<form
|
||||
class="lm-new-layout-container"
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
onSubmit(name)
|
||||
}}>
|
||||
onSubmit(name)
|
||||
}}
|
||||
>
|
||||
<select class="dropdown" bind:value={name}>
|
||||
{#each options as opt (opt)}
|
||||
<option value="opt" >{opt}</option>
|
||||
<option value="opt">{opt}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
|
||||
<button disabled={!name} type="submit" class="mod-cta">Save</button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from "svelte"
|
||||
import type { Snippet } from 'svelte'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
|
|
|
|||
|
|
@ -1,146 +1,145 @@
|
|||
<script lang="ts">
|
||||
import type { SavedLayout } from 'src/settings'
|
||||
import PlatformDropdown from '../PlatformDropdown.svelte'
|
||||
import { setIcon } from 'obsidian'
|
||||
import { buttonInteraction } from '../interaction'
|
||||
import SettingItem from './SettingItem.svelte'
|
||||
import { ConfirmModal } from 'src/modals'
|
||||
import type { SavedLayout } from 'src/settings'
|
||||
import PlatformDropdown from '../PlatformDropdown.svelte'
|
||||
import { setIcon } from 'obsidian'
|
||||
import { buttonInteraction } from '../interaction'
|
||||
import SettingItem from './SettingItem.svelte'
|
||||
|
||||
interface Props {
|
||||
option: SavedLayout
|
||||
otherNames: string[]
|
||||
currentI: number
|
||||
size: number
|
||||
changeOrder: (target: number) => void
|
||||
deleteSelf: () => void
|
||||
}
|
||||
interface Props {
|
||||
option: SavedLayout
|
||||
otherNames: string[]
|
||||
currentI: number
|
||||
size: number
|
||||
changeOrder: (target: number) => void
|
||||
deleteSelf: () => void
|
||||
}
|
||||
|
||||
let {
|
||||
option = $bindable(),
|
||||
otherNames,
|
||||
currentI,
|
||||
size,
|
||||
changeOrder,
|
||||
deleteSelf
|
||||
}: Props = $props()
|
||||
let {
|
||||
option = $bindable(),
|
||||
otherNames,
|
||||
currentI,
|
||||
size,
|
||||
changeOrder,
|
||||
deleteSelf
|
||||
}: Props = $props()
|
||||
|
||||
let editingTitle = $state(false)
|
||||
let editingTitle = $state(false)
|
||||
|
||||
let tmpValue = $state(option.name)
|
||||
let tmpValue = $state(option.name)
|
||||
</script>
|
||||
|
||||
<details
|
||||
ontoggle={(e) => {
|
||||
if (!e.currentTarget.open) {
|
||||
editingTitle = false
|
||||
tmpValue = option.name
|
||||
}
|
||||
}}
|
||||
ontoggle={(e) => {
|
||||
if (!e.currentTarget.open) {
|
||||
editingTitle = false
|
||||
tmpValue = option.name
|
||||
}
|
||||
}}
|
||||
>
|
||||
<summary
|
||||
class="lm-name"
|
||||
onkeyup={(e) => {
|
||||
if (e.key == ' ') {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if editingTitle}
|
||||
<input bind:value={tmpValue} type="text" placeholder="Name" />
|
||||
{:else}
|
||||
<h3>{option.name}</h3>
|
||||
{/if}
|
||||
</summary>
|
||||
<summary
|
||||
class="lm-name"
|
||||
onkeyup={(e) => {
|
||||
if (e.key == ' ') {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if editingTitle}
|
||||
<input bind:value={tmpValue} type="text" placeholder="Name" />
|
||||
{:else}
|
||||
<h3>{option.name}</h3>
|
||||
{/if}
|
||||
</summary>
|
||||
|
||||
<div class="lm-layout-setting">
|
||||
<div class="setting-item lm-setting-manage-container">
|
||||
<p>Manage</p>
|
||||
<div class="pad"></div>
|
||||
<div class="lm-layout-setting">
|
||||
<div class="setting-item lm-setting-manage-container">
|
||||
<p>Manage</p>
|
||||
<div class="pad"></div>
|
||||
|
||||
{#if size > 1}
|
||||
{#if size > 2}
|
||||
<button
|
||||
use:setIcon={'chevrons-up'}
|
||||
{...buttonInteraction(() => changeOrder(0))}
|
||||
class="mod-cta"
|
||||
aria-label="Priorotize to top"
|
||||
disabled={currentI == 0}
|
||||
></button>
|
||||
{/if}
|
||||
<button
|
||||
use:setIcon={'chevron-up'}
|
||||
{...buttonInteraction(() => changeOrder(currentI - 1))}
|
||||
class="mod-cta"
|
||||
aria-label="Priorotize"
|
||||
disabled={currentI == 0}
|
||||
></button>
|
||||
<button
|
||||
use:setIcon={'chevron-down'}
|
||||
{...buttonInteraction(() => changeOrder(currentI + 1))}
|
||||
class="mod-cta"
|
||||
aria-label="Depriorotize"
|
||||
disabled={currentI == size - 1}
|
||||
></button>
|
||||
{#if size > 2}
|
||||
<button
|
||||
use:setIcon={'chevrons-down'}
|
||||
{...buttonInteraction(() => changeOrder(size - 1))}
|
||||
class="mod-cta"
|
||||
aria-label="Depriorotize to bottom"
|
||||
disabled={currentI == size - 1}
|
||||
></button>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if size > 1}
|
||||
{#if size > 2}
|
||||
<button
|
||||
use:setIcon={'chevrons-up'}
|
||||
{...buttonInteraction(() => changeOrder(0))}
|
||||
class="mod-cta"
|
||||
aria-label="Priorotize to top"
|
||||
disabled={currentI == 0}
|
||||
></button>
|
||||
{/if}
|
||||
<button
|
||||
use:setIcon={'chevron-up'}
|
||||
{...buttonInteraction(() => changeOrder(currentI - 1))}
|
||||
class="mod-cta"
|
||||
aria-label="Priorotize"
|
||||
disabled={currentI == 0}
|
||||
></button>
|
||||
<button
|
||||
use:setIcon={'chevron-down'}
|
||||
{...buttonInteraction(() => changeOrder(currentI + 1))}
|
||||
class="mod-cta"
|
||||
aria-label="Depriorotize"
|
||||
disabled={currentI == size - 1}
|
||||
></button>
|
||||
{#if size > 2}
|
||||
<button
|
||||
use:setIcon={'chevrons-down'}
|
||||
{...buttonInteraction(() => changeOrder(size - 1))}
|
||||
class="mod-cta"
|
||||
aria-label="Depriorotize to bottom"
|
||||
disabled={currentI == size - 1}
|
||||
></button>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if editingTitle}
|
||||
<button
|
||||
use:setIcon={'check'}
|
||||
{...buttonInteraction(() => {
|
||||
editingTitle = false
|
||||
option.name = tmpValue.trim()
|
||||
})}
|
||||
class="mod-cta"
|
||||
aria-label="Save"
|
||||
disabled={tmpValue.length === 0 || otherNames.contains(tmpValue)}
|
||||
></button>
|
||||
<button
|
||||
use:setIcon={'x'}
|
||||
{...buttonInteraction(() => {
|
||||
editingTitle = false
|
||||
tmpValue = option.name
|
||||
})}
|
||||
aria-label="Cancel"
|
||||
></button>
|
||||
{:else}
|
||||
<button
|
||||
use:setIcon={'pencil'}
|
||||
{...buttonInteraction(() => {
|
||||
editingTitle = true
|
||||
tmpValue = option.name
|
||||
})}
|
||||
aria-label="Being Editing Name"
|
||||
></button>
|
||||
{/if}
|
||||
{#if editingTitle}
|
||||
<button
|
||||
use:setIcon={'check'}
|
||||
{...buttonInteraction(() => {
|
||||
editingTitle = false
|
||||
option.name = tmpValue.trim()
|
||||
})}
|
||||
class="mod-cta"
|
||||
aria-label="Save"
|
||||
disabled={tmpValue.length === 0 || otherNames.contains(tmpValue)}
|
||||
></button>
|
||||
<button
|
||||
use:setIcon={'x'}
|
||||
{...buttonInteraction(() => {
|
||||
editingTitle = false
|
||||
tmpValue = option.name
|
||||
})}
|
||||
aria-label="Cancel"
|
||||
></button>
|
||||
{:else}
|
||||
<button
|
||||
use:setIcon={'pencil'}
|
||||
{...buttonInteraction(() => {
|
||||
editingTitle = true
|
||||
tmpValue = option.name
|
||||
})}
|
||||
aria-label="Being Editing Name"
|
||||
></button>
|
||||
{/if}
|
||||
|
||||
<div class="lm-dividor"></div>
|
||||
<div class="lm-dividor"></div>
|
||||
|
||||
<button
|
||||
use:setIcon={'trash-2'}
|
||||
{...buttonInteraction(() => deleteSelf())}
|
||||
aria-label="Delete Layout"
|
||||
></button>
|
||||
</div>
|
||||
<button
|
||||
use:setIcon={'trash-2'}
|
||||
{...buttonInteraction(() => deleteSelf())}
|
||||
aria-label="Delete Layout"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<SettingItem
|
||||
name="Glob Patterns"
|
||||
desc="If a pattern matches, this layout will be used. Each line is a new pattern"
|
||||
>
|
||||
<textarea bind:value={option.patterns} placeholder="Patterns (glob)"></textarea>
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
name="Glob Patterns"
|
||||
desc="If a pattern matches, this layout will be used. Each line is a new pattern"
|
||||
>
|
||||
<textarea bind:value={option.patterns} placeholder="Patterns (glob)"></textarea>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem name="Platform Mode" desc="Restrict this layout to only certain platforms">
|
||||
<PlatformDropdown bind:value={option.platformMode} />
|
||||
</SettingItem>
|
||||
</div>
|
||||
<SettingItem name="Platform Mode" desc="Restrict this layout to only certain platforms">
|
||||
<PlatformDropdown bind:value={option.platformMode} />
|
||||
</SettingItem>
|
||||
</div>
|
||||
</details>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,37 @@
|
|||
<script lang="ts">
|
||||
import type { SettingData } from 'src/settings'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import SettingLayout from './SettingLayout.svelte'
|
||||
import type { App } from 'obsidian'
|
||||
import { ConfirmModal } from 'src/modals'
|
||||
import type { SettingData } from 'src/settings'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import SettingLayout from './SettingLayout.svelte'
|
||||
import type { App } from 'obsidian'
|
||||
import { ConfirmModal } from 'src/modals'
|
||||
|
||||
let { settings, app }: { settings: Writable<SettingData>; app: App } = $props()
|
||||
let { settings, app }: { settings: Writable<SettingData>; app: App } = $props()
|
||||
|
||||
let otherNames = $derived($settings.map((s) => s.name))
|
||||
let otherNames = $derived($settings.map((s) => s.name))
|
||||
</script>
|
||||
|
||||
{#each $settings as l, i (l.name)}
|
||||
<SettingLayout
|
||||
bind:option={$settings[i]}
|
||||
{otherNames}
|
||||
currentI={i}
|
||||
size={$settings.length}
|
||||
changeOrder={(targetI) => {
|
||||
$settings.splice(i, 1)
|
||||
$settings.splice(targetI, 0, l)
|
||||
$settings = $settings
|
||||
}}
|
||||
deleteSelf={() => {
|
||||
new ConfirmModal(app, (success) => {
|
||||
if (!success) return
|
||||
$settings.splice(i, 1)
|
||||
<SettingLayout
|
||||
bind:option={$settings[i]}
|
||||
{otherNames}
|
||||
currentI={i}
|
||||
size={$settings.length}
|
||||
changeOrder={(targetI) => {
|
||||
$settings.splice(i, 1)
|
||||
$settings.splice(targetI, 0, l)
|
||||
$settings = $settings
|
||||
}}
|
||||
deleteSelf={() => {
|
||||
new ConfirmModal(
|
||||
app,
|
||||
(success) => {
|
||||
if (!success) return
|
||||
$settings.splice(i, 1)
|
||||
|
||||
$settings = $settings
|
||||
}, { desc: `This will delete the layout '${l.name}'` }).open()
|
||||
}}
|
||||
/>
|
||||
$settings = $settings
|
||||
},
|
||||
{ desc: `This will delete the layout '${l.name}'` }
|
||||
).open()
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
|
|
|
|||
316
src/main.ts
316
src/main.ts
|
|
@ -1,84 +1,92 @@
|
|||
import { FileView, Notice, Platform, Plugin, setIcon, WorkspaceLeaf } from 'obsidian'
|
||||
import type { WorkspaceLeaf } from 'obsidian'
|
||||
import { FileView, Notice, Platform, Plugin, setIcon } from 'obsidian'
|
||||
import { savableLayout, targetedLayout, type AnyContainer, type LayoutData } from './obsidianLayout'
|
||||
import { LayoutMgrSettings, PlatformMode, type SavedLayout, type SettingData } from './settings'
|
||||
import { NewLayoutModal, OverrideLayoutModal } from './modals'
|
||||
import { minimatch } from 'minimatch'
|
||||
|
||||
export default class LayoutManager extends Plugin {
|
||||
settings: SettingData
|
||||
switching = false
|
||||
settings: SettingData
|
||||
switching = false
|
||||
|
||||
/** leaf id -> file path */
|
||||
lastState = new Map<string, string>()
|
||||
/**
|
||||
* The original leaves created by this plugin
|
||||
* If empty, treat all leaves as original
|
||||
*/
|
||||
originalLeafs: string[] = []
|
||||
/** leaf id -> file path */
|
||||
lastState = new Map<string, string>()
|
||||
/**
|
||||
* The original leaves created by this plugin
|
||||
* If empty, treat all leaves as original
|
||||
*/
|
||||
originalLeafs: string[] = []
|
||||
|
||||
async onload(): Promise<void> {
|
||||
await this.loadSettings()
|
||||
async onload(): Promise<void> {
|
||||
await this.loadSettings()
|
||||
|
||||
this.addCommand({
|
||||
id: 'save-layout',
|
||||
name: 'Save Layout',
|
||||
callback: () => {
|
||||
const cont = this.getCurrentLayout()
|
||||
if (!cont) {
|
||||
return
|
||||
}
|
||||
this.addCommand({
|
||||
id: 'save-layout',
|
||||
name: 'Save Layout',
|
||||
callback: () => {
|
||||
const cont = this.getCurrentLayout()
|
||||
if (!cont) {
|
||||
return
|
||||
}
|
||||
|
||||
new NewLayoutModal(this.app, (name, paths, platMode) => {
|
||||
this.settings.push({
|
||||
container: cont,
|
||||
name,
|
||||
patterns: paths.split('\n').filter((v) => !!v).join("\n"),
|
||||
platformMode: platMode
|
||||
})
|
||||
this.saveSettings()
|
||||
}, { otherNames: this.settings.map(s => s.name) }).open()
|
||||
}
|
||||
})
|
||||
new NewLayoutModal(
|
||||
this.app,
|
||||
(name, paths, platMode) => {
|
||||
this.settings.push({
|
||||
container: cont,
|
||||
name,
|
||||
patterns: paths
|
||||
.split('\n')
|
||||
.filter((v) => !!v)
|
||||
.join('\n'),
|
||||
platformMode: platMode
|
||||
})
|
||||
this.saveSettings()
|
||||
},
|
||||
{ otherNames: this.settings.map((s) => s.name) }
|
||||
).open()
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: 'override-layuout',
|
||||
name: 'Override Layout',
|
||||
callback: () => {
|
||||
const cont = this.getCurrentLayout()
|
||||
if (!cont) {
|
||||
return
|
||||
}
|
||||
this.addCommand({
|
||||
id: 'override-layuout',
|
||||
name: 'Override Layout',
|
||||
callback: () => {
|
||||
const cont = this.getCurrentLayout()
|
||||
if (!cont) {
|
||||
return
|
||||
}
|
||||
|
||||
new OverrideLayoutModal(
|
||||
this.app,
|
||||
(name) => {
|
||||
const i = this.settings.findIndex((s) => s.name == name)
|
||||
if (i == -1) {
|
||||
new Notice("Can't find this Layout")
|
||||
return
|
||||
}
|
||||
this.settings[i].container = cont
|
||||
new OverrideLayoutModal(
|
||||
this.app,
|
||||
(name) => {
|
||||
const i = this.settings.findIndex((s) => s.name == name)
|
||||
if (i == -1) {
|
||||
new Notice("Can't find this Layout")
|
||||
return
|
||||
}
|
||||
this.settings[i].container = cont
|
||||
|
||||
this.saveSettings()
|
||||
},
|
||||
{ options: this.settings.map((s) => s.name) }
|
||||
).open()
|
||||
}
|
||||
})
|
||||
this.saveSettings()
|
||||
},
|
||||
{ options: this.settings.map((s) => s.name) }
|
||||
).open()
|
||||
}
|
||||
})
|
||||
|
||||
this.register(() => this.setOriginals([]))
|
||||
this.register(() => this.setOriginals([]))
|
||||
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
this.updateState()
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
this.updateState()
|
||||
|
||||
this.registerEvent(
|
||||
this.registerEvent(
|
||||
// Method got too big, lives in a seperate file now
|
||||
this.app.workspace.on('active-leaf-change', (l) => this.onActiveLeafChange(l))
|
||||
)
|
||||
})
|
||||
this.app.workspace.on('active-leaf-change', (l) => this.onActiveLeafChange(l))
|
||||
)
|
||||
})
|
||||
|
||||
this.addSettingTab(new LayoutMgrSettings(this.app, this))
|
||||
}
|
||||
}
|
||||
|
||||
onActiveLeafChange(l: WorkspaceLeaf | null) {
|
||||
const diffs = this.updateState()
|
||||
|
|
@ -130,71 +138,69 @@ export default class LayoutManager extends Plugin {
|
|||
this.loadLayout(matched.container, path)
|
||||
}
|
||||
|
||||
manageOriginalIcon(id: string, add: boolean) {
|
||||
// Its there... trust me...
|
||||
const l = this.app.workspace.getLeafById(id) as { tabHeaderInnerIconEl: HTMLElement } | null
|
||||
if (!l) return
|
||||
manageOriginalIcon(id: string, add: boolean) {
|
||||
// Its there... trust me...
|
||||
const l = this.app.workspace.getLeafById(id) as { tabHeaderInnerIconEl: HTMLElement } | null
|
||||
if (!l) return
|
||||
|
||||
l.tabHeaderInnerIconEl.toggleClass('lm-original', add)
|
||||
setIcon(l.tabHeaderInnerIconEl, add ? 'layout-dashboard' : 'file')
|
||||
}
|
||||
l.tabHeaderInnerIconEl.toggleClass('lm-original', add)
|
||||
setIcon(l.tabHeaderInnerIconEl, add ? 'layout-dashboard' : 'file')
|
||||
}
|
||||
|
||||
setOriginals(newOnes: string[]) {
|
||||
console.log('Unloading', this.originalLeafs, newOnes)
|
||||
setOriginals(newOnes: string[]) {
|
||||
// Cleanup or removed ones
|
||||
this.originalLeafs.forEach((id) => {
|
||||
if (!newOnes.contains(id)) this.manageOriginalIcon(id, false)
|
||||
})
|
||||
|
||||
// Cleanup or removed ones
|
||||
this.originalLeafs.forEach((id) => {
|
||||
if (!newOnes.contains(id)) this.manageOriginalIcon(id, false)
|
||||
})
|
||||
// Always refresh the new ones, even if they are present as part of old
|
||||
// This is to ensure icon is always right
|
||||
newOnes.forEach((id) => {
|
||||
this.manageOriginalIcon(id, true)
|
||||
})
|
||||
|
||||
// Always refresh the new ones, even if they are present as part of old
|
||||
// This is to ensure icon is always right
|
||||
newOnes.forEach((id) => {
|
||||
this.manageOriginalIcon(id, true)
|
||||
})
|
||||
this.originalLeafs = newOnes
|
||||
}
|
||||
|
||||
this.originalLeafs = newOnes
|
||||
}
|
||||
/**
|
||||
* @returns diffs
|
||||
*/
|
||||
updateState(): Record<string, [string | undefined, string | undefined]> {
|
||||
const curState = new Map<string, string>()
|
||||
|
||||
/**
|
||||
* @returns diffs
|
||||
*/
|
||||
updateState(): Record<string, [string | undefined, string | undefined]> {
|
||||
const curState = new Map<string, string>()
|
||||
this.app.workspace.iterateRootLeaves((l) => {
|
||||
if (!l || !(l.view instanceof FileView) || !l.view.file) {
|
||||
return
|
||||
}
|
||||
|
||||
this.app.workspace.iterateRootLeaves((l) => {
|
||||
if (!l || !(l.view instanceof FileView) || !l.view.file) {
|
||||
return
|
||||
}
|
||||
curState.set(l.id, l.view.file?.path)
|
||||
})
|
||||
|
||||
curState.set(l.id, l.view.file?.path)
|
||||
})
|
||||
const diffs: Record<string, [string | undefined, string | undefined]> = {}
|
||||
|
||||
const diffs: Record<string, [string | undefined, string | undefined]> = {}
|
||||
curState.forEach((v, k) => {
|
||||
const old = this.lastState.get(k)
|
||||
if (old != v) {
|
||||
diffs[k] = [old, v]
|
||||
}
|
||||
})
|
||||
|
||||
curState.forEach((v, k) => {
|
||||
const old = this.lastState.get(k)
|
||||
if (old != v) {
|
||||
diffs[k] = [old, v]
|
||||
}
|
||||
})
|
||||
this.lastState.forEach((v, k) => {
|
||||
if (diffs[k]) {
|
||||
return
|
||||
}
|
||||
if (!curState.has(k)) {
|
||||
diffs[k] = [v, undefined]
|
||||
}
|
||||
})
|
||||
|
||||
this.lastState.forEach((v, k) => {
|
||||
if (diffs[k]) {
|
||||
return
|
||||
}
|
||||
if (!curState.has(k)) {
|
||||
diffs[k] = [v, undefined]
|
||||
}
|
||||
})
|
||||
this.lastState = curState
|
||||
|
||||
this.lastState = curState
|
||||
return diffs
|
||||
}
|
||||
|
||||
return diffs
|
||||
}
|
||||
|
||||
findLayoutForPath(p: string): SavedLayout | null {
|
||||
for (const opt of this.settings) {
|
||||
findLayoutForPath(p: string): SavedLayout | null {
|
||||
for (const opt of this.settings) {
|
||||
if (opt.platformMode == PlatformMode.COMPUTER && !Platform.isDesktop) {
|
||||
continue
|
||||
}
|
||||
|
|
@ -202,58 +208,58 @@ export default class LayoutManager extends Plugin {
|
|||
continue
|
||||
}
|
||||
|
||||
for (const pattern of opt.patterns.split("\n")) {
|
||||
if (pattern && minimatch(p, pattern)) {
|
||||
return opt
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const pattern of opt.patterns.split('\n')) {
|
||||
if (pattern && minimatch(p, pattern)) {
|
||||
return opt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
return this.saveData(this.settings)
|
||||
}
|
||||
async saveSettings() {
|
||||
return this.saveData(this.settings)
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
const d = (await this.loadData()) ?? []
|
||||
async loadSettings() {
|
||||
const d = (await this.loadData()) ?? []
|
||||
|
||||
this.settings = Array.isArray(d) ? d : []
|
||||
}
|
||||
this.settings = Array.isArray(d) ? d : []
|
||||
}
|
||||
|
||||
getCurrentLayout(): AnyContainer | null {
|
||||
const fileSet = new Set<string>()
|
||||
const layoutData = this.app.workspace.getLayout() as LayoutData
|
||||
getCurrentLayout(): AnyContainer | null {
|
||||
const fileSet = new Set<string>()
|
||||
const layoutData = this.app.workspace.getLayout() as LayoutData
|
||||
|
||||
const layout = savableLayout(layoutData.main, fileSet, layoutData.active)
|
||||
if (fileSet.size > 1) {
|
||||
new Notice("Can't save: multiple files found in layout")
|
||||
return null
|
||||
}
|
||||
const layout = savableLayout(layoutData.main, fileSet, layoutData.active)
|
||||
if (fileSet.size > 1) {
|
||||
new Notice("Can't save: multiple files found in layout")
|
||||
return null
|
||||
}
|
||||
|
||||
return layout
|
||||
}
|
||||
return layout
|
||||
}
|
||||
|
||||
async loadLayout(layout: AnyContainer, path: string) {
|
||||
this.switching = true
|
||||
async loadLayout(layout: AnyContainer, path: string) {
|
||||
this.switching = true
|
||||
|
||||
const curLayout = this.app.workspace.getLayout() as LayoutData
|
||||
const curLayout = this.app.workspace.getLayout() as LayoutData
|
||||
|
||||
let activeId = curLayout.active
|
||||
const ogSet = new Set<string>()
|
||||
const main = targetedLayout(layout, path, (id) => (activeId = id), ogSet)
|
||||
let activeId = curLayout.active
|
||||
const ogSet = new Set<string>()
|
||||
const main = targetedLayout(layout, path, (id) => (activeId = id), ogSet)
|
||||
|
||||
await this.app.workspace.changeLayout({
|
||||
...curLayout,
|
||||
main: main,
|
||||
active: activeId
|
||||
})
|
||||
await this.app.workspace.changeLayout({
|
||||
...curLayout,
|
||||
main: main,
|
||||
active: activeId
|
||||
})
|
||||
|
||||
window.setTimeout(() => {
|
||||
this.switching = false
|
||||
window.setTimeout(() => {
|
||||
this.switching = false
|
||||
|
||||
this.setOriginals(Array.from(ogSet))
|
||||
}, 300)
|
||||
}
|
||||
this.setOriginals(Array.from(ogSet))
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,71 +7,71 @@ import OverrideLayout from './components/modals/OverrideLayout.svelte'
|
|||
import Confirmation from './components/modals/Confirmation.svelte'
|
||||
|
||||
abstract class genericSvelteModal<
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
F extends Function,
|
||||
C extends Component,
|
||||
P extends Record<string, unknown> = C extends Component<infer Props> ? Props : never
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
F extends Function,
|
||||
C extends Component,
|
||||
P extends Record<string, unknown> = C extends Component<infer Props> ? Props : never
|
||||
> extends Modal {
|
||||
svelteContent: Record<string, never>
|
||||
callback: F
|
||||
svelteContent: Record<string, never>
|
||||
callback: F
|
||||
|
||||
cbFuncName = 'onSubmit'
|
||||
props: Partial<P> = {}
|
||||
cbFuncName = 'onSubmit'
|
||||
props: Partial<P> = {}
|
||||
|
||||
abstract title: string
|
||||
abstract component: C
|
||||
abstract title: string
|
||||
abstract component: C
|
||||
|
||||
constructor(app: App, cb: F, props: Partial<P> = {}) {
|
||||
super(app)
|
||||
constructor(app: App, cb: F, props: Partial<P> = {}) {
|
||||
super(app)
|
||||
|
||||
this.callback = cb
|
||||
this.props = { ...this.props, ...props }
|
||||
}
|
||||
this.callback = cb
|
||||
this.props = { ...this.props, ...props }
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.setTitle(this.title)
|
||||
this.shouldRestoreSelection = true
|
||||
onOpen(): void {
|
||||
this.setTitle(this.title)
|
||||
this.shouldRestoreSelection = true
|
||||
|
||||
this.svelteContent = mount(this.component, {
|
||||
target: this.contentEl,
|
||||
props: {
|
||||
...this.props,
|
||||
[this.cbFuncName]: (...args: unknown[]) => {
|
||||
this.callback(...args)
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
this.svelteContent = mount(this.component, {
|
||||
target: this.contentEl,
|
||||
props: {
|
||||
...this.props,
|
||||
[this.cbFuncName]: (...args: unknown[]) => {
|
||||
this.callback(...args)
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
unmount(this.svelteContent, { outro: true })
|
||||
}
|
||||
async onClose() {
|
||||
unmount(this.svelteContent, { outro: true })
|
||||
}
|
||||
}
|
||||
|
||||
export type NewLayoutCallback = (name: string, paths: string, platformMode: PlatformMode) => void
|
||||
|
||||
export class NewLayoutModal extends genericSvelteModal<NewLayoutCallback, typeof NewLayout> {
|
||||
title = 'New Layout...'
|
||||
component = NewLayout
|
||||
title = 'New Layout...'
|
||||
component = NewLayout
|
||||
}
|
||||
|
||||
export type OverrideLayoutCallback = (name: string) => void
|
||||
|
||||
export class OverrideLayoutModal extends genericSvelteModal<
|
||||
OverrideLayoutCallback,
|
||||
typeof OverrideLayout
|
||||
OverrideLayoutCallback,
|
||||
typeof OverrideLayout
|
||||
> {
|
||||
title = 'Override Layout...'
|
||||
component = OverrideLayout
|
||||
title = 'Override Layout...'
|
||||
component = OverrideLayout
|
||||
}
|
||||
|
||||
export type ConfirmationModalCallback = (confirmed: boolean) => void
|
||||
|
||||
export class ConfirmModal extends genericSvelteModal<
|
||||
ConfirmationModalCallback,
|
||||
typeof Confirmation
|
||||
typeof Confirmation
|
||||
> {
|
||||
title = 'Are you sure?'
|
||||
component = Confirmation
|
||||
title = 'Are you sure?'
|
||||
component = Confirmation
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,38 +3,38 @@
|
|||
import { type ViewState } from 'obsidian'
|
||||
|
||||
export type KnownContainers = {
|
||||
split: { direction: string }
|
||||
tabs: Record<string, never>
|
||||
leaf: { state: ViewState & ViewStateFile; group?: string }
|
||||
split: { direction: string }
|
||||
tabs: Record<string, never>
|
||||
leaf: { state: ViewState & ViewStateFile; group?: string }
|
||||
}
|
||||
|
||||
// I hate typescript
|
||||
export type AnyContainer =
|
||||
| GenericContainer<'leaf'>
|
||||
| GenericContainer<'split'>
|
||||
| GenericContainer<'tabs'>
|
||||
| GenericContainer<'leaf'>
|
||||
| GenericContainer<'split'>
|
||||
| GenericContainer<'tabs'>
|
||||
|
||||
export type GenericContainer<N extends keyof KnownContainers> = {
|
||||
id?: string
|
||||
active?: boolean
|
||||
type: N
|
||||
children?: AnyContainer[]
|
||||
id?: string
|
||||
active?: boolean
|
||||
type: N
|
||||
children?: AnyContainer[]
|
||||
} & KnownContainers[N]
|
||||
|
||||
export interface ViewStateFile {
|
||||
type: 'markdown'
|
||||
state: {
|
||||
file: string
|
||||
mode: 'preview' | 'source'
|
||||
source: boolean
|
||||
}
|
||||
type: 'markdown'
|
||||
state: {
|
||||
file: string
|
||||
mode: 'preview' | 'source'
|
||||
source: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export type LayoutData = {
|
||||
// We don't care about layout data for anything other than the user content
|
||||
// Though... future release maybe? If someone asks ig
|
||||
main: AnyContainer
|
||||
active: string
|
||||
// We don't care about layout data for anything other than the user content
|
||||
// Though... future release maybe? If someone asks ig
|
||||
main: AnyContainer
|
||||
active: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -42,33 +42,33 @@ export type LayoutData = {
|
|||
* @param setActive When an active leaf is found, it will call this cb
|
||||
*/
|
||||
export function targetedLayout(
|
||||
l: AnyContainer,
|
||||
f: string,
|
||||
setActive: (id: string) => void,
|
||||
ogSet: Set<string>
|
||||
l: AnyContainer,
|
||||
f: string,
|
||||
setActive: (id: string) => void,
|
||||
ogSet: Set<string>
|
||||
): AnyContainer {
|
||||
if (l.active) {
|
||||
delete l.active
|
||||
l.id = randomId(16)
|
||||
setActive(l.id)
|
||||
}
|
||||
if (l.active) {
|
||||
delete l.active
|
||||
l.id = randomId(16)
|
||||
setActive(l.id)
|
||||
}
|
||||
|
||||
if (l.type != 'leaf') {
|
||||
l.children?.forEach((c) => targetedLayout(c, f, setActive, ogSet))
|
||||
return l
|
||||
}
|
||||
if (l.type != 'leaf') {
|
||||
l.children?.forEach((c) => targetedLayout(c, f, setActive, ogSet))
|
||||
return l
|
||||
}
|
||||
|
||||
if (!l.active) {
|
||||
l.id = randomId(16)
|
||||
}
|
||||
if (!l.active) {
|
||||
l.id = randomId(16)
|
||||
}
|
||||
|
||||
ogSet.add(l.id as string)
|
||||
ogSet.add(l.id as string)
|
||||
|
||||
if (l.state.type == 'markdown') {
|
||||
l.state.state.file = f
|
||||
}
|
||||
if (l.state.type == 'markdown') {
|
||||
l.state.state.file = f
|
||||
}
|
||||
|
||||
return l
|
||||
return l
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -76,43 +76,43 @@ export function targetedLayout(
|
|||
* l becomes dirty after this, do not use it again.
|
||||
*/
|
||||
export function savableLayout(
|
||||
l: AnyContainer,
|
||||
fileSet: Set<string>,
|
||||
activeID: string
|
||||
l: AnyContainer,
|
||||
fileSet: Set<string>,
|
||||
activeID: string
|
||||
): AnyContainer {
|
||||
// This is basically js referance abuse, which is always fun & reliable & easy to debug :3
|
||||
// This is basically js referance abuse, which is always fun & reliable & easy to debug :3
|
||||
|
||||
if (l.id == activeID) {
|
||||
l.active = true
|
||||
}
|
||||
if (l.id == activeID) {
|
||||
l.active = true
|
||||
}
|
||||
|
||||
delete l.id
|
||||
delete l.id
|
||||
|
||||
if (l.type == 'leaf') {
|
||||
if (l.state.type == 'markdown') {
|
||||
fileSet.add(l.state.state.file)
|
||||
if (l.type == 'leaf') {
|
||||
if (l.state.type == 'markdown') {
|
||||
fileSet.add(l.state.state.file)
|
||||
|
||||
// @ts-expect-error Title causes issues
|
||||
delete l.state.title
|
||||
// @ts-expect-error Title causes issues
|
||||
delete l.state.title
|
||||
|
||||
// Avoid saving extra metadata, set bare minimums
|
||||
l.state.state = {
|
||||
file: '',
|
||||
mode: l.state.state.mode,
|
||||
source: l.state.state.source
|
||||
}
|
||||
}
|
||||
} else {
|
||||
l.children?.forEach((c) => savableLayout(c, fileSet, activeID))
|
||||
}
|
||||
// Avoid saving extra metadata, set bare minimums
|
||||
l.state.state = {
|
||||
file: '',
|
||||
mode: l.state.state.mode,
|
||||
source: l.state.state.source
|
||||
}
|
||||
}
|
||||
} else {
|
||||
l.children?.forEach((c) => savableLayout(c, fileSet, activeID))
|
||||
}
|
||||
|
||||
return l
|
||||
return l
|
||||
}
|
||||
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
function randomId(l: number) {
|
||||
return Array.from(
|
||||
{ length: l },
|
||||
() => possible[Math.floor(Math.random() * possible.length)]
|
||||
).join('')
|
||||
return Array.from(
|
||||
{ length: l },
|
||||
() => possible[Math.floor(Math.random() * possible.length)]
|
||||
).join('')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { App, PluginSettingTab } from 'obsidian'
|
||||
import type { App } from 'obsidian'
|
||||
import { PluginSettingTab } from 'obsidian'
|
||||
import type { AnyContainer } from './obsidianLayout'
|
||||
import { mount, unmount } from 'svelte'
|
||||
import Settings from './components/settings/Settings.svelte'
|
||||
|
|
@ -6,22 +7,22 @@ import { writable } from 'svelte/store'
|
|||
import type LayoutManager from './main'
|
||||
|
||||
export enum PlatformMode {
|
||||
BOTH = 'both',
|
||||
COMPUTER = 'computer',
|
||||
MOBILE = 'mobile'
|
||||
BOTH = 'both',
|
||||
COMPUTER = 'computer',
|
||||
MOBILE = 'mobile'
|
||||
}
|
||||
|
||||
export interface SavedLayout {
|
||||
platformMode: PlatformMode
|
||||
container: AnyContainer
|
||||
name: string
|
||||
patterns: string
|
||||
platformMode: PlatformMode
|
||||
container: AnyContainer
|
||||
name: string
|
||||
patterns: string
|
||||
}
|
||||
|
||||
export type SettingData = SavedLayout[]
|
||||
|
||||
export class LayoutMgrSettings extends PluginSettingTab {
|
||||
svelteContent: Settings
|
||||
svelteContent: Settings
|
||||
plugin: LayoutManager
|
||||
|
||||
constructor(app: App, plugin: LayoutManager) {
|
||||
|
|
@ -30,8 +31,8 @@ export class LayoutMgrSettings extends PluginSettingTab {
|
|||
this.plugin = plugin
|
||||
}
|
||||
|
||||
display(): void {
|
||||
this.containerEl.addClass("lm-settings")
|
||||
display(): void {
|
||||
this.containerEl.addClass('lm-settings')
|
||||
|
||||
const settingProxy = writable(this.plugin.settings)
|
||||
settingProxy.subscribe((v) => {
|
||||
|
|
@ -39,14 +40,14 @@ export class LayoutMgrSettings extends PluginSettingTab {
|
|||
this.plugin.saveSettings()
|
||||
})
|
||||
|
||||
this.svelteContent = mount(Settings, {
|
||||
target: this.containerEl,
|
||||
props: {
|
||||
this.svelteContent = mount(Settings, {
|
||||
target: this.containerEl,
|
||||
props: {
|
||||
settings: settingProxy,
|
||||
app: this.app,
|
||||
}
|
||||
})
|
||||
}
|
||||
app: this.app
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
hide() {
|
||||
unmount(this.svelteContent, { outro: true })
|
||||
|
|
|
|||
Loading…
Reference in a new issue