Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,9 @@ export default class MediaDbPlugin extends Plugin {
newProperties.push(defaultProperty);
} else {
// newProperty is just an object and take locked status from default property
newProperties.push(new PropertyMapping(newProperty.property, newProperty.newProperty, newProperty.mapping, defaultProperty.locked));
newProperties.push(
new PropertyMapping(newProperty.property, newProperty.newProperty, newProperty.mapping, defaultProperty.locked, newProperty.wikilink ?? false),
);
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/settings/PropertyMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,22 @@ export class PropertyMapper {
for (const [key, value] of Object.entries(obj)) {
for (const propertyMapping of propertyMappings) {
if (propertyMapping.property === key) {
let finalValue = value;
if (propertyMapping.wikilink) {
if (typeof value === 'string') {
finalValue = `[[${value}]]`;
} else if (Array.isArray(value)) {
finalValue = value.map(v => (typeof v === 'string' ? `[[${v}]]` : v));
}
}
if (propertyMapping.mapping === PropertyMappingOption.Map) {
// @ts-ignore
newObj[propertyMapping.newProperty] = value;
newObj[propertyMapping.newProperty] = finalValue;
} else if (propertyMapping.mapping === PropertyMappingOption.Remove) {
// do nothing
} else if (propertyMapping.mapping === PropertyMappingOption.Default) {
// @ts-ignore
newObj[key] = value;
newObj[key] = finalValue;
}
break;
}
Expand Down
6 changes: 4 additions & 2 deletions src/settings/PropertyMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class PropertyMappingModel {
copy(): PropertyMappingModel {
const copy = new PropertyMappingModel(this.type);
for (const property of this.properties) {
const propertyCopy = new PropertyMapping(property.property, property.newProperty, property.mapping, property.locked);
const propertyCopy = new PropertyMapping(property.property, property.newProperty, property.mapping, property.locked, property.wikilink);
copy.properties.push(propertyCopy);
}
return copy;
Expand All @@ -87,12 +87,14 @@ export class PropertyMapping {
newProperty: string;
locked: boolean;
mapping: PropertyMappingOption;
wikilink: boolean;

constructor(property: string, newProperty: string, mapping: PropertyMappingOption, locked?: boolean) {
constructor(property: string, newProperty: string, mapping: PropertyMappingOption, locked?: boolean, wikilink?: boolean) {
this.property = property;
this.newProperty = newProperty;
this.mapping = mapping;
this.locked = locked ?? false;
this.wikilink = wikilink ?? false;
}

validate(): { res: boolean; err?: Error } {
Expand Down
156 changes: 122 additions & 34 deletions src/settings/PropertyMappingModelComponent.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,57 +11,145 @@
let { model, save }: Props = $props();

let validationResult: { res: boolean; err?: Error } | undefined = $derived(model.validate());

// Use $state as a variable declaration initializer
let propertyStates = $state(model.properties.map(p => ({ ...p })));
</script>

<div class="media-db-plugin-property-mappings-model-container">
<div class="setting-item-name">{capitalizeFirstLetter(model.type)}</div>
<div class="media-db-plugin-property-mappings-container">
{#each propertyStates as property, i}
<div class="media-db-plugin-property-mapping-element">
<div class="media-db-plugin-property-mapping-element-property-name-wrapper">
<pre class="media-db-plugin-property-mapping-element-property-name"><code>{property.property}</code></pre>
</div>
{#if property.locked}
<div class="media-db-plugin-property-binding-text">property cannot be remapped</div>
{:else}
<select class="dropdown" bind:value={property.mapping}>
{#each propertyMappingOptions as remappingOption}
<option value={remappingOption}>
{remappingOption}
</option>
{/each}
</select>

{#if property.mapping === PropertyMappingOption.Map}
<Icon iconName="arrow-right" />
<div class="media-db-plugin-property-mapping-to">
<input type="text" spellcheck="false" bind:value={property.newProperty} />
</div>

<table class="media-db-plugin-property-mappings-table">
<thead>
<tr>
<th class="col-property">Property</th>
<th class="col-mapping">Mapping</th>
<th class="col-new-name">New name</th>
<th class="col-wikilink">Wikilink</th>
</tr>
</thead>
<tbody>
{#each model.properties as property}
<tr>
<td class="col-property">
<code>{property.property}</code>
</td>

{#if property.locked}
<td class="col-locked" colspan="3">
<div class="media-db-plugin-property-binding-text">property cannot be remapped</div>
</td>
{:else}
<td class="col-mapping">
<select class="dropdown" bind:value={property.mapping}>
{#each propertyMappingOptions as remappingOption}
<option value={remappingOption}>
{remappingOption}
</option>
{/each}
</select>
</td>

<td class="col-new-name">
{#if property.mapping === PropertyMappingOption.Map}
<div class="media-db-plugin-property-mapping-to">
<Icon iconName="arrow-right" />
<input class="media-db-plugin-property-mapping-input" type="text" spellcheck="false" bind:value={property.newProperty} />
</div>
{:else}
<span class="media-db-plugin-property-mapping-to-disabled">—</span>
{/if}
</td>

<td class="col-wikilink">
<label class="media-db-plugin-property-mapping-wikilink-label" title="Convert value to wikilink ([[value]])">
<input type="checkbox" bind:checked={property.wikilink} />
</label>
</td>
{/if}
{/if}
</div>
{/each}
</div>
</tr>
{/each}
</tbody>
</table>

{#if !validationResult?.res}
<div class="media-db-plugin-property-mapping-validation">
{validationResult?.err?.message}
</div>
{/if}

<button
class="media-db-plugin-property-mappings-save-button {validationResult?.res ? 'mod-cta' : 'mod-muted'}"
onclick={() => {
// Sync propertyStates back to model.properties
model.properties.forEach((p, i) => {
Object.assign(p, propertyStates[i]);
});
if (model.validate().res) save(model);
}}
>Save
>
Save
</button>
</div>

<style>
.media-db-plugin-property-mappings-table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
table-layout: fixed; /* prevent overflow from wide cells */
}

/* remove excessive left padding and keep within container */
.media-db-plugin-property-mappings-table th,
.media-db-plugin-property-mappings-table td {
padding: 2px 4px;
border-bottom: 1px solid var(--background-modifier-border);
vertical-align: middle;
}

/* column widths */
.col-property {
width: 25%;
white-space: nowrap;
}

.col-mapping {
width: 20%;
}

.col-new-name {
width: 40%;
}

.col-wikilink {
width: 15%;
text-align: center;
}

/* ensure inner controls don't push table wider than container */
.media-db-plugin-property-mapping-to {
display: flex;
align-items: center;
gap: 4px;
min-width: 0;
}

.media-db-plugin-property-mapping-input,
.media-db-plugin-property-mappings-table select.dropdown {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}

.media-db-plugin-property-mapping-wikilink-label {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.95em;
cursor: pointer;
}

.media-db-plugin-property-mapping-to-disabled {
color: var(--text-muted);
}

/* avoid extra left indentation from <pre> */
.media-db-plugin-property-mappings-table code {
padding: 0;
margin: 0;
}
</style>
40 changes: 39 additions & 1 deletion src/settings/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,54 @@ export function getDefaultSettings(plugin: MediaDbPlugin): MediaDbPluginSettings
const propertyMappingModel: PropertyMappingModel = new PropertyMappingModel(mediaType);

for (const key of Object.keys(metadataObj)) {
propertyMappingModel.properties.push(new PropertyMapping(key, '', PropertyMappingOption.Default, lockedPropertyMappings.contains(key)));
propertyMappingModel.properties.push(
new PropertyMapping(
key,
'',
PropertyMappingOption.Default,
lockedPropertyMappings.contains(key),
false, // wikilink default
),
);
}

propertyMappingModels.push(propertyMappingModel);
}

// MIGRATION: Ensure all property mappings have wikilink defined (for settings loaded from disk)
if (defaultSettings.propertyMappingModels && Array.isArray(defaultSettings.propertyMappingModels)) {
for (const model of defaultSettings.propertyMappingModels) {
if (model.properties && Array.isArray(model.properties)) {
for (const prop of model.properties) {
if (typeof prop.wikilink === 'undefined') {
prop.wikilink = false;
}
}
}
}
}

defaultSettings.propertyMappingModels = propertyMappingModels;
return defaultSettings;
}

/**
* Ensures all property mappings in loaded settings have the wikilink property defined.
*/
export function ensureWikilinkOnPropertyMappings(settings: MediaDbPluginSettings): void {
if (settings.propertyMappingModels && Array.isArray(settings.propertyMappingModels)) {
for (const model of settings.propertyMappingModels) {
if (model.properties && Array.isArray(model.properties)) {
for (const prop of model.properties) {
if (typeof prop.wikilink === 'undefined') {
prop.wikilink = false;
}
}
}
}
}
}

export class MediaDbSettingTab extends PluginSettingTab {
plugin: MediaDbPlugin;

Expand Down
3 changes: 2 additions & 1 deletion src/settings/suggesters/FileSuggest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export class FileSuggest extends AbstractInputSuggest<TFile> {
const lowerCaseInputStr = query.toLowerCase();

// we do two filters because otherwise TS type inference does convert the array to TFile[]
return this.app.vault.getAllLoadedFiles()
return this.app.vault
.getAllLoadedFiles()
.filter(file => file instanceof TFile)
.filter(file => file.path.toLowerCase().contains(lowerCaseInputStr));
}
Expand Down
3 changes: 2 additions & 1 deletion src/settings/suggesters/FolderSuggest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export class FolderSuggest extends AbstractInputSuggest<TFolder> {
const lowerCaseInputStr = query.toLowerCase();

// we do two filters because otherwise TS type inference does convert the array to TFolder[]
return this.app.vault.getAllLoadedFiles()
return this.app.vault
.getAllLoadedFiles()
.filter(file => file instanceof TFolder)
.filter(file => file.path.toLowerCase().contains(lowerCaseInputStr));
}
Expand Down