mirror of
https://github.com/friebetill/obsidian-file-diff.git
synced 2026-07-22 07:40:25 +00:00
Compare commits
17 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d566ded402 | ||
|
|
7bd7dd41c0 | ||
|
|
185f95f30a | ||
|
|
08e488d425 | ||
|
|
ac4ea85fe0 | ||
|
|
00567dea68 | ||
|
|
ad651d827e | ||
|
|
dd31d3d371 | ||
|
|
5426feb1e1 | ||
|
|
fa81f5dd62 | ||
|
|
6cb64e9743 | ||
|
|
e51806b299 | ||
|
|
5e3c691266 | ||
|
|
87fc2b1bd8 | ||
|
|
1f0ee0187b | ||
|
|
6ee9e381d4 | ||
|
|
b1883dc436 |
10 changed files with 877 additions and 553 deletions
|
|
@ -22,6 +22,7 @@
|
|||
"rules": {
|
||||
"class-methods-use-this": "off",
|
||||
"no-use-before-define": "off",
|
||||
"no-restricted-syntax": "off",
|
||||
"no-warning-comments": 1,
|
||||
"semi": [2, "always"],
|
||||
"@typescript-eslint/no-use-before-define": ["error"],
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Obsidian File Diff
|
||||
|
||||
<!-- [](obsidian://show-plugin?id=file-diff) -->
|
||||
[](obsidian://show-plugin?id=file-diff)
|
||||

|
||||
|
||||
## Commands
|
||||
|
|
@ -9,23 +9,18 @@
|
|||
|
||||
`Compare and merge`: Same as `Compare`, but you have the option to merge changes.
|
||||
|
||||
`Find sync conflicts and merge`: Finds all Syncthing conflicts and shows the merge option for each conflict sequentially.
|
||||
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/10923085/216749496-27f0b241-c05b-4aec-ba88-a7c8c91938a6.gif"
|
||||
alt="GIF of a demo show this plugin" width="900" />
|
||||
|
||||
## Motivation
|
||||
|
||||
I created this plugin because I use SyncThing and it bothered me to clean up merge conflicts by hand.
|
||||
I created this plugin because I use [SyncThing](https://syncthing.net/) and it bothered me to clean up merge conflicts by hand.
|
||||
|
||||
## Installation
|
||||
|
||||
At the moment, the [plugin is in review to be an official plugin](https://github.com/obsidianmd/obsidian-releases/pull/1621) (add a like to get it merged faster). Therefore, it must be installed manually:
|
||||
|
||||
1. Download the `main.js`, `styles.css` and `manifest.json` from [the latest release](https://github.com/friebetill/obsidian-file-diff/releases/latest)
|
||||
2. Move the files to the plugin folder `VaultFolder/.obsidian/plugins/obsidian-file-diff/`
|
||||
|
||||
When the plugin is officially release you can follow these steps to install the plugin:
|
||||
|
||||
1. Search for "File Diff" in the community plugins of Obsidian
|
||||
2. Install the plugin
|
||||
3. Enable the plugin
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "file-diff",
|
||||
"name": "File Diff",
|
||||
"version": "1.0.11",
|
||||
"version": "1.1.2",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Shows the differences between two files..",
|
||||
"author": "Till Friebe",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-file-diff",
|
||||
"version": "1.0.11",
|
||||
"version": "1.1.2",
|
||||
"description": "Shows the differences between two files.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { structuredPatch } from 'diff';
|
||||
import { ItemView, TFile } from 'obsidian';
|
||||
import { structuredPatch, diffWords } from 'diff';
|
||||
import { ItemView, TFile, ViewStateResult, WorkspaceLeaf } from 'obsidian';
|
||||
import { Difference } from '../data/difference';
|
||||
import { FileDifferences } from '../data/file_differences';
|
||||
import { preventEmptyString } from '../utils/string_utils';
|
||||
|
|
@ -8,31 +8,41 @@ import { DeleteFileModal } from './modals/delete_file_modal';
|
|||
|
||||
export const VIEW_TYPE_DIFFERENCES = 'differences-view';
|
||||
|
||||
interface ViewState {
|
||||
export interface ViewState {
|
||||
file1: TFile;
|
||||
file2: TFile;
|
||||
showMergeOption: boolean;
|
||||
continueCallback?: (shouldContinue: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
export class DifferencesView extends ItemView {
|
||||
private file1: TFile;
|
||||
constructor(leaf: WorkspaceLeaf) {
|
||||
super(leaf);
|
||||
|
||||
private file2: TFile;
|
||||
this.registerEvent(
|
||||
this.app.vault.on('modify', async (file) => {
|
||||
if (file !== this.state.file1 && file !== this.state.file2) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.updateState();
|
||||
this.build();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private state: ViewState;
|
||||
|
||||
private file1Content: string;
|
||||
|
||||
private file2Content: string;
|
||||
|
||||
private showMergeOption: boolean;
|
||||
|
||||
private fileDifferences: FileDifferences;
|
||||
|
||||
private file1Lines: string[];
|
||||
|
||||
private file2Lines: string[];
|
||||
|
||||
private lineCount: number;
|
||||
|
||||
private wasDeleteModalShown = false;
|
||||
|
||||
override getViewType(): string {
|
||||
|
|
@ -40,64 +50,60 @@ export class DifferencesView extends ItemView {
|
|||
}
|
||||
|
||||
override getDisplayText(): string {
|
||||
if (this.state?.file1 && this.state?.file2) {
|
||||
return (
|
||||
`File Diff: ${this.state.file1.name} ` +
|
||||
`and ${this.state.file2.name}`
|
||||
);
|
||||
}
|
||||
return `File Diff`;
|
||||
}
|
||||
|
||||
override async setState(state: ViewState): Promise<void> {
|
||||
this.file1 = state.file1;
|
||||
this.file2 = state.file2;
|
||||
this.showMergeOption = state.showMergeOption;
|
||||
|
||||
this.registerEvent(
|
||||
this.app.vault.on('modify', async (file) => {
|
||||
if (file === this.file1 || file === this.file2) {
|
||||
await this.updateState();
|
||||
this.build();
|
||||
}
|
||||
})
|
||||
);
|
||||
override async setState(
|
||||
state: ViewState,
|
||||
result: ViewStateResult
|
||||
): Promise<void> {
|
||||
super.setState(state, result);
|
||||
this.state = state;
|
||||
|
||||
await this.updateState();
|
||||
this.build();
|
||||
}
|
||||
|
||||
async onunload(): Promise<void> {
|
||||
this.state.continueCallback?.(false);
|
||||
}
|
||||
|
||||
private async updateState(): Promise<void> {
|
||||
this.file1Content = await this.app.vault.read(this.file1);
|
||||
this.file2Content = await this.app.vault.read(this.file2);
|
||||
if (this.state.file1 == null || this.state.file2 == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.file1Content = await this.app.vault.cachedRead(this.state.file1);
|
||||
this.file2Content = await this.app.vault.cachedRead(this.state.file2);
|
||||
|
||||
this.file1Lines = this.file1Content
|
||||
// Add trailing new line as this removes edge cases
|
||||
.concat('\n')
|
||||
.split('\n')
|
||||
// Streamline empty lines at the end as this remove edge cases
|
||||
// Streamline empty spaces at the end as this remove edge cases
|
||||
.map((line) => line.trimEnd());
|
||||
|
||||
this.file2Lines = this.file2Content
|
||||
// Add trailing new line as this removes edge cases
|
||||
// Add trailing new spaces as this removes edge cases
|
||||
.concat('\n')
|
||||
.split('\n')
|
||||
// Streamline empty lines at the end as this remove edge cases
|
||||
.map((line) => line.trimEnd());
|
||||
|
||||
const parsedDiff = structuredPatch(
|
||||
this.file1.path,
|
||||
this.file2.path,
|
||||
this.state.file1.path,
|
||||
this.state.file2.path,
|
||||
this.file1Lines.join('\n'),
|
||||
this.file2Lines.join('\n')
|
||||
);
|
||||
this.fileDifferences = FileDifferences.fromParsedDiff(parsedDiff);
|
||||
|
||||
this.lineCount = Math.max(
|
||||
this.file1Lines.length -
|
||||
// Count each difference as one line
|
||||
this.fileDifferences.differences.filter(
|
||||
(d) => d.file1Lines.length > 0
|
||||
).length,
|
||||
this.file2Lines.length -
|
||||
// Count each difference as one line
|
||||
this.fileDifferences.differences.filter(
|
||||
(d) => d.file2Lines.length > 0
|
||||
).length
|
||||
);
|
||||
}
|
||||
|
||||
private build(): void {
|
||||
|
|
@ -112,7 +118,7 @@ export class DifferencesView extends ItemView {
|
|||
this.scrollToFirstDifference();
|
||||
if (
|
||||
this.fileDifferences.differences.length === 0 &&
|
||||
this.showMergeOption &&
|
||||
this.state.showMergeOption &&
|
||||
!this.wasDeleteModalShown
|
||||
) {
|
||||
this.wasDeleteModalShown = true;
|
||||
|
|
@ -123,7 +129,8 @@ export class DifferencesView extends ItemView {
|
|||
private buildLines(container: HTMLDivElement): void {
|
||||
let lineCount1 = 0;
|
||||
let lineCount2 = 0;
|
||||
while (lineCount1 <= this.lineCount || lineCount2 <= this.lineCount) {
|
||||
const maxLineCount = Math.max(this.file1Lines.length, this.file2Lines.length)
|
||||
while (lineCount1 <= maxLineCount || lineCount2 <= maxLineCount) {
|
||||
const difference = this.fileDifferences.differences.find(
|
||||
// eslint-disable-next-line no-loop-func
|
||||
(d) =>
|
||||
|
|
@ -157,41 +164,93 @@ export class DifferencesView extends ItemView {
|
|||
container: HTMLDivElement,
|
||||
difference: Difference
|
||||
): void {
|
||||
const triggerRebuild = async (): Promise<void> => {
|
||||
await this.updateState();
|
||||
this.build();
|
||||
};
|
||||
|
||||
if (this.showMergeOption) {
|
||||
if (this.state.showMergeOption) {
|
||||
new ActionLine({
|
||||
difference,
|
||||
file1: this.file1,
|
||||
file2: this.file2,
|
||||
file1: this.state.file1,
|
||||
file2: this.state.file2,
|
||||
file1Content: this.file1Content,
|
||||
file2Content: this.file2Content,
|
||||
triggerRebuild,
|
||||
triggerRebuild: async (): Promise<void> => {
|
||||
await this.updateState();
|
||||
this.build();
|
||||
},
|
||||
}).build(container);
|
||||
}
|
||||
|
||||
// Draw top diff
|
||||
for (let i = 0; i < difference.file1Lines.length; i += 1) {
|
||||
const line = difference.file1Lines[i];
|
||||
container.createDiv({
|
||||
// Necessary to give the line a height when it's empty.
|
||||
text: preventEmptyString(line),
|
||||
cls: 'file-diff__line file-diff__top-line__bg',
|
||||
});
|
||||
const line1 = difference.file1Lines[i];
|
||||
const line2 = difference.file2Lines[i];
|
||||
|
||||
const lineDiv = container.createDiv({ cls: 'file-diff__line file-diff__top-line__bg' });
|
||||
const diffSpans = this.buildDiffLine(line1, line2, 'file-diff_top-line__character');
|
||||
|
||||
// Remove border radius if applicable
|
||||
if (i < difference.file1Lines.length - 1 || difference.file2Lines.length !== 0) {
|
||||
lineDiv.classList.add('file-diff__no-bottom-border');
|
||||
}
|
||||
if (i !== 0) {
|
||||
lineDiv.classList.add('file-diff__no-top-border');
|
||||
}
|
||||
|
||||
lineDiv.appendChild(diffSpans);
|
||||
}
|
||||
|
||||
// Draw bottom diff
|
||||
for (let i = 0; i < difference.file2Lines.length; i += 1) {
|
||||
const line = difference.file2Lines[i];
|
||||
container.createDiv({
|
||||
// Necessary to give the line a height when it's empty.
|
||||
text: preventEmptyString(line),
|
||||
cls: 'file-diff__line file-diff__bottom-line__bg',
|
||||
});
|
||||
const line1 = difference.file1Lines[i];
|
||||
const line2 = difference.file2Lines[i];
|
||||
|
||||
const lineDiv = container.createDiv({ cls: 'file-diff__line file-diff__bottom-line__bg' });
|
||||
const diffSpans = this.buildDiffLine(line2, line1, 'file-diff_bottom-line__character');
|
||||
|
||||
// Remove border radius if applicable
|
||||
if ((i == 0 && difference.file1Lines.length > 0) || i > 0) {
|
||||
lineDiv.classList.add('file-diff__no-top-border');
|
||||
}
|
||||
if (i < difference.file2Lines.length - 1) {
|
||||
lineDiv.classList.add('file-diff__no-bottom-border');
|
||||
}
|
||||
|
||||
lineDiv.appendChild(diffSpans);
|
||||
}
|
||||
}
|
||||
|
||||
private buildDiffLine(line1: string, line2: string, charClass: string) {
|
||||
const fragment = document.createElement('div');
|
||||
|
||||
if (line1 != undefined && line1.length === 0) {
|
||||
fragment.textContent = preventEmptyString(line1);
|
||||
} else if (line1 != undefined && line2 != undefined) {
|
||||
const differences = diffWords(line2, line1);
|
||||
|
||||
for (const difference of differences) {
|
||||
if (difference.removed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const span = document.createElement('span');
|
||||
// Necessary to give the line a height when it's empty.
|
||||
span.textContent = preventEmptyString(difference.value);
|
||||
if (difference.added) {
|
||||
span.classList.add(charClass);
|
||||
}
|
||||
fragment.appendChild(span);
|
||||
}
|
||||
} else if(line1 != undefined && line2 == undefined) {
|
||||
const span = document.createElement('span');
|
||||
// Necessary to give the line a height when it's empty.
|
||||
span.textContent = preventEmptyString(line1);
|
||||
span.classList.add(charClass);
|
||||
fragment.appendChild(span);
|
||||
} else {
|
||||
fragment.textContent = preventEmptyString(line1);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private scrollToFirstDifference(): void {
|
||||
if (this.fileDifferences.differences.length === 0) {
|
||||
return;
|
||||
|
|
@ -215,12 +274,13 @@ export class DifferencesView extends ItemView {
|
|||
|
||||
return new Promise((resolve, reject) => {
|
||||
new DeleteFileModal({
|
||||
file1: this.file1,
|
||||
file2: this.file2,
|
||||
file1: this.state.file1,
|
||||
file2: this.state.file2,
|
||||
onDone: (e) => {
|
||||
if (e) {
|
||||
return reject(e);
|
||||
}
|
||||
this.state.continueCallback?.(true);
|
||||
this.leaf.detach();
|
||||
return resolve();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -54,11 +54,6 @@ export class DeleteFileModal extends Modal {
|
|||
|
||||
this.close();
|
||||
|
||||
const leaf = this.app.workspace.getLeaf();
|
||||
if (leaf != null) {
|
||||
leaf.openFile(this.file1);
|
||||
}
|
||||
|
||||
this.onDone(null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
118
src/main.ts
118
src/main.ts
|
|
@ -3,6 +3,7 @@ import { Plugin, TFile } from 'obsidian';
|
|||
import {
|
||||
DifferencesView,
|
||||
VIEW_TYPE_DIFFERENCES,
|
||||
ViewState,
|
||||
} from './components/differences_view';
|
||||
import { RiskyActionModal } from './components/modals/risky_action_modal';
|
||||
import { SelectFileModal } from './components/modals/select_file_modal';
|
||||
|
|
@ -20,34 +21,21 @@ export default class FileDiffPlugin extends Plugin {
|
|||
id: 'compare',
|
||||
name: 'Compare',
|
||||
editorCallback: async () => {
|
||||
// Get current active file
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
if (activeFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get file to compare
|
||||
const compareFile = await this.getFileToCompare(activeFile);
|
||||
if (compareFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open differences view
|
||||
this.app.workspace.detachLeavesOfType(VIEW_TYPE_DIFFERENCES);
|
||||
|
||||
await this.app.workspace.getLeaf(true).setViewState({
|
||||
type: VIEW_TYPE_DIFFERENCES,
|
||||
active: true,
|
||||
state: {
|
||||
file1: activeFile,
|
||||
file2: compareFile,
|
||||
showMergeOption: false,
|
||||
},
|
||||
this.openDifferencesView({
|
||||
file1: activeFile,
|
||||
file2: compareFile,
|
||||
showMergeOption: false,
|
||||
});
|
||||
|
||||
this.app.workspace.revealLeaf(
|
||||
this.app.workspace.getLeavesOfType(VIEW_TYPE_DIFFERENCES)[0]
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -63,34 +51,54 @@ export default class FileDiffPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// Get current active file
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
if (activeFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get file to compare
|
||||
const compareFile = await this.getFileToCompare(activeFile);
|
||||
if (compareFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open differences view
|
||||
this.app.workspace.detachLeavesOfType(VIEW_TYPE_DIFFERENCES);
|
||||
|
||||
await this.app.workspace.getLeaf(true).setViewState({
|
||||
type: VIEW_TYPE_DIFFERENCES,
|
||||
active: true,
|
||||
state: {
|
||||
file1: activeFile,
|
||||
file2: compareFile,
|
||||
showMergeOption: true,
|
||||
},
|
||||
this.openDifferencesView({
|
||||
file1: activeFile,
|
||||
file2: compareFile,
|
||||
showMergeOption: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
this.app.workspace.revealLeaf(
|
||||
this.app.workspace.getLeavesOfType(VIEW_TYPE_DIFFERENCES)[0]
|
||||
);
|
||||
this.addCommand({
|
||||
id: 'find-sync-conflicts-and-merge',
|
||||
name: 'Find sync conflicts and merge',
|
||||
callback: async () => {
|
||||
// Show warning when this option is selected for the first time
|
||||
if (!localStorage.getItem(this.fileDiffMergeWarningKey)) {
|
||||
await this.showRiskyActionModal();
|
||||
if (!localStorage.getItem(this.fileDiffMergeWarningKey)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const syncConflicts = this.findSyncConflicts();
|
||||
|
||||
for await (const syncConflict of syncConflicts) {
|
||||
const continuePromise = new Promise<boolean>((resolve) => {
|
||||
this.openDifferencesView({
|
||||
file1: syncConflict.originalFile,
|
||||
file2: syncConflict.syncConflictFile,
|
||||
showMergeOption: true,
|
||||
continueCallback: async (shouldContinue: boolean) =>
|
||||
resolve(shouldContinue),
|
||||
});
|
||||
});
|
||||
|
||||
const shouldContinue = await continuePromise;
|
||||
if (!shouldContinue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -136,4 +144,48 @@ export default class FileDiffPlugin extends Plugin {
|
|||
}).open();
|
||||
});
|
||||
}
|
||||
|
||||
async openDifferencesView(state: ViewState): Promise<void> {
|
||||
// Closes all leafs (views) of the type VIEW_TYPE_DIFFERENCES
|
||||
this.app.workspace.detachLeavesOfType(VIEW_TYPE_DIFFERENCES);
|
||||
|
||||
// Opens a new leaf (view) of the type VIEW_TYPE_DIFFERENCES
|
||||
const leaf = this.app.workspace.getLeaf(true);
|
||||
leaf.setViewState({
|
||||
type: VIEW_TYPE_DIFFERENCES,
|
||||
active: true,
|
||||
state,
|
||||
});
|
||||
this.app.workspace.revealLeaf(leaf);
|
||||
}
|
||||
|
||||
findSyncConflicts(): { originalFile: TFile; syncConflictFile: TFile }[] {
|
||||
const syncConflicts: {
|
||||
originalFile: TFile;
|
||||
syncConflictFile: TFile;
|
||||
}[] = [];
|
||||
|
||||
const files = app.vault.getMarkdownFiles();
|
||||
|
||||
for (const file of files) {
|
||||
if (file.name.includes('sync-conflict')) {
|
||||
const originalFileName = file.name.replace(
|
||||
/\.sync-conflict-\d{8}-\d{6}-[A-Z0-9]+/,
|
||||
''
|
||||
);
|
||||
const originalFile = files.find(
|
||||
(f) => f.name === originalFileName && (file.parent?.path ?? "") === (f.parent?.path ?? "")
|
||||
);
|
||||
|
||||
if (originalFile) {
|
||||
syncConflicts.push({
|
||||
originalFile,
|
||||
syncConflictFile: file,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return syncConflicts;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
styles.css
42
styles.css
|
|
@ -13,28 +13,38 @@
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.file-diff__top-line__bg {
|
||||
background-color: #d9f4ef;
|
||||
}
|
||||
|
||||
.file-diff__bottom-line__bg {
|
||||
background-color: #d9edff;
|
||||
}
|
||||
.file-diff__top-line__bg {
|
||||
background-color: color-mix(in srgb, var(--background-primary), #0fc 15%);
|
||||
border-radius: 0.25rem; /* 4px */
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.file-diff__top-line__bg {
|
||||
background-color: #25403b;
|
||||
}
|
||||
.file-diff_top-line__character {
|
||||
background-color: color-mix(in srgb, var(--background-primary), #0fc 50%);
|
||||
border-radius: 0.25rem; /* 4px */
|
||||
}
|
||||
|
||||
.file-diff__bottom-line__bg {
|
||||
background-color: #25394b;
|
||||
}
|
||||
.file-diff__bottom-line__bg {
|
||||
background-color: color-mix(in srgb, var(--background-primary), #08f 15%);
|
||||
border-radius: 0.25rem; /* 4px */
|
||||
}
|
||||
|
||||
.file-diff_bottom-line__character {
|
||||
background-color: color-mix(in srgb, var(--background-primary), #08f 50%);
|
||||
border-radius: 0.25rem; /* 4px */
|
||||
}
|
||||
|
||||
.file-diff__no-bottom-border {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.file-diff__no-top-border {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.file-diff__action-line {
|
||||
color: #919191;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Tailwind CSS */
|
||||
|
|
|
|||
|
|
@ -10,5 +10,10 @@
|
|||
"1.0.8": "0.15.0",
|
||||
"1.0.9": "0.15.0",
|
||||
"1.0.10": "0.15.0",
|
||||
"1.0.11": "0.15.0"
|
||||
"1.0.11": "0.15.0",
|
||||
"1.0.12": "0.15.0",
|
||||
"1.0.13": "0.15.0",
|
||||
"1.1.0": "0.15.0",
|
||||
"1.1.1": "0.15.0",
|
||||
"1.1.2": "0.15.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue