diff --git a/.editorconfig b/.editorconfig index 84b8a66..81f3ec3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,10 +1,10 @@ -# top-most EditorConfig file -root = true - -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -indent_style = tab -indent_size = 4 -tab_width = 4 +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = tab +indent_size = 4 +tab_width = 4 diff --git a/.eslintignore b/.eslintignore index 14ed7c6..e019f3c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,3 @@ -node_modules/ - -main.js +node_modules/ + +main.js diff --git a/.eslintrc b/.eslintrc index b66af83..0807290 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,23 +1,23 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "env": { "node": true }, - "plugins": [ - "@typescript-eslint" - ], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" - ], - "parserOptions": { - "sourceType": "module" - }, - "rules": { - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], - "@typescript-eslint/ban-ts-comment": "off", - "no-prototype-builtins": "off", - "@typescript-eslint/no-empty-function": "off" - } +{ + "root": true, + "parser": "@typescript-eslint/parser", + "env": { "node": true }, + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], + "@typescript-eslint/ban-ts-comment": "off", + "no-prototype-builtins": "off", + "@typescript-eslint/no-empty-function": "off" + } } \ No newline at end of file diff --git a/.gitignore b/.gitignore index fe5a467..975eb18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,26 @@ -# vscode -.vscode - -# Intellij -*.iml -.idea - -# npm -node_modules - -# Don't include the compiled main.js file in the repo. -# They should be uploaded to GitHub releases instead. -main.js - -# Exclude sourcemaps -*.map - -# obsidian -data.json - -# Exclude macOS Finder (System Explorer) View States -.DS_Store - -node_modules -package.json -package-lock.json +# vscode +.vscode + +# Intellij +*.iml +.idea + +# npm +node_modules + +# Don't include the compiled main.js file in the repo. +# They should be uploaded to GitHub releases instead. +main.js + +# Exclude sourcemaps +*.map + +# obsidian +data.json + +# Exclude macOS Finder (System Explorer) View States +.DS_Store + +node_modules +package.json +package-lock.json diff --git a/Demo.png b/Demo.png new file mode 100644 index 0000000..925c938 Binary files /dev/null and b/Demo.png differ diff --git a/FinshotsView.ts b/FinshotsView.ts deleted file mode 100644 index 5a44c05..0000000 --- a/FinshotsView.ts +++ /dev/null @@ -1,445 +0,0 @@ -import FinshotsDailyPlugin, { FinshotsArticle, FINSHOTS_VIEW_TYPE } from 'main'; -import { ItemView, WorkspaceLeaf, Notice } from 'obsidian'; - - -export class FinshotsView extends ItemView { - plugin: FinshotsDailyPlugin; - private article: FinshotsArticle | null = null; - private isLoading = false; - - constructor(leaf: WorkspaceLeaf, plugin: FinshotsDailyPlugin) { - super(leaf); - this.plugin = plugin; - } - - getViewType(): string { - return FINSHOTS_VIEW_TYPE; - } - - getDisplayText(): string { - return "Finshots Daily"; - } - - getIcon(): string { - return "newspaper"; - } - - async onOpen() { - await this.render(); - await this.loadArticle(); - } - - async onClose() { - } - - private async render() { - const container = this.containerEl.children[1]; - container.empty(); - - const headerEl = container.createEl("div", { cls: "finshots-header" }); - headerEl.createEl("h2", { text: "Finshots Daily" }); - - const refreshBtn = headerEl.createEl("button", { - text: "Refresh", - cls: "finshots-refresh-btn" - }); - refreshBtn.addEventListener("click", () => this.loadArticle()); - - const contentEl = container.createEl("div", { cls: "finshots-content" }); - - this.renderContent(contentEl); - this.addStyles(); - } - - private renderContent(contentEl: HTMLElement) { - contentEl.empty(); - - if (this.isLoading) { - contentEl.createEl("div", { - text: "Loading today's article...", - cls: "finshots-loading" - }); - return; - } - - if (!this.article) { - const errorEl = contentEl.createEl("div", { cls: "finshots-error" }); - errorEl.createEl("p", { text: "No article found for today." }); - errorEl.createEl("p", { text: "Click refresh to try again." }); - return; - } - - // Article container - const articleEl = contentEl.createEl("div", { cls: "finshots-article" }); - - // Date - articleEl.createEl("div", { - text: this.article.date, - cls: "finshots-date" - }); - - // Image - if (this.article.imageUrl) { - const imageEl = articleEl.createEl("img", { - cls: "finshots-image" - }); - imageEl.src = this.article.imageUrl; - imageEl.alt = this.article.title; - } - - // Title - const titleEl = articleEl.createEl("h3", { - text: this.article.title, - cls: "finshots-title" - }); - - // Summary if available - if (this.article.summary) { - articleEl.createEl("p", { - text: this.article.summary, - cls: "finshots-summary" - }); - } - - // Read more button - const readMoreEl = articleEl.createEl("a", { - text: "Read Full Article", - cls: "finshots-read-more" - }); - readMoreEl.href = this.article.articleUrl; - readMoreEl.target = "_blank"; - } - - private addStyles() { - const styleEl = document.createElement("style"); - styleEl.textContent = ` - .finshots-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px; - border-bottom: 1px solid var(--background-modifier-border); - margin-bottom: 10px; - } - - .finshots-refresh-btn { - padding: 5px 10px; - background: var(--interactive-accent); - color: var(--text-on-accent); - border: none; - border-radius: 3px; - cursor: pointer; - } - - .finshots-content { - padding: 10px; - } - - .finshots-loading, .finshots-error { - text-align: center; - padding: 20px; - color: var(--text-muted); - } - - .finshots-article { - border: 1px solid var(--background-modifier-border); - border-radius: 5px; - padding: 15px; - background: var(--background-secondary); - } - - .finshots-date { - font-size: 0.8em; - color: var(--text-muted); - margin-bottom: 10px; - } - - .finshots-image { - width: 100%; - max-height: 200px; - object-fit: cover; - border-radius: 3px; - margin-bottom: 10px; - } - - .finshots-title { - margin: 0 0 10px 0; - color: var(--text-normal); - line-height: 1.3; - } - - .finshots-summary { - color: var(--text-muted); - line-height: 1.4; - margin-bottom: 15px; - } - - .finshots-read-more { - display: inline-block; - padding: 8px 15px; - background: var(--interactive-accent); - color: var(--text-on-accent); - text-decoration: none; - border-radius: 3px; - font-size: 0.9em; - } - - .finshots-read-more:hover { - background: var(--interactive-accent-hover); - } - `; - document.head.appendChild(styleEl); - } - - async loadArticle() { - this.isLoading = true; - const contentEl = this.containerEl.querySelector('.finshots-content') as HTMLElement; - if (contentEl) { - this.renderContent(contentEl); - } - - try { - this.article = await this.ParseAndGetArticle(); - } catch (error) { - console.error('Failed to load Finshots article:', error); - new Notice('Failed to load Finshots article'); - this.article = null; - } finally { - this.isLoading = false; - if (contentEl) { - this.renderContent(contentEl); - } - } - } - - private async ParseAndGetArticle(): Promise { - try { - const proxyUrl = 'https://api.allorigins.win/get?url='; - const targetUrl = encodeURIComponent('https://finshots.in/archive/'); - - const response = await fetch(proxyUrl + targetUrl); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - - // Check if we have the expected structure - if (!data || !data.contents) { - throw new Error('Invalid response structure from proxy'); - } - - const html = data.contents; - - // Parse HTML using DOMParser - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - - // Try multiple selectors to find articles - const selectors = [ - '.post-feed article.post-card', - 'article.post-card', - '.post-card', - 'article', - '.post' - ]; - - let firstArticle = null; - let usedSelector = ''; - - for (const selector of selectors) { - firstArticle = doc.querySelector(selector); - if (firstArticle) { - usedSelector = selector; - console.log(`Found article using selector: ${selector}`); - break; - } - } - - if (!firstArticle) { - const articleElements = doc.querySelectorAll('[class*="post"], [class*="article"], [class*="card"]'); - articleElements.forEach((el, index) => { - }); - - throw new Error('Could not find any article in the post feed'); - } - - const titleSelectors = [ - '.post-card-title', - '.post-title', - 'h2', - 'h3', - '[class*="title"]' - ]; - - let titleElement = null; - let title = ''; - - for (const selector of titleSelectors) { - titleElement = firstArticle.querySelector(selector); - if (titleElement) { - title = titleElement.textContent?.trim() || ''; - if (title) { - console.log(`Found title using selector: ${selector}`); - break; - } - } - } - - if (!title) { - title = 'Latest Finshots Article'; - console.log('Using fallback title'); - } - - const imageSelectors = [ - '.post-card-image', - 'img', - '[class*="image"]' - ]; - - let imageElement = null; - let imageUrl = ''; - - for (const selector of imageSelectors) { - imageElement = firstArticle.querySelector(selector); - if (imageElement) { - imageUrl = imageElement.getAttribute('src') || ''; - if (!imageUrl) { - const srcset = imageElement.getAttribute('srcset'); - if (srcset) { - const srcsetUrls = srcset.split(','); - const lastUrl = srcsetUrls[srcsetUrls.length - 1]; - imageUrl = lastUrl.split(' ')[0].trim(); - } - } - if (imageUrl) { - console.log(`Found image using selector: ${selector}`); - break; - } - } - } - - const linkSelectors = [ - '.post-card-content-link', - 'a[href*="/archive/"]', - 'a', - '[href]' - ]; - - let linkElement = null; - let articleUrl = ''; - - for (const selector of linkSelectors) { - linkElement = firstArticle.querySelector(selector); - if (linkElement) { - articleUrl = linkElement.getAttribute('href') || ''; - if (articleUrl && articleUrl !== '#') { - console.log(`Found link using selector: ${selector}`); - break; - } - } - } - - if (articleUrl && !articleUrl.startsWith('http')) { - articleUrl = `https://finshots.in${articleUrl}`; - } - - const dateSelectors = [ - '.post-card-meta-date', - 'time', - '[datetime]', - '[class*="date"]' - ]; - - let dateElement = null; - let dateString = ''; - let formattedDate = new Date().toLocaleDateString(); - - for (const selector of dateSelectors) { - dateElement = firstArticle.querySelector(selector); - if (dateElement) { - dateString = dateElement.getAttribute('datetime') || dateElement.textContent?.trim() || ''; - if (dateString) { - console.log(`Found date using selector: ${selector}`); - break; - } - } - } - - if (dateString) { - try { - const articleDate = new Date(dateString); - if (!isNaN(articleDate.getTime())) { - formattedDate = articleDate.toLocaleDateString(); - } - } catch (error) { - console.warn('Failed to parse date:', dateString); - } - } - - const excerptSelectors = [ - '.post-card-excerpt', - '.excerpt', - 'p', - '[class*="summary"]' - ]; - - let excerptElement = null; - let summary = ''; - - for (const selector of excerptSelectors) { - excerptElement = firstArticle.querySelector(selector); - if (excerptElement) { - summary = excerptElement.textContent?.trim() || ''; - if (summary && summary.length > 20) { // Ensure it's not just a short text - console.log(`Found summary using selector: ${selector}`); - break; - } - } - } - - const tagSelectors = [ - '.post-card-primary-tag', - '.tag', - '[class*="tag"]', - '[class*="category"]' - ]; - - let tagElement = null; - let category = ''; - - for (const selector of tagSelectors) { - tagElement = firstArticle.querySelector(selector); - if (tagElement) { - category = tagElement.textContent?.trim() || ''; - if (category) { - console.log(`Found category using selector: ${selector}`); - break; - } - } - } - - if (imageUrl && !imageUrl.startsWith('http')) { - imageUrl = `https://finshots.in${imageUrl}`; - } - - return { - title, - imageUrl, - articleUrl, - date: formattedDate, - summary: summary ? `${category ? `[${category}] ` : ''}${summary}` : undefined - }; - - } catch (error) { - return { - title: 'Failed to fetch the latest article from Finshots Daily', - imageUrl: '', - articleUrl: 'https://finshots.in/', - date: new Date().toLocaleDateString(), - summary: 'Please check the Finshots website directly or try refreshing.' - }; - } - } -} diff --git a/LICENSE b/LICENSE index e507511..25e02d0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2025 nitishkhurana - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2025 nitishkhurana + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MovedFilesDemo.png b/MovedFilesDemo.png new file mode 100644 index 0000000..2c6d780 Binary files /dev/null and b/MovedFilesDemo.png differ diff --git a/README.md b/README.md index e75f031..2886322 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,39 @@ -# Finshots Daily Plugin - -An Obsidian plugin that fetches and displays the latest articles from Finshots Daily in a dedicated sidebar view. - -## Features - -- **Daily Article Display**: Shows the latest Finshots article with title, image, and summary -- **Auto-refresh**: Automatically refreshes daily at 9 AM -- **Manual Refresh**: Click the refresh button to update the content anytime - -## Usage - -### Opening the Finshots View - -1. **Ribbon Icon**: Click the newspaper icon in the left ribbon -2. **Command Palette**: Use Ctrl/Cmd+P and search for "Open Finshots Daily" -3. The view will open in the right sidebar by default - -**Note**: This plugin is not officially affiliated with Finshots. It's a community-created tool for Obsidian users who want convenient access to Finshots content. +# Move Files Plugin + +When editing a note, open command pallete and run +**"Move Files Plugin: Move linked files and update links"**. +This will create a folder with Name: +"{filename} files" and move all the linked files for e.g. png,pdf,jpg...etc to this folder. It will also udpate the links in the note. + +### Updates +Version 1.1.0 : Added support for moving the open note/md files also to the folder. + +## Settings +Select the below setting to move the open note/md file as well to the folder created. + +![Move Files Setting](Settings.png) + + +## Example +![Demo](Demo.png) + + + +Run the command and all the files are moved to the folder as shown below. + +![FilesMoved](MovedFilesDemo.png) + + +
+ +## Tip + +Use the https://github.com/reorx/obsidian-paste-image-rename +plugin to automatically rename the copied images/file's as per you custom settings. + + +## Acknowledgement + +Developed it to keep the note and their linked files organised so as to keep the vault clean and organized. +Inspired from the Image Collector Plugin +https://github.com/tdaykin/obsidian_image_collector diff --git a/Settings.png b/Settings.png new file mode 100644 index 0000000..ecf84c4 Binary files /dev/null and b/Settings.png differ diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 0c38f47..a5de8b8 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -1,49 +1,49 @@ -import esbuild from "esbuild"; -import process from "process"; -import builtins from "builtin-modules"; - -const banner = -`/* -THIS IS A GENERATED/BUNDLED FILE BY ESBUILD -if you want to view the source, please visit the github repository of this plugin -*/ -`; - -const prod = (process.argv[2] === "production"); - -const context = await esbuild.context({ - banner: { - js: banner, - }, - entryPoints: ["main.ts"], - bundle: true, - external: [ - "obsidian", - "electron", - "@codemirror/autocomplete", - "@codemirror/collab", - "@codemirror/commands", - "@codemirror/language", - "@codemirror/lint", - "@codemirror/search", - "@codemirror/state", - "@codemirror/view", - "@lezer/common", - "@lezer/highlight", - "@lezer/lr", - ...builtins], - format: "cjs", - target: "es2018", - logLevel: "info", - sourcemap: prod ? false : "inline", - treeShaking: true, - outfile: "main.js", - minify: prod, -}); - -if (prod) { - await context.rebuild(); - process.exit(0); -} else { - await context.watch(); -} +import esbuild from "esbuild"; +import process from "process"; +import builtins from "builtin-modules"; + +const banner = +`/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ +`; + +const prod = (process.argv[2] === "production"); + +const context = await esbuild.context({ + banner: { + js: banner, + }, + entryPoints: ["main.ts"], + bundle: true, + external: [ + "obsidian", + "electron", + "@codemirror/autocomplete", + "@codemirror/collab", + "@codemirror/commands", + "@codemirror/language", + "@codemirror/lint", + "@codemirror/search", + "@codemirror/state", + "@codemirror/view", + "@lezer/common", + "@lezer/highlight", + "@lezer/lr", + ...builtins], + format: "cjs", + target: "es2018", + logLevel: "info", + sourcemap: prod ? false : "inline", + treeShaking: true, + outfile: "main.js", + minify: prod, +}); + +if (prod) { + await context.rebuild(); + process.exit(0); +} else { + await context.watch(); +} diff --git a/main copy.ts b/main copy.ts deleted file mode 100644 index 74dfa8e..0000000 --- a/main copy.ts +++ /dev/null @@ -1,733 +0,0 @@ -import { App, Notice, Plugin, PluginSettingTab, Setting, WorkspaceLeaf, ItemView } from 'obsidian'; - -interface FinshotsPluginSettings { - refreshTime: string; - autoRefresh: boolean; - useAPI: boolean; - apiEndpoint: string; -} - -const DEFAULT_SETTINGS: FinshotsPluginSettings = { - refreshTime: '09:00', - autoRefresh: true, - useAPI: false, - apiEndpoint: '' -}; - -interface FinshotsArticle { - title: string; - imageUrl: string; - articleUrl: string; - date: string; - summary?: string; -} - -export const FINSHOTS_VIEW_TYPE = "finshots-daily-view"; - -export class FinshotsView extends ItemView { - plugin: FinshotsDailyPlugin; - private article: FinshotsArticle | null = null; - private isLoading = false; - - constructor(leaf: WorkspaceLeaf, plugin: FinshotsDailyPlugin) { - super(leaf); - this.plugin = plugin; - } - - getViewType(): string { - return FINSHOTS_VIEW_TYPE; - } - - getDisplayText(): string { - return "Finshots Daily"; - } - - getIcon(): string { - return "newspaper"; - } - - async onOpen() { - await this.render(); - await this.loadArticle(); - } - - async onClose() { - } - - private async render() { - const container = this.containerEl.children[1]; - container.empty(); - - const headerEl = container.createEl("div", { cls: "finshots-header" }); - headerEl.createEl("h2", { text: "Finshots Daily" }); - - const refreshBtn = headerEl.createEl("button", { - text: "Refresh", - cls: "finshots-refresh-btn" - }); - refreshBtn.addEventListener("click", () => this.loadArticle()); - - const contentEl = container.createEl("div", { cls: "finshots-content" }); - - this.renderContent(contentEl); - this.addStyles(); - } - - private renderContent(contentEl: HTMLElement) { - contentEl.empty(); - - if (this.isLoading) { - contentEl.createEl("div", { - text: "Loading today's article...", - cls: "finshots-loading" - }); - return; - } - - if (!this.article) { - const errorEl = contentEl.createEl("div", { cls: "finshots-error" }); - errorEl.createEl("p", { text: "No article found for today." }); - errorEl.createEl("p", { text: "Click refresh to try again." }); - return; - } - - // Article container - const articleEl = contentEl.createEl("div", { cls: "finshots-article" }); - - // Date - articleEl.createEl("div", { - text: this.article.date, - cls: "finshots-date" - }); - - // Image - if (this.article.imageUrl) { - const imageEl = articleEl.createEl("img", { - cls: "finshots-image" - }); - imageEl.src = this.article.imageUrl; - imageEl.alt = this.article.title; - } - - // Title - const titleEl = articleEl.createEl("h3", { - text: this.article.title, - cls: "finshots-title" - }); - - // Summary if available - if (this.article.summary) { - articleEl.createEl("p", { - text: this.article.summary, - cls: "finshots-summary" - }); - } - - // Read more button - const readMoreEl = articleEl.createEl("a", { - text: "Read Full Article", - cls: "finshots-read-more" - }); - readMoreEl.href = this.article.articleUrl; - readMoreEl.target = "_blank"; - } - - private addStyles() { - const styleEl = document.createElement("style"); - styleEl.textContent = ` - .finshots-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px; - border-bottom: 1px solid var(--background-modifier-border); - margin-bottom: 10px; - } - - .finshots-refresh-btn { - padding: 5px 10px; - background: var(--interactive-accent); - color: var(--text-on-accent); - border: none; - border-radius: 3px; - cursor: pointer; - } - - .finshots-content { - padding: 10px; - } - - .finshots-loading, .finshots-error { - text-align: center; - padding: 20px; - color: var(--text-muted); - } - - .finshots-article { - border: 1px solid var(--background-modifier-border); - border-radius: 5px; - padding: 15px; - background: var(--background-secondary); - } - - .finshots-date { - font-size: 0.8em; - color: var(--text-muted); - margin-bottom: 10px; - } - - .finshots-image { - width: 100%; - max-height: 200px; - object-fit: cover; - border-radius: 3px; - margin-bottom: 10px; - } - - .finshots-title { - margin: 0 0 10px 0; - color: var(--text-normal); - line-height: 1.3; - } - - .finshots-summary { - color: var(--text-muted); - line-height: 1.4; - margin-bottom: 15px; - } - - .finshots-read-more { - display: inline-block; - padding: 8px 15px; - background: var(--interactive-accent); - color: var(--text-on-accent); - text-decoration: none; - border-radius: 3px; - font-size: 0.9em; - } - - .finshots-read-more:hover { - background: var(--interactive-accent-hover); - } - `; - document.head.appendChild(styleEl); - } - - async loadArticle() { - this.isLoading = true; - const contentEl = this.containerEl.querySelector('.finshots-content') as HTMLElement; - if (contentEl) { - this.renderContent(contentEl); - } - - try { - this.article = await this.scrapeWebsite(); - } catch (error) { - console.error('Failed to load Finshots article:', error); - new Notice('Failed to load Finshots article'); - this.article = null; - } finally { - this.isLoading = false; - if (contentEl) { - this.renderContent(contentEl); - } - } - } - - private async scrapeWebsite(): Promise { - try { - const proxyUrl = 'https://api.allorigins.win/get?url='; - const targetUrl = encodeURIComponent('https://finshots.in/archive/'); - - const response = await fetch(proxyUrl + targetUrl); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - - // Debug: Check what we're getting from the proxy - console.log('Proxy response data:', data); - - // Check if we have the expected structure - if (!data || !data.contents) { - throw new Error('Invalid response structure from proxy'); - } - - const html = data.contents; - - // Debug: Check HTML length and first few characters - console.log('HTML length:', html.length); - console.log('HTML preview:', html.substring(0, 500)); - - // Write HTML to file for debugging - //await this.writeHtmlToVault(html); - - // Parse HTML using DOMParser - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - - // Debug: Check if parsing worked - console.log('Parsed document:', doc); - console.log('Document title:', doc.title); - - // Try multiple selectors to find articles - const selectors = [ - '.post-feed article.post-card', - 'article.post-card', - '.post-card', - 'article', - '.post' - ]; - - let firstArticle = null; - let usedSelector = ''; - - for (const selector of selectors) { - firstArticle = doc.querySelector(selector); - if (firstArticle) { - usedSelector = selector; - console.log(`Found article using selector: ${selector}`); - break; - } - } - - if (!firstArticle) { - // Debug: Log available elements - const allElements = doc.querySelectorAll('*'); - console.log('Total elements found:', allElements.length); - - const articleElements = doc.querySelectorAll('[class*="post"], [class*="article"], [class*="card"]'); - console.log('Potential article elements:', articleElements.length); - - articleElements.forEach((el, index) => { - console.log(`Element ${index}:`, el.className, el.tagName); - }); - - throw new Error('Could not find any article in the post feed'); - } - - console.log('Article element:', firstArticle); - console.log('Article HTML:', firstArticle.outerHTML.substring(0, 500)); - - // Extract title with multiple fallbacks - const titleSelectors = [ - '.post-card-title', - '.post-title', - 'h2', - 'h3', - '[class*="title"]' - ]; - - let titleElement = null; - let title = ''; - - for (const selector of titleSelectors) { - titleElement = firstArticle.querySelector(selector); - if (titleElement) { - title = titleElement.textContent?.trim() || ''; - if (title) { - console.log(`Found title using selector: ${selector}`); - break; - } - } - } - - if (!title) { - title = 'Latest Finshots Article'; - console.log('Using fallback title'); - } - - // Extract image URL with multiple fallbacks - const imageSelectors = [ - '.post-card-image', - 'img', - '[class*="image"]' - ]; - - let imageElement = null; - let imageUrl = ''; - - for (const selector of imageSelectors) { - imageElement = firstArticle.querySelector(selector); - if (imageElement) { - imageUrl = imageElement.getAttribute('src') || ''; - if (!imageUrl) { - const srcset = imageElement.getAttribute('srcset'); - if (srcset) { - const srcsetUrls = srcset.split(','); - const lastUrl = srcsetUrls[srcsetUrls.length - 1]; - imageUrl = lastUrl.split(' ')[0].trim(); - } - } - if (imageUrl) { - console.log(`Found image using selector: ${selector}`); - break; - } - } - } - - // Extract article URL with multiple fallbacks - const linkSelectors = [ - '.post-card-content-link', - 'a[href*="/archive/"]', - 'a', - '[href]' - ]; - - let linkElement = null; - let articleUrl = ''; - - for (const selector of linkSelectors) { - linkElement = firstArticle.querySelector(selector); - if (linkElement) { - articleUrl = linkElement.getAttribute('href') || ''; - if (articleUrl && articleUrl !== '#') { - console.log(`Found link using selector: ${selector}`); - break; - } - } - } - - // Convert relative URL to absolute URL - if (articleUrl && !articleUrl.startsWith('http')) { - articleUrl = `https://finshots.in${articleUrl}`; - } - - // Extract date with multiple fallbacks - const dateSelectors = [ - '.post-card-meta-date', - 'time', - '[datetime]', - '[class*="date"]' - ]; - - let dateElement = null; - let dateString = ''; - let formattedDate = new Date().toLocaleDateString(); - - for (const selector of dateSelectors) { - dateElement = firstArticle.querySelector(selector); - if (dateElement) { - dateString = dateElement.getAttribute('datetime') || dateElement.textContent?.trim() || ''; - if (dateString) { - console.log(`Found date using selector: ${selector}`); - break; - } - } - } - - if (dateString) { - try { - const articleDate = new Date(dateString); - if (!isNaN(articleDate.getTime())) { - formattedDate = articleDate.toLocaleDateString(); - } - } catch (error) { - console.warn('Failed to parse date:', dateString); - } - } - - // Extract excerpt/summary with multiple fallbacks - const excerptSelectors = [ - '.post-card-excerpt', - '.excerpt', - 'p', - '[class*="summary"]' - ]; - - let excerptElement = null; - let summary = ''; - - for (const selector of excerptSelectors) { - excerptElement = firstArticle.querySelector(selector); - if (excerptElement) { - summary = excerptElement.textContent?.trim() || ''; - if (summary && summary.length > 20) { // Ensure it's not just a short text - console.log(`Found summary using selector: ${selector}`); - break; - } - } - } - - // Extract category/tag with multiple fallbacks - const tagSelectors = [ - '.post-card-primary-tag', - '.tag', - '[class*="tag"]', - '[class*="category"]' - ]; - - let tagElement = null; - let category = ''; - - for (const selector of tagSelectors) { - tagElement = firstArticle.querySelector(selector); - if (tagElement) { - category = tagElement.textContent?.trim() || ''; - if (category) { - console.log(`Found category using selector: ${selector}`); - break; - } - } - } - - // Ensure image URL is absolute - if (imageUrl && !imageUrl.startsWith('http')) { - imageUrl = `https://finshots.in${imageUrl}`; - } - - // Log extracted data for debugging - const extractedData = { - title, - imageUrl, - articleUrl, - date: formattedDate, - summary, - category, - usedSelector - }; - - console.log('Extracted article data:', extractedData); - - new Notice(`Found article: ${title}`); - - return { - title, - imageUrl, - articleUrl, - date: formattedDate, - summary: summary ? `${category ? `[${category}] ` : ''}${summary}` : undefined - }; - - } catch (error) { - console.error('Web scraping failed:', error); - new Notice('Failed to scrape Finshots website: ' + error.message); - - // Return a fallback article - return { - title: 'Failed to fetch the latest article from Finshots Daily', - imageUrl: '', - articleUrl: 'https://finshots.in/', - date: new Date().toLocaleDateString(), - summary: 'Please check the Finshots website directly or try refreshing.' - }; - } - } - - // Add this helper method to write HTML to vault for debugging - // private async writeHtmlToVault(html: string): Promise { - // try { - // const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - // const filename = `finshots-debug-${timestamp}.txt`; - - // // Write to vault root - // await this.app.vault.create(filename, html); - - // console.log(`HTML content saved to vault file: ${filename}`); - // new Notice(`Debug HTML saved to: ${filename}`); - - // } catch (error) { - // console.error('Failed to write HTML to vault:', error); - - // // Fallback: try to create in a debug folder - // try { - // const debugFolder = 'Debug'; - // if (!await this.app.vault.adapter.exists(debugFolder)) { - // await this.app.vault.createFolder(debugFolder); - // } - - // const filepath = `${debugFolder}/${filename}`; - // await this.app.vault.create(filepath, html); - // console.log(`HTML saved to: ${filepath}`); - // new Notice(`Debug HTML saved to: ${filepath}`); - // } catch (fallbackError) { - // console.error('Fallback write also failed:', fallbackError); - // } - // } - // } -} - -export default class FinshotsDailyPlugin extends Plugin { - settings: FinshotsPluginSettings; - - private refreshInterval: number | null = null; - - async onload() { - await this.loadSettings(); - - // Register the view - this.registerView( - FINSHOTS_VIEW_TYPE, - (leaf) => new FinshotsView(leaf, this) - ); - - // Add ribbon icon - this.addRibbonIcon('newspaper', 'Open Finshots Daily', () => { - this.activateView(); - }); - - // Add command - this.addCommand({ - id: 'open-finshots-daily', - name: 'Open Finshots Daily', - callback: () => { - this.activateView(); - }, - }); - - // Add settings tab - this.addSettingTab(new FinshotsSettingTab(this.app, this)); - - // Set up auto-refresh - this.setupAutoRefresh(); - } - - onunload() { - if (this.refreshInterval) { - clearInterval(this.refreshInterval); - } - } - - async activateView() { - const { workspace } = this.app; - - let leaf: WorkspaceLeaf | null = null; - const leaves = workspace.getLeavesOfType(FINSHOTS_VIEW_TYPE); - - if (leaves.length > 0) { - // A leaf with our view already exists, use that - leaf = leaves[0]; - } else { - // Our view could not be found in the workspace, create a new leaf - // in the right sidebar for it - leaf = workspace.getRightLeaf(false); - if (leaf) { - if(leaf.getViewState().active) { - await leaf.setViewState({ type: FINSHOTS_VIEW_TYPE, active: false }); - } - await leaf.setViewState({ type: FINSHOTS_VIEW_TYPE, active: true }); - } - } - - // "Reveal" the leaf in case it is in a collapsed sidebar - if (leaf) { - workspace.revealLeaf(leaf); - } - } - - setupAutoRefresh() { - if (this.refreshInterval) { - clearInterval(this.refreshInterval); - } - - if (!this.settings.autoRefresh) { - return; - } - - // Calculate time until next refresh - const now = new Date(); - const [hours, minutes] = this.settings.refreshTime.split(':').map(Number); - - const nextRefresh = new Date(); - nextRefresh.setHours(hours, minutes, 0, 0); - - // If the time has already passed today, schedule for tomorrow - if (nextRefresh <= now) { - nextRefresh.setDate(nextRefresh.getDate() + 1); - } - - const timeUntilRefresh = nextRefresh.getTime() - now.getTime(); - - // Set initial timeout - setTimeout(() => { - this.refreshArticle(); - // Then set up daily interval - this.refreshInterval = window.setInterval(() => { - this.refreshArticle(); - }, 24 * 60 * 60 * 1000); // 24 hours - }, timeUntilRefresh); - } - - async refreshArticle() { - const leaves = this.app.workspace.getLeavesOfType(FINSHOTS_VIEW_TYPE); - for (const leaf of leaves) { - if (leaf.view instanceof FinshotsView) { - await leaf.view.loadArticle(); - } - } - new Notice('Finshots Daily article refreshed'); - } - - async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); - } - - async saveSettings() { - await this.saveData(this.settings); - this.setupAutoRefresh(); // Restart auto-refresh with new settings - } -} - -class FinshotsSettingTab extends PluginSettingTab { - plugin: FinshotsDailyPlugin; - - constructor(app: App, plugin: FinshotsDailyPlugin) { - super(app, plugin); - this.plugin = plugin; - } - - display(): void { - const { containerEl } = this; - - containerEl.empty(); - - containerEl.createEl('h2', { text: 'Finshots Daily Settings' }); - - new Setting(containerEl) - .setName('Auto-refresh') - .setDesc('Automatically refresh the article daily') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.autoRefresh) - .onChange(async (value) => { - this.plugin.settings.autoRefresh = value; - await this.plugin.saveSettings(); - })); - - new Setting(containerEl) - .setName('Refresh time') - .setDesc('Time to refresh the article (24-hour format, e.g., 09:00)') - .addText(text => text - .setPlaceholder('09:00') - .setValue(this.plugin.settings.refreshTime) - .onChange(async (value) => { - // Validate time format - if (/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/.test(value)) { - this.plugin.settings.refreshTime = value; - await this.plugin.saveSettings(); - } - })); - - new Setting(containerEl) - .setName('Use API') - .setDesc('Use API endpoint instead of web scraping (when available)') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.useAPI) - .onChange(async (value) => { - this.plugin.settings.useAPI = value; - await this.plugin.saveSettings(); - })); - - new Setting(containerEl) - .setName('API Endpoint') - .setDesc('API endpoint URL for fetching articles') - .addText(text => text - .setPlaceholder('https://api.finshots.com/daily') - .setValue(this.plugin.settings.apiEndpoint) - .onChange(async (value) => { - this.plugin.settings.apiEndpoint = value; - await this.plugin.saveSettings(); - })); - } -} diff --git a/main.ts b/main.ts index 9cc0329..2f7d204 100644 --- a/main.ts +++ b/main.ts @@ -1,108 +1,149 @@ -import { FinshotsView } from 'FinshotsView'; -import { App, Notice, Plugin, WorkspaceLeaf } from 'obsidian'; - -export interface FinshotsArticle { - title: string; - imageUrl: string; - articleUrl: string; - date: string; - summary?: string; -} - -export const FINSHOTS_VIEW_TYPE = "finshots-daily-view"; - -export default class FinshotsDailyPlugin extends Plugin { - - private refreshInterval: number | null = null; - - async onload() { - - this.registerView( - FINSHOTS_VIEW_TYPE, - (leaf) => new FinshotsView(leaf, this) - ); - - // Add ribbon icon - this.addRibbonIcon('newspaper', 'Open Finshots Daily', () => { - this.activateView(); - }); - - // Add command - this.addCommand({ - id: 'open-finshots-daily', - name: 'Open Finshots Daily', - callback: () => { - this.activateView(); - }, - }); - - // Set up auto-refresh - this.setupAutoRefresh(); - } - - onunload() { - if (this.refreshInterval) { - clearInterval(this.refreshInterval); - } - } - - async activateView() { - const { workspace } = this.app; - - let leaf: WorkspaceLeaf | null = null; - const leaves = workspace.getLeavesOfType(FINSHOTS_VIEW_TYPE); - - if (leaves.length > 0) { - leaf = leaves[0]; - } else { - leaf = workspace.getRightLeaf(false); - if (leaf) { - if(leaf.getViewState().active) { - await leaf.setViewState({ type: FINSHOTS_VIEW_TYPE, active: false }); - } - await leaf.setViewState({ type: FINSHOTS_VIEW_TYPE, active: true }); - } - } - - if (leaf) { - workspace.revealLeaf(leaf); - } - } - - setupAutoRefresh() { - if (this.refreshInterval) { - clearInterval(this.refreshInterval); - } - - // Calculate time until next refresh - const now = new Date(); - const [hours, minutes] = [9, 0]; - - const nextRefresh = new Date(); - nextRefresh.setHours(hours, minutes, 0, 0); - - if (nextRefresh <= now) { - nextRefresh.setDate(nextRefresh.getDate() + 1); - } - - const timeUntilRefresh = nextRefresh.getTime() - now.getTime(); - - setTimeout(() => { - this.refreshArticle(); - this.refreshInterval = window.setInterval(() => { - this.refreshArticle(); - }, 24 * 60 * 60 * 1000); // 24 hours - }, timeUntilRefresh); - } - - async refreshArticle() { - const leaves = this.app.workspace.getLeavesOfType(FINSHOTS_VIEW_TYPE); - for (const leaf of leaves) { - if (leaf.view instanceof FinshotsView) { - await leaf.view.loadArticle(); - } - } - new Notice('Finshots Daily article refreshed'); - } - -} \ No newline at end of file +import { App, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile, TFolder } from 'obsidian'; + + interface MoveFilesPluginSettings { + moveMdFile: boolean; + } + + const DEFAULT_SETTINGS: MoveFilesPluginSettings = { + moveMdFile: false, + }; + + + +export default class MoveFilesPlugin extends Plugin { + settings: MoveFilesPluginSettings = DEFAULT_SETTINGS; + + async onload() { + + await this.loadSettings(); + + this.addCommand({ + id: 'move-linked-files', + name: 'Move linked files and update links', + checkCallback: (checking: boolean) => { + // Obtain the current active file + const activeFile = this.app.workspace.getActiveFile(); + // Check if there's an active file and it's a markdown file + if (activeFile && activeFile instanceof TFile && activeFile.extension === 'md') { + //If the check is true then the command will be shown to the user else the command is not visible + //If it is true then the callback is called with checking as false, so execute the command + if(!checking) + { + // Call the export function with the active file + this.moveFilesToANewFolder(activeFile); + } + return true; + } + return false; + }, + }); + + this.addSettingTab(new class extends PluginSettingTab { + plugin: MoveFilesPlugin; + constructor(app: App, plugin: MoveFilesPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + const { containerEl } = this; + containerEl.empty(); + containerEl.createEl('h2', { text: 'Move Files Plugin Settings' }); + + new Setting(containerEl) + .setName('Move the current open markdown file itself') + .setDesc('If enabled, the markdown file will also be moved to the new folder.') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.moveMdFile) + .onChange(async (value) => { + this.plugin.settings.moveMdFile = value; + await this.plugin.saveSettings(); + })); + } + }(this.app, this)); + } + + onunload() { + + } + + async moveFilesToANewFolder(file: TFile) { + const files = []; + + const embeds = this.app.metadataCache.getFileCache(file)?.embeds ?? []; + const fileLinks = embeds.map(embed => this.app.metadataCache.getFirstLinkpathDest(embed.link, file.path)).filter(file => file?.extension !== 'md'); + + for (const file of fileLinks) { + if (file instanceof TFile) { + files.push(file.path); + } + } + + if (this.settings.moveMdFile) { + // If the setting is enabled, include the markdown file itself + files.push(file.path); + } + + if (files.length === 0) { + new Notice('No linked files found in the markdown file.'); + return; + } + + const targetFolderName = `${file.basename} files`; + const folderExists = this.app.vault.getAbstractFileByPath(targetFolderName); + if (!(folderExists instanceof TFolder) ) + { + await this.app.vault.createFolder(targetFolderName).catch(() => {}); + } + + for (const filePath of files) { + const fileItem = this.app.metadataCache.getFirstLinkpathDest(filePath, file.path); + + if (fileItem instanceof TFile) { + try { + const targetPath = `${targetFolderName}/${fileItem.name}`; + const targetFile = this.app.vault.getAbstractFileByPath(targetPath); + + if(!(targetFile instanceof TFile)) + { + await this.app.fileManager.renameFile(fileItem,targetPath); + + //update the links in open md file + const view = this.app.workspace.getActiveViewOfType(MarkdownView); + + // Make sure the user is editing a Markdown file. + if (view) { + const line = view.editor.getValue(); + const updatedLine = line.replace(fileItem.name,targetPath); + view.editor.setValue(updatedLine); + } + else { + new Notice(`Failed to rename ${targetPath}: no active editor`) + return + } + new Notice(`Moved ${fileItem.name} to ${targetPath} and updated the links`); + } + else + { + new Notice(`Did not move ${fileItem.name} to ${targetPath} as file already exists`); + } + + } catch (error) { + new Notice(`Failed to move file ${fileItem.name}: ${error}`); + console.error(`Failed to move file ${fileItem.name}:`, error); + } + } else { + new Notice(`File not found: ${filePath}`); + console.error(`File not found: ${filePath}`); + } + } + } + + async loadSettings() { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + + async saveSettings() { + await this.saveData(this.settings); + } +} diff --git a/manifest.json b/manifest.json index c4dded9..3a4e705 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ -{ - "id": "finshots-daily", - "name": "Finshots Daily Article", - "version": "1.0.0", - "minAppVersion": "0.15.0", - "description": "A plugin to fetch and display the latest Finshots Daily articles", - "author": "Nitish Khurana", - "authorUrl": "https://github.com/nitishkhurana", - "isDesktopOnly": false -} +{ + "id": "move-files", + "name": "Move Files", + "version": "1.1.0", + "minAppVersion": "0.15.0", + "description": "Moves all the files linked to a open md file to a folder and updates the link in md file.", + "author": "Nitish Khurana", + "authorUrl": "https://github.com/nitishkhurana", + "isDesktopOnly": false +} diff --git a/tsconfig.json b/tsconfig.json index 09c9112..c44b729 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,24 +1,24 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "inlineSourceMap": true, - "inlineSources": true, - "module": "ESNext", - "target": "ES6", - "allowJs": true, - "noImplicitAny": true, - "moduleResolution": "node", - "importHelpers": true, - "isolatedModules": true, - "strictNullChecks": true, - "lib": [ - "DOM", - "ES5", - "ES6", - "ES7" - ] - }, - "include": [ - "**/*.ts" - ] -} +{ + "compilerOptions": { + "baseUrl": ".", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "ES6", + "allowJs": true, + "noImplicitAny": true, + "moduleResolution": "node", + "importHelpers": true, + "isolatedModules": true, + "strictNullChecks": true, + "lib": [ + "DOM", + "ES5", + "ES6", + "ES7" + ] + }, + "include": [ + "**/*.ts" + ] +} diff --git a/version-bump.mjs b/version-bump.mjs index 693e799..d409fa0 100644 --- a/version-bump.mjs +++ b/version-bump.mjs @@ -1,14 +1,14 @@ -import { readFileSync, writeFileSync } from "fs"; - -const targetVersion = process.env.npm_package_version; - -// read minAppVersion from manifest.json and bump version to target version -let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); -const { minAppVersion } = manifest; -manifest.version = targetVersion; -writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); - -// update versions.json with target version and minAppVersion from manifest.json -let versions = JSON.parse(readFileSync("versions.json", "utf8")); -versions[targetVersion] = minAppVersion; -writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); +import { readFileSync, writeFileSync } from "fs"; + +const targetVersion = process.env.npm_package_version; + +// read minAppVersion from manifest.json and bump version to target version +let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); +const { minAppVersion } = manifest; +manifest.version = targetVersion; +writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); + +// update versions.json with target version and minAppVersion from manifest.json +let versions = JSON.parse(readFileSync("versions.json", "utf8")); +versions[targetVersion] = minAppVersion; +writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); diff --git a/versions.json b/versions.json index 0f96315..26382a1 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ -{ - "1.0.0": "0.15.0" -} +{ + "1.0.0": "0.15.0" +}