mirror of
https://github.com/quorafind/Obsidian-Float-Search.git
synced 2026-07-22 07:30:25 +00:00
Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16fcf917a6 | ||
|
|
fd4bf87a1f | ||
|
|
1e1ee35f9d | ||
|
|
e3201d9ed6 |
5 changed files with 320 additions and 14 deletions
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"id": "float-search",
|
"id": "float-search",
|
||||||
"name": "Floating Search",
|
"name": "Floating Search",
|
||||||
"version": "4.2.0",
|
"version": "4.3.0",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "1.2.0",
|
||||||
"description": "You can use search view in modal/leaf/popout window now.",
|
"description": "You can use search view in modal/leaf/popout window now.",
|
||||||
"author": "Boninall",
|
"author": "Boninall",
|
||||||
"authorUrl": "https://github.com/Quorafind",
|
"authorUrl": "https://github.com/Quorafind",
|
||||||
"isDesktopOnly": false
|
"isDesktopOnly": false
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "float-search",
|
"name": "float-search",
|
||||||
"version": "4.2.0",
|
"version": "4.3.0",
|
||||||
"description": "You can use search view in modal/leaf/popout window now.",
|
"description": "You can use search view in modal/leaf/popout window now.",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
PaneType,
|
PaneType,
|
||||||
Plugin,
|
Plugin,
|
||||||
prepareFuzzySearch,
|
prepareFuzzySearch,
|
||||||
|
prepareSimpleSearch,
|
||||||
renderResults,
|
renderResults,
|
||||||
SearchResult,
|
SearchResult,
|
||||||
requireApiVersion,
|
requireApiVersion,
|
||||||
|
|
@ -67,6 +68,9 @@ interface FloatSearchSettings {
|
||||||
defaultViewType: searchType;
|
defaultViewType: searchType;
|
||||||
cmdkTriggerKey: CmdkTriggerKey;
|
cmdkTriggerKey: CmdkTriggerKey;
|
||||||
cmdkDoubleTapInterval: number;
|
cmdkDoubleTapInterval: number;
|
||||||
|
cmdkQuickCreate: boolean;
|
||||||
|
cmdkQuickCreateFolder: string;
|
||||||
|
cmdkQuickCreateTitleFormat: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: FloatSearchSettings = {
|
const DEFAULT_SETTINGS: FloatSearchSettings = {
|
||||||
|
|
@ -83,6 +87,9 @@ const DEFAULT_SETTINGS: FloatSearchSettings = {
|
||||||
defaultViewType: "modal",
|
defaultViewType: "modal",
|
||||||
cmdkTriggerKey: "Shift",
|
cmdkTriggerKey: "Shift",
|
||||||
cmdkDoubleTapInterval: 300,
|
cmdkDoubleTapInterval: 300,
|
||||||
|
cmdkQuickCreate: false,
|
||||||
|
cmdkQuickCreateFolder: "",
|
||||||
|
cmdkQuickCreateTitleFormat: "YYYYMMDDHHmmss",
|
||||||
};
|
};
|
||||||
|
|
||||||
const allViews: viewType[] = [
|
const allViews: viewType[] = [
|
||||||
|
|
@ -1377,6 +1384,54 @@ class FloatSearchSettingTab extends PluginSettingTab {
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
containerEl.createEl("h3", { text: "Quick Create" });
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Enable quick create")
|
||||||
|
.setDesc(
|
||||||
|
"When no exact match is found in quick search, show an option to create a new note with the search text as content."
|
||||||
|
)
|
||||||
|
.addToggle((toggle) => {
|
||||||
|
toggle
|
||||||
|
.setValue(this.plugin.settings.cmdkQuickCreate)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.cmdkQuickCreate = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Quick create folder")
|
||||||
|
.setDesc(
|
||||||
|
"Folder to create new notes in. Leave empty for vault root."
|
||||||
|
)
|
||||||
|
.addText((text) => {
|
||||||
|
text.setPlaceholder("e.g. Inbox")
|
||||||
|
.setValue(this.plugin.settings.cmdkQuickCreateFolder)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.cmdkQuickCreateFolder =
|
||||||
|
value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Title format")
|
||||||
|
.setDesc(
|
||||||
|
"Timestamp format for the note title. Tokens: YYYY, MM, DD, HH, mm, ss."
|
||||||
|
)
|
||||||
|
.addText((text) => {
|
||||||
|
text.setPlaceholder("YYYYMMDDHHmmss")
|
||||||
|
.setValue(
|
||||||
|
this.plugin.settings.cmdkQuickCreateTitleFormat
|
||||||
|
)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.cmdkQuickCreateTitleFormat =
|
||||||
|
value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1896,7 +1951,11 @@ interface CmdkResult {
|
||||||
pathMatch: SearchResult | null;
|
pathMatch: SearchResult | null;
|
||||||
heading?: string;
|
heading?: string;
|
||||||
headingMatch?: SearchResult | null;
|
headingMatch?: SearchResult | null;
|
||||||
type: "file" | "heading";
|
content?: string;
|
||||||
|
contentMatch?: SearchResult | null;
|
||||||
|
line?: number;
|
||||||
|
type: "file" | "heading" | "content" | "create";
|
||||||
|
createQuery?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FloatSearchCmdkModal extends SuggestModal<CmdkResult> {
|
class FloatSearchCmdkModal extends SuggestModal<CmdkResult> {
|
||||||
|
|
@ -1998,8 +2057,31 @@ class FloatSearchCmdkModal extends SuggestModal<CmdkResult> {
|
||||||
return sb - sa;
|
return sb - sa;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if there is an exact name match (for quick-create gating)
|
||||||
|
const queryLower = query.trim().toLowerCase();
|
||||||
|
const hasExactMatch = fileResults.some(
|
||||||
|
(r) => r.file.basename.toLowerCase() === queryLower
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultsToShow = fileResults.slice(0, this.limit);
|
||||||
|
|
||||||
|
// Append "quick create" option when enabled and no exact match
|
||||||
|
if (
|
||||||
|
this.plugin.settings.cmdkQuickCreate &&
|
||||||
|
!hasExactMatch &&
|
||||||
|
query.trim().length > 0
|
||||||
|
) {
|
||||||
|
resultsToShow.push({
|
||||||
|
file: null as any,
|
||||||
|
nameMatch: null,
|
||||||
|
pathMatch: null,
|
||||||
|
type: "create",
|
||||||
|
createQuery: query.trim(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Render file results immediately
|
// Render file results immediately
|
||||||
chooser.setSuggestions(fileResults.slice(0, this.limit));
|
chooser.setSuggestions(resultsToShow);
|
||||||
|
|
||||||
// Phase 2: heading search — progressive, batched via setTimeout
|
// Phase 2: heading search — progressive, batched via setTimeout
|
||||||
if (query.trim().length >= 2) {
|
if (query.trim().length >= 2) {
|
||||||
|
|
@ -2009,6 +2091,14 @@ class FloatSearchCmdkModal extends SuggestModal<CmdkResult> {
|
||||||
chooser,
|
chooser,
|
||||||
signal
|
signal
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Phase 3: content search — progressive, async (reads file content)
|
||||||
|
this.progressiveContentSearch(
|
||||||
|
files,
|
||||||
|
query,
|
||||||
|
chooser,
|
||||||
|
signal
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2061,11 +2151,114 @@ class FloatSearchCmdkModal extends SuggestModal<CmdkResult> {
|
||||||
setTimeout(processBatch, 0);
|
setTimeout(processBatch, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private progressiveContentSearch(
|
||||||
|
files: TFile[],
|
||||||
|
query: string,
|
||||||
|
chooser: any,
|
||||||
|
signal: AbortSignal
|
||||||
|
) {
|
||||||
|
const BATCH_SIZE = 50;
|
||||||
|
const MAX_CONTENT_RESULTS = 20;
|
||||||
|
const DURATION_LIMIT = 5; // ms before yielding
|
||||||
|
const simpleSearch = prepareSimpleSearch(query);
|
||||||
|
let idx = 0;
|
||||||
|
let added = 0;
|
||||||
|
|
||||||
|
const processBatch = async () => {
|
||||||
|
if (signal.aborted) return;
|
||||||
|
const start = performance.now();
|
||||||
|
|
||||||
|
for (; idx < files.length; idx++) {
|
||||||
|
if (signal.aborted || added >= MAX_CONTENT_RESULTS) return;
|
||||||
|
|
||||||
|
// Adaptive yielding: check time every BATCH_SIZE items
|
||||||
|
if (idx % BATCH_SIZE === 0 && idx > 0) {
|
||||||
|
if (performance.now() - start > DURATION_LIMIT) {
|
||||||
|
setTimeout(processBatch, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = files[idx];
|
||||||
|
if (file.extension !== "md") continue;
|
||||||
|
|
||||||
|
let text: string;
|
||||||
|
try {
|
||||||
|
text = await this.app.vault.cachedRead(file);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = simpleSearch(text);
|
||||||
|
if (!result) continue;
|
||||||
|
|
||||||
|
// Compute line number and context from first match offset
|
||||||
|
const matchStart = result.matches[0][0];
|
||||||
|
let line = 0;
|
||||||
|
let lineStart = 0;
|
||||||
|
for (let i = 0; i < matchStart; i++) {
|
||||||
|
if (text.charCodeAt(i) === 10) {
|
||||||
|
line++;
|
||||||
|
lineStart = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let lineEnd = text.indexOf("\n", lineStart);
|
||||||
|
if (lineEnd === -1) lineEnd = text.length;
|
||||||
|
const lineText = text.substring(lineStart, lineEnd).trim();
|
||||||
|
|
||||||
|
// Recompute match on the line for highlight offsets
|
||||||
|
const lineMatch = simpleSearch(lineText);
|
||||||
|
|
||||||
|
chooser.addSuggestion({
|
||||||
|
file,
|
||||||
|
nameMatch: null,
|
||||||
|
pathMatch: null,
|
||||||
|
content: lineText,
|
||||||
|
contentMatch: lineMatch,
|
||||||
|
line,
|
||||||
|
type: "content",
|
||||||
|
} as CmdkResult);
|
||||||
|
added++;
|
||||||
|
if (added >= MAX_CONTENT_RESULTS) return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start after heading search has a chance to render
|
||||||
|
setTimeout(processBatch, 50);
|
||||||
|
}
|
||||||
|
|
||||||
renderSuggestion(result: CmdkResult, el: HTMLElement) {
|
renderSuggestion(result: CmdkResult, el: HTMLElement) {
|
||||||
el.addClass("mod-complex");
|
el.addClass("mod-complex");
|
||||||
|
|
||||||
|
if (result.type === "create") {
|
||||||
|
el.addClass("float-search-cmdk-create-item");
|
||||||
|
const contentEl = el.createDiv("suggestion-content");
|
||||||
|
const titleEl = contentEl.createDiv("suggestion-title");
|
||||||
|
titleEl.setText("Create new note");
|
||||||
|
const noteEl = contentEl.createDiv("suggestion-note");
|
||||||
|
const folder =
|
||||||
|
this.plugin.settings.cmdkQuickCreateFolder || "/";
|
||||||
|
noteEl.setText(
|
||||||
|
`"${result.createQuery}" → ${folder}`
|
||||||
|
);
|
||||||
|
const auxEl = el.createDiv("suggestion-aux");
|
||||||
|
const flair = auxEl.createSpan("suggestion-flair");
|
||||||
|
setIcon(flair, "plus");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const contentEl = el.createDiv("suggestion-content");
|
const contentEl = el.createDiv("suggestion-content");
|
||||||
|
|
||||||
if (result.type === "heading" && result.heading) {
|
if (result.type === "content" && result.content) {
|
||||||
|
const titleEl = contentEl.createDiv("suggestion-title");
|
||||||
|
if (result.contentMatch) {
|
||||||
|
renderResults(titleEl, result.content, result.contentMatch);
|
||||||
|
} else {
|
||||||
|
titleEl.setText(result.content);
|
||||||
|
}
|
||||||
|
const noteEl = contentEl.createDiv("suggestion-note");
|
||||||
|
noteEl.setText(result.file.path);
|
||||||
|
} else if (result.type === "heading" && result.heading) {
|
||||||
const titleEl = contentEl.createDiv("suggestion-title");
|
const titleEl = contentEl.createDiv("suggestion-title");
|
||||||
if (result.headingMatch) {
|
if (result.headingMatch) {
|
||||||
renderResults(titleEl, result.heading, result.headingMatch);
|
renderResults(titleEl, result.heading, result.headingMatch);
|
||||||
|
|
@ -2106,6 +2299,11 @@ class FloatSearchCmdkModal extends SuggestModal<CmdkResult> {
|
||||||
result: CmdkResult,
|
result: CmdkResult,
|
||||||
evt: MouseEvent | KeyboardEvent
|
evt: MouseEvent | KeyboardEvent
|
||||||
) {
|
) {
|
||||||
|
if (result.type === "create") {
|
||||||
|
this.quickCreateNote(result.createQuery ?? "", evt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const leaf = Keymap.isModEvent(evt)
|
const leaf = Keymap.isModEvent(evt)
|
||||||
? this.app.workspace.getLeaf("tab")
|
? this.app.workspace.getLeaf("tab")
|
||||||
: this.app.workspace.getMostRecentLeaf() ??
|
: this.app.workspace.getMostRecentLeaf() ??
|
||||||
|
|
@ -2113,8 +2311,9 @@ class FloatSearchCmdkModal extends SuggestModal<CmdkResult> {
|
||||||
|
|
||||||
const eState: Record<string, any> = {};
|
const eState: Record<string, any> = {};
|
||||||
if (result.type === "heading" && result.heading) {
|
if (result.type === "heading" && result.heading) {
|
||||||
// Use subpath to jump to heading
|
|
||||||
eState.subpath = "#" + result.heading;
|
eState.subpath = "#" + result.heading;
|
||||||
|
} else if (result.type === "content" && result.line != null) {
|
||||||
|
eState.line = result.line;
|
||||||
}
|
}
|
||||||
|
|
||||||
leaf.setViewState({
|
leaf.setViewState({
|
||||||
|
|
@ -2122,28 +2321,96 @@ class FloatSearchCmdkModal extends SuggestModal<CmdkResult> {
|
||||||
state: { file: result.file.path },
|
state: { file: result.file.path },
|
||||||
active: true,
|
active: true,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (eState.subpath) {
|
if (eState.subpath || eState.line != null) {
|
||||||
// Apply eState after view is ready
|
|
||||||
leaf.setEphemeralState(eState);
|
leaf.setEphemeralState(eState);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async quickCreateNote(
|
||||||
|
content: string,
|
||||||
|
evt: MouseEvent | KeyboardEvent
|
||||||
|
) {
|
||||||
|
const settings = this.plugin.settings;
|
||||||
|
const fmt = settings.cmdkQuickCreateTitleFormat || "YYYYMMDDHHmmss";
|
||||||
|
const title = this.formatTimestamp(new Date(), fmt);
|
||||||
|
|
||||||
|
// Resolve target folder
|
||||||
|
let folderPath = settings.cmdkQuickCreateFolder.trim();
|
||||||
|
if (!folderPath || folderPath === "/") {
|
||||||
|
folderPath = "";
|
||||||
|
}
|
||||||
|
// Ensure folder exists
|
||||||
|
if (folderPath) {
|
||||||
|
const folder =
|
||||||
|
this.app.vault.getAbstractFileByPath(folderPath);
|
||||||
|
if (!folder) {
|
||||||
|
await this.app.vault.createFolder(folderPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = folderPath
|
||||||
|
? `${folderPath}/${title}.md`
|
||||||
|
: `${title}.md`;
|
||||||
|
|
||||||
|
const file = await this.app.vault.create(filePath, content);
|
||||||
|
|
||||||
|
const leaf = Keymap.isModEvent(evt)
|
||||||
|
? this.app.workspace.getLeaf("tab")
|
||||||
|
: this.app.workspace.getMostRecentLeaf() ??
|
||||||
|
this.app.workspace.getLeaf();
|
||||||
|
|
||||||
|
await leaf.setViewState({
|
||||||
|
type: "markdown",
|
||||||
|
state: { file: file.path },
|
||||||
|
active: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatTimestamp(date: Date, fmt: string): string {
|
||||||
|
const pad = (n: number, len = 2) =>
|
||||||
|
String(n).padStart(len, "0");
|
||||||
|
const tokens: Record<string, string> = {
|
||||||
|
YYYY: String(date.getFullYear()),
|
||||||
|
YY: String(date.getFullYear()).slice(-2),
|
||||||
|
MM: pad(date.getMonth() + 1),
|
||||||
|
DD: pad(date.getDate()),
|
||||||
|
HH: pad(date.getHours()),
|
||||||
|
mm: pad(date.getMinutes()),
|
||||||
|
ss: pad(date.getSeconds()),
|
||||||
|
};
|
||||||
|
let result = fmt;
|
||||||
|
// Replace longest tokens first to avoid partial matches
|
||||||
|
for (const token of ["YYYY", "YY", "MM", "DD", "HH", "mm", "ss"]) {
|
||||||
|
result = result.split(token).join(tokens[token]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Called by internal Chooser on selection change
|
// Called by internal Chooser on selection change
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
onSelectedChange = debounce(
|
onSelectedChange = debounce(
|
||||||
(result: CmdkResult, _evt: Event | null) => {
|
(result: CmdkResult, _evt: Event | null) => {
|
||||||
if (result?.file) this.showPreview(result.file);
|
if (result?.type === "create" || !result?.file) {
|
||||||
|
this.hidePreview();
|
||||||
|
} else {
|
||||||
|
this.showPreview(result);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
100
|
100
|
||||||
);
|
);
|
||||||
|
|
||||||
async showPreview(file: TFile) {
|
private hidePreview() {
|
||||||
|
if (this.previewEl) {
|
||||||
|
this.previewEl.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async showPreview(result: CmdkResult) {
|
||||||
if (!this.previewEl) {
|
if (!this.previewEl) {
|
||||||
this.previewEl = this.bodyEl.createDiv(
|
this.previewEl = this.bodyEl.createDiv(
|
||||||
"float-search-cmdk-preview"
|
"float-search-cmdk-preview"
|
||||||
);
|
);
|
||||||
this.modalEl.addClass("float-search-cmdk-expanded");
|
|
||||||
const [leaf, view] = spawnLeafView(
|
const [leaf, view] = spawnLeafView(
|
||||||
this.plugin,
|
this.plugin,
|
||||||
this.previewEl
|
this.previewEl
|
||||||
|
|
@ -2152,7 +2419,29 @@ class FloatSearchCmdkModal extends SuggestModal<CmdkResult> {
|
||||||
this.fileEmbeddedView = view;
|
this.fileEmbeddedView = view;
|
||||||
this.fileLeaf.setPinned(true);
|
this.fileLeaf.setPinned(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.previewEl.show();
|
||||||
|
this.modalEl.addClass("float-search-cmdk-expanded");
|
||||||
|
|
||||||
|
const file = result.file;
|
||||||
await this.fileLeaf!.openFile(file, { active: false });
|
await this.fileLeaf!.openFile(file, { active: false });
|
||||||
|
|
||||||
|
const eState: Record<string, any> = {};
|
||||||
|
if (result.type === "heading" && result.heading) {
|
||||||
|
eState.subpath = "#" + result.heading;
|
||||||
|
} else if (result.type === "content" && result.line != null) {
|
||||||
|
eState.line = result.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eState.subpath || eState.line != null) {
|
||||||
|
this.fileLeaf!.setViewState({
|
||||||
|
type: file.extension === "pdf" ? "pdf" : "markdown",
|
||||||
|
state: { file: file.path },
|
||||||
|
}).then(() => {
|
||||||
|
this.fileLeaf?.setEphemeralState(eState);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.inputEl.focus();
|
this.inputEl.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
16
styles.css
16
styles.css
|
|
@ -213,3 +213,19 @@ body:not(.show-file-path) .search-result-file-title .search-result-file-path {
|
||||||
.modal-container.float-search-cmdk-container.mod-dim {
|
.modal-container.float-search-cmdk-container.mod-dim {
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Quick Create item ── */
|
||||||
|
.float-search-cmdk-create-item {
|
||||||
|
border-top: 1px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-search-cmdk-create-item .suggestion-title {
|
||||||
|
color: var(--text-accent);
|
||||||
|
font-weight: var(--font-semibold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-search-cmdk-create-item .suggestion-flair {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--text-accent);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,5 +33,6 @@
|
||||||
"4.0.0": "0.15.0",
|
"4.0.0": "0.15.0",
|
||||||
"4.1.0": "0.15.0",
|
"4.1.0": "0.15.0",
|
||||||
"4.1.1": "0.15.0",
|
"4.1.1": "0.15.0",
|
||||||
"4.2.0": "0.15.0"
|
"4.2.0": "1.2.0",
|
||||||
|
"4.3.0": "1.2.0"
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue