Merge pull request #231 from ltctceplrm/master
Add checkbox to linkify properties into wikilinks
This commit is contained in:
commit
987922b7db
7 changed files with 181 additions and 41 deletions
|
|
@ -584,7 +584,9 @@ export default class MediaDbPlugin extends Plugin {
|
||||||
newProperties.push(defaultProperty);
|
newProperties.push(defaultProperty);
|
||||||
} else {
|
} else {
|
||||||
// newProperty is just an object and take locked status from default property
|
// 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),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,22 @@ export class PropertyMapper {
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
for (const propertyMapping of propertyMappings) {
|
for (const propertyMapping of propertyMappings) {
|
||||||
if (propertyMapping.property === key) {
|
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) {
|
if (propertyMapping.mapping === PropertyMappingOption.Map) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
newObj[propertyMapping.newProperty] = value;
|
newObj[propertyMapping.newProperty] = finalValue;
|
||||||
} else if (propertyMapping.mapping === PropertyMappingOption.Remove) {
|
} else if (propertyMapping.mapping === PropertyMappingOption.Remove) {
|
||||||
// do nothing
|
// do nothing
|
||||||
} else if (propertyMapping.mapping === PropertyMappingOption.Default) {
|
} else if (propertyMapping.mapping === PropertyMappingOption.Default) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
newObj[key] = value;
|
newObj[key] = finalValue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ export class PropertyMappingModel {
|
||||||
copy(): PropertyMappingModel {
|
copy(): PropertyMappingModel {
|
||||||
const copy = new PropertyMappingModel(this.type);
|
const copy = new PropertyMappingModel(this.type);
|
||||||
for (const property of this.properties) {
|
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);
|
copy.properties.push(propertyCopy);
|
||||||
}
|
}
|
||||||
return copy;
|
return copy;
|
||||||
|
|
@ -87,12 +87,14 @@ export class PropertyMapping {
|
||||||
newProperty: string;
|
newProperty: string;
|
||||||
locked: boolean;
|
locked: boolean;
|
||||||
mapping: PropertyMappingOption;
|
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.property = property;
|
||||||
this.newProperty = newProperty;
|
this.newProperty = newProperty;
|
||||||
this.mapping = mapping;
|
this.mapping = mapping;
|
||||||
this.locked = locked ?? false;
|
this.locked = locked ?? false;
|
||||||
|
this.wikilink = wikilink ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(): { res: boolean; err?: Error } {
|
validate(): { res: boolean; err?: Error } {
|
||||||
|
|
|
||||||
|
|
@ -11,57 +11,145 @@
|
||||||
let { model, save }: Props = $props();
|
let { model, save }: Props = $props();
|
||||||
|
|
||||||
let validationResult: { res: boolean; err?: Error } | undefined = $derived(model.validate());
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="media-db-plugin-property-mappings-model-container">
|
<div class="media-db-plugin-property-mappings-model-container">
|
||||||
<div class="setting-item-name">{capitalizeFirstLetter(model.type)}</div>
|
<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}
|
<table class="media-db-plugin-property-mappings-table">
|
||||||
<Icon iconName="arrow-right" />
|
<thead>
|
||||||
<div class="media-db-plugin-property-mapping-to">
|
<tr>
|
||||||
<input type="text" spellcheck="false" bind:value={property.newProperty} />
|
<th class="col-property">Property</th>
|
||||||
</div>
|
<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}
|
||||||
{/if}
|
</tr>
|
||||||
</div>
|
{/each}
|
||||||
{/each}
|
</tbody>
|
||||||
</div>
|
</table>
|
||||||
|
|
||||||
{#if !validationResult?.res}
|
{#if !validationResult?.res}
|
||||||
<div class="media-db-plugin-property-mapping-validation">
|
<div class="media-db-plugin-property-mapping-validation">
|
||||||
{validationResult?.err?.message}
|
{validationResult?.err?.message}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="media-db-plugin-property-mappings-save-button {validationResult?.res ? 'mod-cta' : 'mod-muted'}"
|
class="media-db-plugin-property-mappings-save-button {validationResult?.res ? 'mod-cta' : 'mod-muted'}"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
// Sync propertyStates back to model.properties
|
|
||||||
model.properties.forEach((p, i) => {
|
|
||||||
Object.assign(p, propertyStates[i]);
|
|
||||||
});
|
|
||||||
if (model.validate().res) save(model);
|
if (model.validate().res) save(model);
|
||||||
}}
|
}}
|
||||||
>Save
|
>
|
||||||
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<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>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -155,16 +155,54 @@ export function getDefaultSettings(plugin: MediaDbPlugin): MediaDbPluginSettings
|
||||||
const propertyMappingModel: PropertyMappingModel = new PropertyMappingModel(mediaType);
|
const propertyMappingModel: PropertyMappingModel = new PropertyMappingModel(mediaType);
|
||||||
|
|
||||||
for (const key of Object.keys(metadataObj)) {
|
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);
|
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;
|
defaultSettings.propertyMappingModels = propertyMappingModels;
|
||||||
return defaultSettings;
|
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 {
|
export class MediaDbSettingTab extends PluginSettingTab {
|
||||||
plugin: MediaDbPlugin;
|
plugin: MediaDbPlugin;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ export class FileSuggest extends AbstractInputSuggest<TFile> {
|
||||||
const lowerCaseInputStr = query.toLowerCase();
|
const lowerCaseInputStr = query.toLowerCase();
|
||||||
|
|
||||||
// we do two filters because otherwise TS type inference does convert the array to TFile[]
|
// 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 instanceof TFile)
|
||||||
.filter(file => file.path.toLowerCase().contains(lowerCaseInputStr));
|
.filter(file => file.path.toLowerCase().contains(lowerCaseInputStr));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ export class FolderSuggest extends AbstractInputSuggest<TFolder> {
|
||||||
const lowerCaseInputStr = query.toLowerCase();
|
const lowerCaseInputStr = query.toLowerCase();
|
||||||
|
|
||||||
// we do two filters because otherwise TS type inference does convert the array to TFolder[]
|
// 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 instanceof TFolder)
|
||||||
.filter(file => file.path.toLowerCase().contains(lowerCaseInputStr));
|
.filter(file => file.path.toLowerCase().contains(lowerCaseInputStr));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue