Skip to content

Commit

Permalink
fix: subdir issue with file suggestion component
Browse files Browse the repository at this point in the history
chore: reorg code a bit more. more to come yet.
chore: update project metadata
  • Loading branch information
inhumantsar committed Apr 25, 2024
1 parent 9a2396a commit 7ad1e0e
Show file tree
Hide file tree
Showing 22 changed files with 495 additions and 104,283 deletions.
2 changes: 1 addition & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: inhumantsar
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: inhumantsar
Expand Down
15 changes: 15 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://biomejs.dev/schemas/1.7.1/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"files": {
"ignore": [ "node_modules/**/*", "src/**/*.test.ts", "src/**/*.svelte" ]
}
}
18 changes: 10 additions & 8 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { MarkdownView, Notice, Plugin } from 'obsidian';
import { DEFAULT_SETTINGS } from './src/const';
import { createFrontMatter, createFrontMatterPropSettings, createFrontMatterProps } from './src/frontmatter';
import { Logger } from './src/logger';
import { getNewFilePath } from "./src/lib/files";
import { Logger } from './src/lib/logger';
import { removeTrailingSlash } from './src/lib/util';
import { SlurpNewNoteModal } from './src/modals/new-note';
import { fetchHtml, mergeMetadata, parseMarkdown, parseMetadata, parsePage } from './src/parse';
import { SlurpSettingsTab } from './src/settings';
import type { FormatterArgs, IArticle, IFrontMatterSettings, IFrontMatterTagSettings, ISettings, ISettingsV0, TFrontMatterProps } from './src/types';
import { getNewFilePath, removeTrailingSlash } from './src/util';

export default class SlurpPlugin extends Plugin {
settings!: ISettings;
Expand All @@ -27,7 +28,7 @@ export default class SlurpPlugin extends Plugin {
});

this.registerObsidianProtocolHandler("slurp", async (e) => {
if (!e.url || e.url == "") console.error("URI is empty or undefined");
if (!e.url || e.url === "") console.error("URI is empty or undefined");

try {
this.slurp(e.url);
Expand All @@ -37,10 +38,10 @@ export default class SlurpPlugin extends Plugin {

onunload() { }

migrateSettingsV0toV1(loadedSettings: Object): ISettings {
migrateSettingsV0toV1(loadedSettings: ISettingsV0 | ISettings): ISettings {
// only v0 lacks the settingsVersion key
if (Object.keys(loadedSettings).contains("settingsVersion")) return loadedSettings as ISettings;
if (Object.keys(loadedSettings).length == 0) return DEFAULT_SETTINGS;
if (Object.keys(loadedSettings).length === 0) return DEFAULT_SETTINGS;

const v0 = loadedSettings as ISettingsV0;

Expand Down Expand Up @@ -70,7 +71,8 @@ export default class SlurpPlugin extends Plugin {
this.settings.defaultPath = DEFAULT_SETTINGS.defaultPath;
}

migrateObjToMap<K, V>(obj: Object) {
migrateObjToMap<K, V>(obj: { [key: string]: V; }) {
// biome-ignore lint/suspicious/noPrototypeBuiltins: <explanation>
if (!obj.hasOwnProperty('keys')) {
if (Object.keys(obj).length === 0)
return new Map<K, V>();
Expand Down Expand Up @@ -121,10 +123,10 @@ export default class SlurpPlugin extends Plugin {
this.logger.debug("parsed page", article);

// find metadata that readability doesn't pick up
const parsedMetadata = parseMetadata(doc, this.fmProps, this.settings.fm.tags.prefix, this.settings.fm.tags.case)
const parsedMetadata = parseMetadata(doc, this.fmProps, this.settings.fm.tags.prefix, this.settings.fm.tags.case);
this.logger.debug("parsed metadata", parsedMetadata);

const mergedMetadata = mergeMetadata(article, parsedMetadata)
const mergedMetadata = mergeMetadata(article, parsedMetadata);
this.logger.debug("merged metadata", parsedMetadata);

const md = parseMarkdown(article.content);
Expand Down
2 changes: 1 addition & 1 deletion manifest-beta.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "slurp",
"name": "Slurp",
"version": "0.1.8",
"version": "0.1.9",
"minAppVersion": "0.15.0",
"description": "Slurps webpages and saves them as clean, uncluttered Markdown.",
"author": "inhumantsar",
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "slurp",
"name": "Slurp",
"version": "0.1.8",
"version": "0.1.9",
"minAppVersion": "0.15.0",
"description": "Slurps webpages and saves them as clean, uncluttered Markdown.",
"author": "inhumantsar",
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
"author": "",
"keywords": [
"html-to-markdown",
"readability",
"obsidian",
"obsidian-plugin"
],
"author": "inhumantsar",
"license": "MIT",
"devDependencies": {
"@tsconfig/svelte": "^5.0.4",
Expand Down
90 changes: 90 additions & 0 deletions src/components/file-suggestion/file-input-suggest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { AbstractInputSuggest, TFolder, type App, type TAbstractFile } from "obsidian";
import { damerauLevenshtein } from "../../lib/damerau-levenshtein";

/**
* Provides suggestions for file and folder paths.
*/
export class FileInputSuggest extends AbstractInputSuggest<TAbstractFile> {
app: App;
inputEl: HTMLDivElement | HTMLInputElement;
// biome-ignore lint/suspicious/noExplicitAny: obsidian demands it
callback: (value: TAbstractFile, evt: MouseEvent | KeyboardEvent) => any = () => { };
filter: "file" | "folder" | "both" = "both";

/**
* Creates a new instance of the FileInputSuggest class.
* @param app - The application instance. Required to list files/folders in the Vault.
* @param textInputEl - An input or editable div.
*/
constructor(app: App, textInputEl: HTMLDivElement | HTMLInputElement) {
super(app, textInputEl);
this.inputEl = textInputEl;
this.app = app;
}

/**
* Calculates the similarity score between a query and a path.
* The score is based on the Damerau-Levenshtein distance between the query and path,
* with a multiplier that favors partial substring matches and prefix matches.
*
* @param query - The query string.
* @param path - The path string.
* @returns The similarity score between the query and path.
*/
static similarityScore(query: string, path: string): number {
// augment the edit distance with a multiplier which favours partial substring matches
const q = query.toLowerCase();
const p = path.toLowerCase();

return damerauLevenshtein(q, p) * (p.includes(q) ? 0.2 : 1) * (p.startsWith(q) ? 0.1 : 1);
};

/**
* Recurses through the entire directory tree to get the complete list of folders.
* @param folder - The folder to start the recursion from. If not provided, the root folder of the app's vault is used.
* @returns A flat array of folder objects.
*/
getFolders(folder?: TFolder): TFolder[] {
const f = (folder || this.app.vault.getRoot());
const filteredChildren = f.children
.filter((val): val is TFolder => val instanceof TFolder);
const childFolders = filteredChildren
.map((folder) => this.getFolders(folder));
const flatChildFolders = childFolders.flat();
return [f, ...flatChildFolders];
}

/**
* Retrieves the suggestions for the given query.
* @param query - The query string.
* @returns An array of file or folder objects which match the query.
*/
protected getSuggestions(query: string): TAbstractFile[] {
const files = [
...this.filter !== "file" ? this.getFolders() : [],
...this.filter !== "folder" ? this.app.vault.getFiles() : []
];
files.sort((a, b) => FileInputSuggest.similarityScore(query, a.name) - FileInputSuggest.similarityScore(query, b.name));
return files;
}

/**
* Adds the suggestion for the given file to the popover.
* @param file - The file or folder to render.
* @param el - The HTML element to render the suggestion in.
*/
renderSuggestion(file: TAbstractFile, el: HTMLElement) {
el.appendText(file.path);
}

/**
* Fired when the user selects a given suggestion and performs the necessary actions.
* @param file - The file or folder that was selected.
* @param evt - The event that triggered the selection.
*/
async selectSuggestion(file: TAbstractFile, evt: MouseEvent | KeyboardEvent) {
super.setValue(file.path);
this.callback(file, evt);
this.close();
}
}
58 changes: 58 additions & 0 deletions src/components/file-suggestion/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { TextComponent, type App, type TAbstractFile } from "obsidian";
import { FileInputSuggest } from "./file-input-suggest";

/**
* Represents a component for file suggestions.
* @class
*/
export class FileSuggestionComponent extends TextComponent {
fileInputSuggest: FileInputSuggest;

/**
* Creates an <input> which will offer a popover list of suggested files and/or folders.
* @constructor
* @param {HTMLElement} containerEl - The container element to append the FileSuggester to.
* @param {App} app - The App instance. Required to list files/folders in the Vault.
*/
constructor(containerEl: HTMLElement, app: App) {
super(containerEl);
containerEl.appendChild(this.inputEl);
this.fileInputSuggest = new FileInputSuggest(app, this.inputEl);
}

/**
* Sets the callback function to be executed when a file is selected.
*
* @param cb - The callback function to be executed. It takes two parameters: the selected file and the event that triggered the selection.
* @returns The current instance of the `FileSuggester` class.
*/
// biome-ignore lint/suspicious/noExplicitAny: obsidian demands it
onSelect(cb: (value: TAbstractFile, evt: MouseEvent | KeyboardEvent) => any): this {
this.fileInputSuggest.onSelect(cb);
this.fileInputSuggest.callback = cb;
return this;
}

/**
* Sets the component up to suggest files, folders, or both.
*
* @param filter - The filter to apply. Can be "file", "folder", or "both". Defaults to "both".
* @returns The current instance of the file suggester.
*/
addFilter(filter: "file" | "folder" | "both" = "both"): this {
this.fileInputSuggest.filter = filter;
return this;
}

/**
* Sets the limit for the number of files and/or folders suggested.
*
* @param limit - The maximum number of files and/or folders to suggest. Defaults to 100.
* @returns The current instance of the `FileSuggester` class.
*/
addLimit(limit = 100): this {
this.fileInputSuggest.limit = limit;
return this;
}
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
<script lang="ts">
import { flip } from "svelte/animate";
import { crossfade } from "svelte/transition";
import { quintOut } from "svelte/easing";
import { FrontMatterProp, validateFrontMatterProps } from "../frontmatter";
import { sortFrontMatterItems } from "../util";
import { crossfade } from "svelte/transition";
import { FrontMatterProp, sortFrontMatterItems, validateFrontMatterProps } from "../frontmatter";
// for the order-shift animation
const [send, receive] = crossfade({ duration: 350 });
export let props: FrontMatterProp[];
Expand Down
Loading

0 comments on commit 7ad1e0e

Please sign in to comment.