From b107e394bb464e107a0191e657c849123f6bce49 Mon Sep 17 00:00:00 2001 From: luxmargos Date: Tue, 27 Feb 2024 19:06:51 +0900 Subject: [PATCH] Refine folder location options for export --- .vscode/extensions.json | 3 + .vscode/settings.json | 18 +++++- manifest.json | 2 +- package.json | 2 +- src/export_pack/export_utils.ts | 35 +++++++----- src/i18n/trans/en.ts | 11 +++- src/main.ts | 54 ++++++++++++++---- src/settings/setting_types.ts | 10 +++- src/settings/settings.ts | 36 ++++++++++-- src/settings/settings_tab.ts | 97 +++++++++++++++++---------------- versions.json | 3 +- 11 files changed, 188 insertions(+), 83 deletions(-) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c83e263 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["esbenp.prettier-vscode"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 870fb81..13e6f5c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,21 @@ { + // "editor.defaultFormatter": "esbenp.prettier-vscode", + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.tabSize": 2, + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.tabSize": 2, + }, + "editor.formatOnSave": true, + "prettier.tabWidth": 2, + "prettier.printWidth": 180, + "files.exclude": { + "**/.git": false + }, "files.associations": { "*.json":"jsonc" } -} \ No newline at end of file + } + \ No newline at end of file diff --git a/manifest.json b/manifest.json index 02cfd21..63a1b0f 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "image-magician", "name": "Image Magician", - "version": "0.1.7", + "version": "0.1.8", "minAppVersion": "0.15.0", "description": "Supports viewing and exporting various image formats powerd by ImageMagick.", "author": "luxmargos", diff --git a/package.json b/package.json index 749f7aa..e06c7ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-image-magician-plugin", - "version": "0.1.7", + "version": "0.1.8", "description": "This is a plugin for Obsidian (https://obsidian.md). Supports viewing and exporting various image formats using ImageMagick.", "main": "main.js", "scripts": { diff --git a/src/export_pack/export_utils.ts b/src/export_pack/export_utils.ts index 0937762..ea4afea 100644 --- a/src/export_pack/export_utils.ts +++ b/src/export_pack/export_utils.ts @@ -1,7 +1,10 @@ import { TFile, normalizePath } from "obsidian"; import * as bp from "path-browserify"; import { ExportFormat, ExportPathData, exportFormatList } from "./export_types"; -import { ImgkExportSettings } from "../settings/setting_types"; +import { + ImgkExportSettings, + ImgkFolderDeterminer, +} from "../settings/setting_types"; import { ImgkRuntimeExportSettings } from "../settings/settings_as_func"; import { resolveExportDstInfo } from "../engines/imgEngine"; import { MainPluginContext } from "../context"; @@ -29,11 +32,11 @@ export const buildFileNameFormat = (prefix: string, suffix: string) => { }; /** - * - * @param settings - * @param srcFile - * @param specificDst Optional. Specifies the destination file path. If provided, 'settings' will be ignored. Used for instant export. - * @returns + * + * @param settings + * @param srcFile + * @param specificDst Optional. Specifies the destination file path. If provided, 'settings' will be ignored. Used for instant export. + * @returns */ export const genExportPath = ( settings: ImgkExportSettings, @@ -43,7 +46,7 @@ export const genExportPath = ( const srcFilePath: string = srcFile instanceof TFile ? srcFile.path : srcFile; - const dir = bp.dirname(srcFilePath); + const srcFileDir = bp.dirname(srcFilePath); const srcFileName = bp.basename(srcFilePath); let srcFileNameWithoutExt = bp.basename(srcFilePath); @@ -98,16 +101,22 @@ export const genExportPath = ( return undefined; } - let dirText = dir; - if (!settings.pathOpts.asRelativePath) { - if (dir.length > 0 && dir !== ".") { - dirText = settings.pathOpts.exportDirAbs + "/" + dir; + let dirText = srcFileDir; + const fd = settings.pathOpts.folderDeterminer; + + if (fd === ImgkFolderDeterminer.Absolute) { + dirText = settings.pathOpts.exportDirAbs; + } else if ( + fd === ImgkFolderDeterminer.AbsoluteAndReflectFolderStructure + ) { + if (srcFileDir.length > 0 && srcFileDir !== ".") { + dirText = settings.pathOpts.exportDirAbs + "/" + srcFileDir; } else { dirText = settings.pathOpts.exportDirAbs; } } else { - if (dir.length > 0 && dir !== ".") { - dirText = dir + "/" + settings.pathOpts.exportDirRel; + if (srcFileDir.length > 0 && srcFileDir !== ".") { + dirText = srcFileDir + "/" + settings.pathOpts.exportDirRel; } else { dirText = settings.pathOpts.exportDirRel; } diff --git a/src/i18n/trans/en.ts b/src/i18n/trans/en.ts index 01b3c3c..fb787c0 100644 --- a/src/i18n/trans/en.ts +++ b/src/i18n/trans/en.ts @@ -78,9 +78,14 @@ export default { DOUBLE_EXTS_BLOCKER_DESC: "Avoid export if source file has at least two extensions. The filter determine file is already exported from another source. e.g, 'MyImage.psd.exported.png'", - AS_RELATIVE_FOLDER: "As relative folder", - AS_RELATIVE_FOLDER_DESC: - "If turned on, all exported images will be generated relative to their source file. Otherwise, they will be generated into the absolute folder.", + FOLDER_LOCATION: "Folder location", + FOLDER_LOCATION_DESC: + "Specify the folder location where the generated file will be placed.", + + FOLDER_LOCATION_TYPE_RELATIVE: "Relative", + FOLDER_LOCATION_TYPE_ABSOLUTE_WITH_REFLECT_STRUCTURE: + "Absolute, reflect source's folder structure", + FOLDER_LOCATION_TYPE_ABSOLUTE: "Absolute", FOLDER_ABSOLUTE: "Folder (Absolute)", FOLDER_RELATIVE: "Folder (Relative)", diff --git a/src/main.ts b/src/main.ts index 6edb46f..8dd7263 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,6 +17,7 @@ import { DEFAULT_INSTANT_EXPORT_SETTINGS, DEFAULT_SETTINGS, SettingsUtil, + migrageFolderDeterminer, } from "./settings/settings"; import { MainPlugin, MainPluginContext } from "./context"; import { ImgkPluginSettingTab } from "./settings/settings_tab"; @@ -35,9 +36,13 @@ import { asTFile, isTFile } from "./vault_util"; import { t } from "./i18n/t"; import { clearCaches } from "./img_cache"; import { cloneDeep } from "lodash-es"; -import { ImgkPluginSettings } from "./settings/setting_types"; +import { + ImgkFolderDeterminer, + ImgkPluginSettings, +} from "./settings/setting_types"; import fileUrl from "file-url"; import { logLevelMobilePatcher } from "./utils/log_utils"; +import packageJson from "../package.json"; export default class ImgMagicianPlugin extends MainPlugin { settings: ImgkPluginSettings; @@ -429,31 +434,34 @@ export default class ImgMagicianPlugin extends MainPlugin { this.app.vault.adapter.getBasePath(); debug("Reveal BasePath with Desktop"); - } else{ + } else { try { - if( + if ( //@ts-ignore this.app.vault.adapter["getBasePath"] && //@ts-ignore - typeof this.app.vault.adapter["getBasePath"] === "function" - ){ + typeof this.app.vault.adapter["getBasePath"] === + "function" + ) { debug("Reveal BasePath with Func"); - //@ts-ignore - const getBasePathFunc = this.app.vault.adapter.getBasePath; + const getBasePathFunc = + //@ts-ignore + this.app.vault.adapter.getBasePath; this.baseResourcePath = getBasePathFunc(); } } catch (err) {} } - if(this.baseResourcePath){ - try{ + if (this.baseResourcePath) { + try { const fileUrlString = fileUrl(this.baseResourcePath); const url = new URL(fileUrlString); this.baseResourcePath = url.pathname; - }catch(e){} + } catch (e) {} - if(this.baseResourcePath){ - this.baseResourcePathIdx = this.baseResourcePath?.length ?? -1; + if (this.baseResourcePath) { + this.baseResourcePathIdx = + this.baseResourcePath?.length ?? -1; } } @@ -534,6 +542,28 @@ export default class ImgMagicianPlugin extends MainPlugin { const savedData = await this.loadData(); this.settings = Object.assign({}, DEFAULT_SETTINGS, savedData); + //MINGRATION + //Legacy + if (this.settings.version === undefined) { + } else { + } + // + + migrageFolderDeterminer( + this.settings.instantExport.pathOpts, + ImgkFolderDeterminer.Relative + ); + + for (const autoExportSettings of this.settings.autoExportList) { + migrageFolderDeterminer( + autoExportSettings.pathOpts, + ImgkFolderDeterminer.AbsoluteAndReflectFolderStructure + ); + } + + this.settings.version = packageJson.version; + //END OF MIGRATION + this.settingsUtil = new SettingsUtil(this.settings); } diff --git a/src/settings/setting_types.ts b/src/settings/setting_types.ts index 68890df..d5ce53f 100644 --- a/src/settings/setting_types.ts +++ b/src/settings/setting_types.ts @@ -15,6 +15,12 @@ export enum ImgkFileFilterType { DoubleExtsBlocker = 4, } +export enum ImgkFolderDeterminer { + Relative = 0, + AbsoluteAndReflectFolderStructure = 1, + Absolute = 2, +} + export interface ImgkFileFilter { active: boolean; type: ImgkFileFilterType; @@ -35,7 +41,9 @@ export interface ImgkExportPath { sourceFilters: ImgkTextFilter[]; useBuiltInSourceFilters: boolean; builtInSourceFilters: ImgkFileFilter[]; - asRelativePath: boolean; + /** @deprecated */ + asRelativePath?: boolean; + folderDeterminer: ImgkFolderDeterminer; exportDirAbs: string; exportDirRel: string; useCustomFileNameFormat: boolean; diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 88bcf75..a0eb389 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -15,11 +15,14 @@ import { import { asTFile, isTFile } from "../vault_util"; import packageJson from "../../package.json"; import { + ImgkExportPath, ImgkExportSettings, ImgkFileFilterType, + ImgkFolderDeterminer, ImgkPluginSettings, } from "./setting_types"; import { ExportFormatPng } from "../export_pack/export_types"; +import { UnsupportedChannelKindOffset } from "@webtoon/psd/dist/utils"; export const DEFAULT_EXPORT_SUPPORTED_FORMATS = [ "psd", @@ -107,7 +110,9 @@ export const DEFAULT_EXPORT_SETTINGS: ImgkExportSettings = { ], sourceExts: [], sourceFilters: [], - asRelativePath: false, + // asRelativePath: false, + folderDeterminer: + ImgkFolderDeterminer.AbsoluteAndReflectFolderStructure, exportDirAbs: "Exported Images", exportDirRel: "", useCustomFileNameFormat: false, @@ -117,14 +122,37 @@ export const DEFAULT_EXPORT_SETTINGS: ImgkExportSettings = { }, }; -const cloneDefaultExportSettings = () => { +export const migrageFolderDeterminer = ( + pathOpts: ImgkExportPath, + defaultFolderDeterminer: ImgkFolderDeterminer +) => { + if (pathOpts["asRelativePath"] !== undefined) { + console.log("migrate ", JSON.stringify(pathOpts, null, 2)); + + const value: boolean = pathOpts["asRelativePath"]; + if (value === true) { + pathOpts.folderDeterminer = ImgkFolderDeterminer.Relative; + } else { + pathOpts.folderDeterminer = + ImgkFolderDeterminer.AbsoluteAndReflectFolderStructure; + } + + delete pathOpts["asRelativePath"]; + } + + if (pathOpts["folderDeterminer"] === undefined) { + pathOpts.folderDeterminer = defaultFolderDeterminer; + } +}; + +const cloneDefaultExportSettingsForInstant = () => { const defaultSettings = cloneDeep(DEFAULT_EXPORT_SETTINGS); - defaultSettings.pathOpts.asRelativePath = true; + defaultSettings.pathOpts.folderDeterminer = ImgkFolderDeterminer.Relative; return defaultSettings; }; export const DEFAULT_INSTANT_EXPORT_SETTINGS: ImgkExportSettings = - cloneDefaultExportSettings(); + cloneDefaultExportSettingsForInstant(); export const getWarnList = () => { if (satisfies(apiVersion, ">=1.5.3")) { diff --git a/src/settings/settings_tab.ts b/src/settings/settings_tab.ts index 22f6630..63a0f8e 100644 --- a/src/settings/settings_tab.ts +++ b/src/settings/settings_tab.ts @@ -29,6 +29,7 @@ import { exportFormatList } from "../export_pack/export_types"; import { ImgkExportSettings, ImgkFileFilterType, + ImgkFolderDeterminer, ImgkImageSize, ImgkPluginSettings, ImgkSizeAdjustType, @@ -373,12 +374,12 @@ export class ImgkPluginSettingTab extends PluginSettingTab { } } else { try { - if (settings.pathOpts.sourceExts.length < 1) { + if (pathOpts.sourceExts.length < 1) { throw new Error("No source extensions"); } - const firstSourceExt = settings.pathOpts.sourceExts[0]; - const otherExts = settings.pathOpts.sourceExts.filter( + const firstSourceExt = pathOpts.sourceExts[0]; + const otherExts = pathOpts.sourceExts.filter( (item, itemIndex) => { return itemIndex > 0; } @@ -386,9 +387,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { let testFileName = "My Image."; let testFilePath = testFileName + firstSourceExt; - let srcDir = normalizeObsidianDir( - settings.pathOpts.sourceDir - ); + let srcDir = normalizeObsidianDir(pathOpts.sourceDir); if (srcDir.length > 0) { testFilePath = `${srcDir}/${testFilePath}`; } @@ -442,10 +441,10 @@ export class ImgkPluginSettingTab extends PluginSettingTab { sourceFolderSet.addText((comp: TextComponent) => { comp.inputEl.classList.add("imgk-wide-input"); - comp.setValue(settings.pathOpts.sourceDir); + comp.setValue(pathOpts.sourceDir); comp.onChange((value) => { - settings.pathOpts.sourceDir = normalizeObsidianDir(value); + pathOpts.sourceDir = normalizeObsidianDir(value); refreshExportPreview(); }); }); @@ -454,9 +453,9 @@ export class ImgkPluginSettingTab extends PluginSettingTab { .setName(t("RECURSIVE")) .setDesc(t("RECURSIVE_DESC")) .addToggle((comp) => { - comp.setValue(settings.pathOpts.recursiveSources); + comp.setValue(pathOpts.recursiveSources); comp.onChange((value) => { - settings.pathOpts.recursiveSources = value; + pathOpts.recursiveSources = value; }); }); @@ -466,10 +465,10 @@ export class ImgkPluginSettingTab extends PluginSettingTab { t("IMAGE_FORMAT_FILTER_DESC"), t("FORMATS_PLACEHOLDER"), false, - () => settings.pathOpts.sourceExts, + () => pathOpts.sourceExts, () => [], (value: string[]) => { - settings.pathOpts.sourceExts = value; + pathOpts.sourceExts = value; }, () => { refreshExportPreview(); @@ -481,7 +480,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { t("FILTERS_DESC"), false, containerEl, - settings.pathOpts.sourceFilters, + pathOpts.sourceFilters, () => { return { active: true, @@ -510,7 +509,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { const builtInFiltersName = t("BUILT_IN_FILTERS"); const refreshBuiltInFiltersName = () => { let activeCount = 0; - settings.pathOpts.builtInSourceFilters.forEach((item) => { + pathOpts.builtInSourceFilters.forEach((item) => { if (item.active) { activeCount += 1; } @@ -550,7 +549,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { }); builtInFiltersListDiv = this.createListDiv(containerEl); - for (const filter of settings.pathOpts.builtInSourceFilters) { + for (const filter of pathOpts.builtInSourceFilters) { const filterSet = new Setting(builtInFiltersListDiv); if (filter.type === ImgkFileFilterType.DoubleExtsBlocker) { filterSet.setName(t("DOUBLE_EXTS_BLOCKER")); @@ -574,7 +573,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { dstSet.setHeading(); const refreshExportView = () => { - if (pathOpts.asRelativePath) { + if (pathOpts.folderDeterminer === ImgkFolderDeterminer.Relative) { exportDirAbsSet.settingEl.hide(); exportDirRelativeSet.settingEl.show(); } else { @@ -585,15 +584,30 @@ export class ImgkPluginSettingTab extends PluginSettingTab { refreshExportPreview(); }; exportPathTypeSet = new Setting(containerEl); - exportPathTypeSet.setName(t("AS_RELATIVE_FOLDER")); - exportPathTypeSet.setDesc(t("AS_RELATIVE_FOLDER_DESC")); + exportPathTypeSet.setName(t("FOLDER_LOCATION")); + exportPathTypeSet.setDesc(t("FOLDER_LOCATION_DESC")); - exportPathTypeSet.addToggle((comp: ToggleComponent) => { - comp.setValue(pathOpts.asRelativePath); - comp.onChange((value) => { - pathOpts.asRelativePath = value; + exportPathTypeSet.addDropdown((comp: DropdownComponent) => { + comp.addOption( + ImgkFolderDeterminer.Relative.toString(), + t("FOLDER_LOCATION_TYPE_RELATIVE") + ); + comp.addOption( + ImgkFolderDeterminer.AbsoluteAndReflectFolderStructure.toString(), + t("FOLDER_LOCATION_TYPE_ABSOLUTE_WITH_REFLECT_STRUCTURE") + ); + comp.addOption( + ImgkFolderDeterminer.Absolute.toString(), + t("FOLDER_LOCATION_TYPE_ABSOLUTE") + ); + + comp.onChange((value: string) => { + const valueNum = Number(value) as ImgkFolderDeterminer; + pathOpts.folderDeterminer = valueNum; refreshExportView(); }); + + comp.setValue(pathOpts.folderDeterminer.toString()); }); exportDirAbsSet = new Setting(containerEl); @@ -626,13 +640,13 @@ export class ImgkPluginSettingTab extends PluginSettingTab { const refreshFileNameFormatSets = () => { customFileNameFormatSet?.settingEl.toggle( - settings.pathOpts.useCustomFileNameFormat + pathOpts.useCustomFileNameFormat ); fileNameFormatPrefixSet?.settingEl.toggle( - !settings.pathOpts.useCustomFileNameFormat + !pathOpts.useCustomFileNameFormat ); fileNameFormatSuffixSet?.settingEl.toggle( - !settings.pathOpts.useCustomFileNameFormat + !pathOpts.useCustomFileNameFormat ); refreshExportPreview(); }; @@ -641,8 +655,8 @@ export class ImgkPluginSettingTab extends PluginSettingTab { fileNameFormatSet.setName("Use custom file name format"); fileNameFormatSet.addToggle((comp) => { comp.onChange((value) => { - settings.pathOpts.useCustomFileNameFormat = - !settings.pathOpts.useCustomFileNameFormat; + pathOpts.useCustomFileNameFormat = + !pathOpts.useCustomFileNameFormat; refreshFileNameFormatSets(); }); @@ -653,7 +667,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { let customNameFormatSetComp: TextComponent; const refreshCustomNameFormatState = () => { - if (settings.pathOpts.customFileNameFormat.trim().length < 1) { + if (pathOpts.customFileNameFormat.trim().length < 1) { customFileNameFormatSet.settingEl.addClass("imgk-warning"); customNameFormatSetComp?.inputEl.addClass("imgk-warning"); } else { @@ -663,17 +677,14 @@ export class ImgkPluginSettingTab extends PluginSettingTab { }; const setCustomNameFormatControlValue = () => { - customNameFormatSetComp?.setValue( - settings.pathOpts.customFileNameFormat - ); + customNameFormatSetComp?.setValue(pathOpts.customFileNameFormat); }; customFileNameFormatSet.setName(t("FILE_NAME_FORMAT")); customFileNameFormatSet.addExtraButton((comp) => { comp.setIcon("reset"); comp.onClick(() => { - settings.pathOpts.customFileNameFormat = - DEFAULT_FILE_NAME_FORMAT; + pathOpts.customFileNameFormat = DEFAULT_FILE_NAME_FORMAT; setCustomNameFormatControlValue(); refreshCustomNameFormatState(); refreshExportPreview(); @@ -683,7 +694,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { customNameFormatSetComp = comp; comp.inputEl.classList.add("imgk-wide-input"); comp.onChange((value: string) => { - settings.pathOpts.customFileNameFormat = value; + pathOpts.customFileNameFormat = value; refreshExportPreview(); refreshCustomNameFormatState(); }); @@ -693,9 +704,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { let fileNamePrefixSetComp: TextComponent; const setFileNamePrefixControlValue = () => { - fileNamePrefixSetComp?.setValue( - settings.pathOpts.fileNameFormatPrefix - ); + fileNamePrefixSetComp?.setValue(pathOpts.fileNameFormatPrefix); }; fileNameFormatPrefixSet = new Setting(containerEl); @@ -703,8 +712,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { fileNameFormatPrefixSet.addExtraButton((comp) => { comp.setIcon("reset"); comp.onClick(() => { - settings.pathOpts.fileNameFormatPrefix = - DEFAULT_FILE_NAME_PREFIX; + pathOpts.fileNameFormatPrefix = DEFAULT_FILE_NAME_PREFIX; setFileNamePrefixControlValue(); refreshExportPreview(); }); @@ -712,7 +720,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { fileNameFormatPrefixSet.addText((comp: TextComponent) => { fileNamePrefixSetComp = comp; comp.onChange((value) => { - settings.pathOpts.fileNameFormatPrefix = value; + pathOpts.fileNameFormatPrefix = value; refreshExportPreview(); }); setFileNamePrefixControlValue(); @@ -720,9 +728,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { let fileNameSuffixSetComp: TextComponent; const setFileNameSuffixControlValue = () => { - fileNameSuffixSetComp?.setValue( - settings.pathOpts.fileNameFormatSuffix - ); + fileNameSuffixSetComp?.setValue(pathOpts.fileNameFormatSuffix); }; fileNameFormatSuffixSet = new Setting(containerEl); @@ -730,8 +736,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { fileNameFormatSuffixSet.addExtraButton((comp) => { comp.setIcon("reset"); comp.onClick(() => { - settings.pathOpts.fileNameFormatSuffix = - DEFAULT_FILE_NAME_SUFFIX; + pathOpts.fileNameFormatSuffix = DEFAULT_FILE_NAME_SUFFIX; setFileNameSuffixControlValue(); refreshExportPreview(); }); @@ -739,7 +744,7 @@ export class ImgkPluginSettingTab extends PluginSettingTab { fileNameFormatSuffixSet.addText((comp: TextComponent) => { fileNameSuffixSetComp = comp; comp.onChange((value) => { - settings.pathOpts.fileNameFormatSuffix = value; + pathOpts.fileNameFormatSuffix = value; refreshExportPreview(); }); setFileNameSuffixControlValue(); diff --git a/versions.json b/versions.json index c47f1fa..d0cdbcc 100644 --- a/versions.json +++ b/versions.json @@ -6,5 +6,6 @@ "0.1.4": "0.15.0", "0.1.5": "0.15.0", "0.1.6": "0.15.0", - "0.1.7": "0.15.0" + "0.1.7": "0.15.0", + "0.1.8": "0.15.0" } \ No newline at end of file