From 1eb478eb00a3b5b2d0f5a2f3f99435f136779aca Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 20 Nov 2024 22:27:57 +0000 Subject: [PATCH 01/53] Update CITATION.cff --- CITATION.cff | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CITATION.cff b/CITATION.cff index 4296411bef..954ac967b6 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -14,7 +14,7 @@ keywords: - GUI - editor - pandoc -version: v3.3.0 +version: v3.3.1 doi: 10.5281/zenodo.2580173 date-released: 2024-11-20 license: gpl-3.0 From 415c52426f6586c6ea1fd11ac74afe27362e42f0 Mon Sep 17 00:00:00 2001 From: Hendrik Erz Date: Fri, 22 Nov 2024 14:24:20 +0100 Subject: [PATCH 02/53] refactor: File type detection system --- CHANGELOG.md | 5 +- .../commands/file-duplicate.ts | 9 +- .../service-providers/commands/file-new.ts | 2 +- .../service-providers/commands/file-rename.ts | 2 +- .../commands/importer/index.ts | 2 +- .../service-providers/commands/root-open.ts | 22 ++- source/app/service-providers/config/index.ts | 10 +- .../documents/util/can-open-file.ts | 4 +- .../service-providers/fsal/fsal-directory.ts | 28 ++-- .../fsal/util/valid-file-extensions.ts | 40 ------ .../app/service-providers/workspaces/index.ts | 6 +- source/app/util/extract-files-from-argv.ts | 4 +- .../plugins/md-paste-drop-handlers.ts | 2 +- .../markdown-editor/renderers/readability.ts | 2 +- .../markdown-editor/statusbar/magic-quotes.ts | 2 +- .../util/open-markdown-link.ts | 2 +- source/common/util/file-extention-checks.ts | 125 ++++++++++++++++++ source/common/util/ignore-file.ts | 30 ----- source/win-main/GlobalSearch.vue | 2 +- source/win-main/MainEditor.vue | 2 +- 20 files changed, 174 insertions(+), 127 deletions(-) delete mode 100644 source/app/service-providers/fsal/util/valid-file-extensions.ts create mode 100644 source/common/util/file-extention-checks.ts delete mode 100644 source/common/util/ignore-file.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cfae545dd..ad365dadde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,10 @@ ## Under the Hood -(nothing here) +- Refactored the file type detection system; it is now simpler and easier to use + and can detect a variety of additional groups of files (previously only + Markdown and code files; now also images, PDFs, MS and Open Office files as + well as data files) # 3.3.1 diff --git a/source/app/service-providers/commands/file-duplicate.ts b/source/app/service-providers/commands/file-duplicate.ts index f0277c3e1f..84a299110f 100644 --- a/source/app/service-providers/commands/file-duplicate.ts +++ b/source/app/service-providers/commands/file-duplicate.ts @@ -16,10 +16,7 @@ import ZettlrCommand from './zettlr-command' import { trans } from '@common/i18n-main' import path from 'path' import sanitize from 'sanitize-filename' -import { codeFileExtensions, mdFileExtensions } from '@providers/fsal/util/valid-file-extensions' - -const CODEFILE_TYPES = codeFileExtensions(true) -const ALLOWED_FILETYPES = mdFileExtensions(true) +import { hasCodeExt, hasMarkdownExt } from '@common/util/file-extention-checks' export default class FileDuplicate extends ZettlrCommand { constructor (app: any) { @@ -88,9 +85,9 @@ export default class FileDuplicate extends ZettlrCommand { // If no valid filename is provided, assume the original file's extension const newFileExtname = path.extname(filename).toLowerCase() - if (file.type === 'file' && !ALLOWED_FILETYPES.includes(newFileExtname)) { + if (file.type === 'file' && !hasMarkdownExt(filename)) { filename += file.ext // Assume the original file's extension - } else if (file.type === 'code' && !CODEFILE_TYPES.includes(newFileExtname)) { + } else if (file.type === 'code' && !hasCodeExt(filename)) { filename += file.ext // Assume the original file's extension } diff --git a/source/app/service-providers/commands/file-new.ts b/source/app/service-providers/commands/file-new.ts index dddfe15dc1..4633d125ae 100644 --- a/source/app/service-providers/commands/file-new.ts +++ b/source/app/service-providers/commands/file-new.ts @@ -17,8 +17,8 @@ import { trans } from '@common/i18n-main' import path from 'path' import sanitize from 'sanitize-filename' import generateFilename from '@common/util/generate-filename' -import { hasMdOrCodeExt } from '@providers/fsal/util/is-md-or-code-file' import { app } from 'electron' +import { hasMdOrCodeExt } from '@common/util/file-extention-checks' export default class FileNew extends ZettlrCommand { constructor (app: any) { diff --git a/source/app/service-providers/commands/file-rename.ts b/source/app/service-providers/commands/file-rename.ts index 11ca8ac91d..fcd44a8457 100644 --- a/source/app/service-providers/commands/file-rename.ts +++ b/source/app/service-providers/commands/file-rename.ts @@ -18,7 +18,7 @@ import sanitize from 'sanitize-filename' import { dialog } from 'electron' import { trans } from '@common/i18n-main' import replaceLinks from '@common/util/replace-links' -import { hasMdOrCodeExt } from '@providers/fsal/util/is-md-or-code-file' +import { hasMdOrCodeExt } from '@common/util/file-extention-checks' export default class FileRename extends ZettlrCommand { constructor (app: any) { diff --git a/source/app/service-providers/commands/importer/index.ts b/source/app/service-providers/commands/importer/index.ts index 144b868165..d15a50aee5 100644 --- a/source/app/service-providers/commands/importer/index.ts +++ b/source/app/service-providers/commands/importer/index.ts @@ -26,7 +26,7 @@ import { trans } from '@common/i18n-main' import type AssetsProvider from '@providers/assets' import { type PandocProfileMetadata } from '@providers/assets' import { SUPPORTED_READERS } from '@common/util/pandoc-maps' -import { hasMarkdownExt } from '@providers/fsal/util/is-md-or-code-file' +import { hasMarkdownExt } from '@common/util/file-extention-checks' export default async function makeImport ( fileList: string[], diff --git a/source/app/service-providers/commands/root-open.ts b/source/app/service-providers/commands/root-open.ts index 4bce8c61a5..4f97257fff 100644 --- a/source/app/service-providers/commands/root-open.ts +++ b/source/app/service-providers/commands/root-open.ts @@ -14,14 +14,11 @@ import { trans } from '@common/i18n-main' import ignoreDir from '@common/util/ignore-dir' -import ignoreFile from '@common/util/ignore-file' import { app } from 'electron' import path from 'path' import ZettlrCommand from './zettlr-command' -import { showNativeNotification } from '@common/util/show-notification' import { type DirDescriptor } from '@dts/common/fsal' -import isFile from '@common/util/is-file' -import isDir from '@common/util/is-dir' +import { CODE_EXT, MD_EXT } from '@common/util/file-extention-checks' export default class RootOpen extends ZettlrCommand { constructor (app: any) { @@ -58,8 +55,10 @@ export default class RootOpen extends ZettlrCommand { */ private async openFiles (): Promise { // The user wants to open another file or directory. - const extensions = [ 'markdown', 'md', 'txt', 'rmd' ] - const filter = [{ name: trans('Files'), extensions }] + const filter = [ + { name: trans('Markdown Files'), extensions: MD_EXT.map(x => x.slice(1)) }, + { name: trans('Code Files'), extensions: CODE_EXT.map(x => x.slice(1)) } + ] return await this._app.windows.askFile(filter, true) } @@ -72,18 +71,17 @@ export default class RootOpen extends ZettlrCommand { private async openWorkspaces (): Promise { // TODO: Move this to a command // The user wants to open another file or directory. - const ret = await this._app.windows.askDir(trans('Open project folder'), null) + const ret = await this._app.windows.askDir(trans('Open workspace'), null) for (const workspace of ret) { - const ignoredDir = await this._app.fsal.isDir(workspace) && ignoreDir(workspace) - const ignoredFile = await this._app.fsal.isFile(workspace) && ignoreFile(workspace) - if (ignoredDir || ignoredFile || workspace === app.getPath('home')) { + const isDir = await this._app.fsal.isDir(workspace) + if (!isDir || ignoreDir(workspace) || workspace === app.getPath('home')) { // We cannot add this dir, because it is in the list of ignored directories. this._app.log.error(`The chosen workspace "${workspace}" is on the ignore list.`) this._app.windows.prompt({ type: 'error', - title: trans('Cannot open directory'), - message: trans('Directory "%s" cannot be opened by Zettlr.', path.basename(workspace)) + title: trans('Cannot open workpace'), + message: trans('Cannot open workspace "%s".', path.basename(workspace)) }) } } diff --git a/source/app/service-providers/config/index.ts b/source/app/service-providers/config/index.ts index 4aa183860b..80bf120bdc 100644 --- a/source/app/service-providers/config/index.ts +++ b/source/app/service-providers/config/index.ts @@ -18,7 +18,6 @@ import EventEmitter from 'events' import { ValidationRule, VALIDATE_RULES, VALIDATE_PROPERTIES } from './config-validation' import PersistentDataContainer from '@common/modules/persistent-data-container' import { app, dialog, ipcMain } from 'electron' -import ignoreFile from '@common/util/ignore-file' import safeAssign from '@common/util/safe-assign' import isDir from '@common/util/is-dir' import broadcastIpcMessage from '@common/util/broadcast-ipc-message' @@ -27,6 +26,9 @@ import enumDictFiles from '@common/util/enum-dict-files' import ProviderContract from '../provider-contract' import type LogProvider from '../log' import { loadData, trans } from '@common/i18n-main' +import isFile from '@common/util/is-file' +import { hasMdOrCodeExt } from '@common/util/file-extention-checks' +import ignoreDir from '@common/util/ignore-dir' const ZETTLR_VERSION = app.getVersion() @@ -336,7 +338,11 @@ export default class ConfigProvider extends ProviderContract { */ addPath (p: string): boolean { // Only add valid and unique paths - if ((!ignoreFile(p) || isDir(p)) && !this.config.openPaths.includes(p)) { + if (this.config.openPaths.includes(p)) { + return false + } + + if ((isFile(p) && hasMdOrCodeExt(p)) || (isDir(p) && !ignoreDir(p))) { this.config.openPaths.push(p) this.consolidateRootPaths() this.sortPaths() diff --git a/source/app/service-providers/documents/util/can-open-file.ts b/source/app/service-providers/documents/util/can-open-file.ts index 7b5b929c2e..a65652244b 100644 --- a/source/app/service-providers/documents/util/can-open-file.ts +++ b/source/app/service-providers/documents/util/can-open-file.ts @@ -1,4 +1,4 @@ -import { isMdOrCodeFile } from '@providers/fsal/util/is-md-or-code-file' +import { hasMdOrCodeExt } from '@common/util/file-extention-checks' import { promises as fs, constants as FSConstants } from 'fs' /** @@ -23,7 +23,7 @@ export async function canOpenFile (filePath: string): Promise { } // Then check if it's actually a file we can handle - if (!isMdOrCodeFile(filePath)) { + if (!hasMdOrCodeExt(filePath)) { return false } diff --git a/source/app/service-providers/fsal/fsal-directory.ts b/source/app/service-providers/fsal/fsal-directory.ts index 197d0aea29..f8e87affa2 100644 --- a/source/app/service-providers/fsal/fsal-directory.ts +++ b/source/app/service-providers/fsal/fsal-directory.ts @@ -25,14 +25,10 @@ import * as FSALCodeFile from './fsal-code-file' import * as FSALAttachment from './fsal-attachment' import type { DirDescriptor, AnyDescriptor, MDFileDescriptor, SortMethod, ProjectSettings } from '@dts/common/fsal' import type FSALCache from './fsal-cache' -import { - codeFileExtensions, - mdFileExtensions -} from '@providers/fsal/util/valid-file-extensions' -import { hasCodeExt, hasMarkdownExt, isMdOrCodeFile } from './util/is-md-or-code-file' import { safeDelete } from './util/safe-delete' import { getFilesystemMetadata } from './util/get-fs-metadata' import type LogProvider from '@providers/log' +import { hasCodeExt, hasMarkdownExt } from '@common/util/file-extention-checks' /** * Determines what will be written to file (.ztr-directory) @@ -43,9 +39,6 @@ const SETTINGS_TEMPLATE = { icon: null as null|string // Default: no icon } -const ALLOWED_CODE_FILES = codeFileExtensions(true) -const MARKDOWN_FILES = mdFileExtensions(true) - /** * Used to insert a default project */ @@ -212,15 +205,12 @@ export async function parse ( if (isDir(absolutePath) && !ignoreDir(absolutePath)) { const cDir = await parse(absolutePath, cache, parser, sorter, false) dir.children.push(cDir) - } else if (isMdOrCodeFile(absolutePath)) { - const isCode = ALLOWED_CODE_FILES.includes(path.extname(absolutePath).toLowerCase()) - if (isCode) { - const file = await FSALCodeFile.parse(absolutePath, cache, false) - dir.children.push(file) - } else { - const file = await FSALFile.parse(absolutePath, cache, parser, false) - dir.children.push(file) - } + } else if (hasMarkdownExt(absolutePath)) { + const file = await FSALFile.parse(absolutePath, cache, parser, false) + dir.children.push(file) + } else if (hasCodeExt(absolutePath)) { + const file = await FSALCodeFile.parse(absolutePath, cache, false) + dir.children.push(file) } else if (isFile(absolutePath)) { dir.children.push(await FSALAttachment.parse(absolutePath)) } // Else: Probably a symlink TODO @@ -520,9 +510,9 @@ export async function addChild ( ): Promise { if (isDir(childPath)) { dirObject.children.push(await parse(childPath, cache, parser, sorter, false)) - } else if (ALLOWED_CODE_FILES.includes(path.extname(childPath))) { + } else if (hasCodeExt(childPath)) { dirObject.children.push(await FSALCodeFile.parse(childPath, cache, false)) - } else if (MARKDOWN_FILES.includes(path.extname(childPath))) { + } else if (hasMarkdownExt(childPath)) { dirObject.children.push(await FSALFile.parse(childPath, cache, parser, false)) } sortChildren(dirObject, sorter) diff --git a/source/app/service-providers/fsal/util/valid-file-extensions.ts b/source/app/service-providers/fsal/util/valid-file-extensions.ts deleted file mode 100644 index 669b6c19db..0000000000 --- a/source/app/service-providers/fsal/util/valid-file-extensions.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @ignore - * BEGIN HEADER - * - * Contains: File extension functions - * CVM-Role: - * Maintainer: Hendrik Erz - * License: GNU GPL v3 - * - * Description: Two small utility functions to centralise which file - * extensions are allowed both for Markdown and code files. - * - * END HEADER - */ - -/** - * Returns an array with all recognised Markdown file extensions (lowercase) - * - * @param {boolean} withDot If true, returns the extensions with dot. - * - * @return {string[]} The list of valid file extensions - */ -export function mdFileExtensions (withDot = false): string[] { - const ext = [ 'md', 'rmd', 'qmd', 'markdown', 'txt', 'mdx', 'mkd' ] - - return (withDot) ? ext.map(e => '.' + e) : ext -} - -/** - * Returns an array with all recognised Code file extensions (lowercase) - * - * @param {boolean} withDot If true, returns the extensions with dot. - * - * @return {string[]} The list of valid file extensions - */ -export function codeFileExtensions (withDot = false): string[] { - const ext = [ 'tex', 'json', 'yaml', 'yml' ] - - return (withDot) ? ext.map(e => '.' + e) : ext -} diff --git a/source/app/service-providers/workspaces/index.ts b/source/app/service-providers/workspaces/index.ts index 44d9a75409..9aa0ff3487 100644 --- a/source/app/service-providers/workspaces/index.ts +++ b/source/app/service-providers/workspaces/index.ts @@ -27,8 +27,6 @@ import EventEmitter from 'events' import { getIDRE } from '@common/regular-expressions' import findObject from '@common/util/find-object' import type { AnyDescriptor, CodeFileDescriptor, DirDescriptor, MDFileDescriptor, OtherFileDescriptor } from '@dts/common/fsal' -import { hasMarkdownExt } from '@providers/fsal/util/is-md-or-code-file' -import { mdFileExtensions } from '@providers/fsal/util/valid-file-extensions' import locateByPath from '@providers/fsal/util/locate-by-path' import { showSplashScreen, closeSplashScreen, updateSplashScreen } from '@providers/workspaces/splash-screen' import { trans } from '@common/i18n-main' @@ -36,6 +34,7 @@ import path from 'path' import { performance } from 'perf_hooks' import objectToArray from '@common/util/object-to-array' import generateStats, { type WorkspacesStatistics } from './generate-stats' +import { hasMarkdownExt, MD_EXT } from '@common/util/file-extention-checks' export enum WORKSPACE_PROVIDER_EVENTS { WorkspaceAdded = 'workspace-added', @@ -348,7 +347,6 @@ export default class WorkspaceProvider extends ProviderContract { public findExact (query: string): MDFileDescriptor|undefined { const idREPattern = this._config.get().zkn.idRE const idRE = getIDRE(idREPattern, true) - const extensions = mdFileExtensions(true) const allWorkspaces = this.roots.map(root => root.rootDescriptor) // First, let's see if what we got looks like an ID, or not. If it looks @@ -364,7 +362,7 @@ export default class WorkspaceProvider extends ProviderContract { // No file ending given, so let's test all allowed. The filetypes are // sorted by probability (first .md, then .markdown), to reduce the // amount of time spent on the tree. - for (const type of extensions) { + for (const type of MD_EXT) { const file = findObject(allWorkspaces, 'name', query + type, 'children') if (file !== undefined) { return file diff --git a/source/app/util/extract-files-from-argv.ts b/source/app/util/extract-files-from-argv.ts index 690f25e352..e97a8291dd 100644 --- a/source/app/util/extract-files-from-argv.ts +++ b/source/app/util/extract-files-from-argv.ts @@ -13,7 +13,7 @@ */ // Helpers to determine what files from argv we can open -import { isMdOrCodeFile } from '@providers/fsal/util/is-md-or-code-file' +import { hasMdOrCodeExt } from '@common/util/file-extention-checks' /** * Extracts files from argv. @@ -29,6 +29,6 @@ export default function extractFilesFromArgv (argv = process.argv): string[] { return argv.filter((arg) => { // Filter out CLI arguments, non-files, and non-supported files - return !arg.startsWith('--') && isMdOrCodeFile(arg) + return !arg.startsWith('--') && hasMdOrCodeExt(arg) }) } diff --git a/source/common/modules/markdown-editor/plugins/md-paste-drop-handlers.ts b/source/common/modules/markdown-editor/plugins/md-paste-drop-handlers.ts index 225e0c5bb1..30c1b33bdd 100644 --- a/source/common/modules/markdown-editor/plugins/md-paste-drop-handlers.ts +++ b/source/common/modules/markdown-editor/plugins/md-paste-drop-handlers.ts @@ -19,7 +19,7 @@ import html2md from '@common/util/html-to-md' import { configField } from '../util/configuration' import { pathBasename, pathDirname, pathExtname, relativePath } from '@common/util/renderer-path-polyfill' import { type SaveImageFromClipboardAPI } from 'source/app/service-providers/commands/save-image-from-clipboard' -import { hasMdOrCodeExt } from 'source/app/service-providers/fsal/util/is-md-or-code-file' +import { hasMdOrCodeExt } from '@common/util/file-extention-checks' const ipcRenderer = window.ipc diff --git a/source/common/modules/markdown-editor/renderers/readability.ts b/source/common/modules/markdown-editor/renderers/readability.ts index 1ed0039abc..1ca16794b5 100644 --- a/source/common/modules/markdown-editor/renderers/readability.ts +++ b/source/common/modules/markdown-editor/renderers/readability.ts @@ -15,7 +15,7 @@ import { type EditorState } from '@codemirror/state' import { type EditorView } from '@codemirror/view' import { trans } from '@common/i18n-renderer' -import { hasMarkdownExt } from '@providers/fsal/util/is-md-or-code-file' +import { hasMarkdownExt } from '@common/util/file-extention-checks' import { type StatusbarItem } from '../statusbar' import { configField, configUpdateEffect } from '../util/configuration' diff --git a/source/common/modules/markdown-editor/statusbar/magic-quotes.ts b/source/common/modules/markdown-editor/statusbar/magic-quotes.ts index 4917f96a21..2dc0f6695f 100644 --- a/source/common/modules/markdown-editor/statusbar/magic-quotes.ts +++ b/source/common/modules/markdown-editor/statusbar/magic-quotes.ts @@ -18,7 +18,7 @@ import { trans } from '@common/i18n-renderer' import showPopupMenu from '@common/modules/window-register/application-menu-helper' import { resolveLangCode } from '@common/util/map-lang-code' import { type AnyMenuItem } from '@dts/renderer/context' -import { hasMarkdownExt } from '@providers/fsal/util/is-md-or-code-file' +import { hasMarkdownExt } from '@common/util/file-extention-checks' import { type StatusbarItem } from '.' import { configField } from '../util/configuration' diff --git a/source/common/modules/markdown-editor/util/open-markdown-link.ts b/source/common/modules/markdown-editor/util/open-markdown-link.ts index 446a7d3ce8..661b9ca390 100644 --- a/source/common/modules/markdown-editor/util/open-markdown-link.ts +++ b/source/common/modules/markdown-editor/util/open-markdown-link.ts @@ -18,7 +18,7 @@ import { type EditorState, type Line } from '@codemirror/state' import { configField } from './configuration' import { EditorView } from '@codemirror/view' import { tocField } from '../plugins/toc-field' -import { hasMdOrCodeExt } from '@providers/fsal/util/is-md-or-code-file' +import { hasMdOrCodeExt } from '@common/util/file-extention-checks' import { isAbsolutePath, pathDirname } from '@common/util/renderer-path-polyfill' const ipcRenderer = window.ipc diff --git a/source/common/util/file-extention-checks.ts b/source/common/util/file-extention-checks.ts new file mode 100644 index 0000000000..2d75b0f3de --- /dev/null +++ b/source/common/util/file-extention-checks.ts @@ -0,0 +1,125 @@ +/** + * @ignore + * BEGIN HEADER + * + * Contains: File extension check functions + * CVM-Role: Utility functions + * Maintainer: Hendrik Erz + * License: GNU GPL v3 + * + * Description: This file provides the backbone for Zettlr's file detection. + * It includes various functions to determine if paths or + * filenames have proper file extensions as they can be handled + * by Zettlr. + * + * END HEADER + */ + +export const MD_EXT = [ '.md', '.rmd', '.qmd', '.markdown', '.txt', '.mdx', '.mkd' ] +export const CODE_EXT = [ '.tex', '.json', '.yaml', '.yml' ] +export const IMG_EXT = [ '.jpg', '.jpeg', '.png', '.svg', '.webp', '.bmp', '.tiff' ] +export const PDF_EXT = ['.pdf'] +export const MS_OFFICE_EXT = [ '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx' ] +export const OPEN_OFFICE_EXT = [ '.odt', '.ods', '.odp' ] +export const DATA_EXT = [ '.csv', '.tsv', '.sav', '.zsav' ] + +/** + * Returns true if the given path has a valid Markdown or Code extension + * + * @param {string} p The path to check + * + * @return {boolean} True or false + */ +export function hasMdOrCodeExt (p: string): boolean { + return hasMarkdownExt(p) || hasCodeExt(p) +} + +/** + * Checks if the provided `filePath` has a filename extension from the list of + * `extensions`. + * + * @param {string} filePath The file path in question + * @param {string[]} extensions The list of filename extensions to check + * + * @return {boolean} Whether the file has one of these extensions + */ +function hasExt (filePath: string, extensions: string[]): boolean { + return extensions.some(ext => filePath.endsWith(ext)) +} + +/** + * Has the given path a valid Markdown file extension? + * + * @param {string} filePath The path to check + * + * @return {boolean} True or false + */ +export function hasMarkdownExt (filePath: string): boolean { + return hasExt(filePath, MD_EXT) +} + +/** + * Has the given path a valid Code file extension? + * + * @param {string} filePath The path to check + * + * @return {boolean} True or false + */ +export function hasCodeExt (filePath: string): boolean { + return hasExt(filePath, CODE_EXT) +} + +/** + * Has the given path a valid Image file extension? + * + * @param {string} filePath The path to check + * + * @return {boolean} True or false + */ +export function hasImageExt (filePath: string): boolean { + return hasExt(filePath, IMG_EXT) +} + +/** + * Has the given path a valid PDF file extension? + * + * @param {string} filePath The path to check + * + * @return {boolean} True or false + */ +export function hasPDFExt (filePath: string): boolean { + return hasExt(filePath, PDF_EXT) +} + +/** + * Has the given path a valid MS Office file extension? + * + * @param {string} filePath The path to check + * + * @return {boolean} True or false + */ +export function hasMSOfficeExt (filePath: string): boolean { + return hasExt(filePath, MS_OFFICE_EXT) +} + +/** + * Has the given path a valid Open Office file extension? + * + * @param {string} filePath The path to check + * + * @return {boolean} True or false + */ +export function hasOpenOfficeExt (filePath: string): boolean { + return hasExt(filePath, OPEN_OFFICE_EXT) +} + +/** + * Has the given path a valid Data file extension? + * + * @param {string} filePath The path to check + * + * @return {boolean} True or false + */ +export function hasDataExt (filePath: string): boolean { + return hasExt(filePath, DATA_EXT) +} diff --git a/source/common/util/ignore-file.ts b/source/common/util/ignore-file.ts deleted file mode 100644 index eb045f484b..0000000000 --- a/source/common/util/ignore-file.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * BEGIN HEADER - * - * Contains: Utility function - * CVM-Role: - * Maintainer: Hendrik Erz - * License: GNU GPL v3 - * - * Description: This file contains a utility function to check for ignored files. - * - * END HEADER - */ - -import path from 'path' -import { mdFileExtensions, codeFileExtensions } from '../../app/service-providers/fsal/util/valid-file-extensions' - -const MD_FILES = mdFileExtensions(true) -const CODE_FILES = codeFileExtensions(true) - -/** - * Returns true, if a given file should be ignored. - * - * @param {string} p The path to the file. - * - * @return {boolean} True or false, depending on whether the file should be ignored. - */ -export default function ignoreFile (p: string): boolean { - let ext = path.extname(p).toLowerCase() - return (!MD_FILES.includes(ext) && !CODE_FILES.includes(ext)) -} diff --git a/source/win-main/GlobalSearch.vue b/source/win-main/GlobalSearch.vue index 19c9b2e646..d00d5e592c 100644 --- a/source/win-main/GlobalSearch.vue +++ b/source/win-main/GlobalSearch.vue @@ -145,7 +145,7 @@ import { ref, computed, watch, onMounted } from 'vue' import type { FileSearchDescriptor, SearchResult, SearchResultWrapper } from '@dts/common/search' import showPopupMenu from '@common/modules/window-register/application-menu-helper' import { type AnyMenuItem } from '@dts/renderer/context' -import { hasMdOrCodeExt } from '@providers/fsal/util/is-md-or-code-file' +import { hasMdOrCodeExt } from '@common/util/file-extention-checks' import { useConfigStore, useWindowStateStore, useWorkspacesStore } from 'source/pinia' import type { MaybeRootDescriptor } from 'source/types/common/fsal' diff --git a/source/win-main/MainEditor.vue b/source/win-main/MainEditor.vue index 7ee06f4c26..5e919e6191 100644 --- a/source/win-main/MainEditor.vue +++ b/source/win-main/MainEditor.vue @@ -36,7 +36,7 @@ import objectToArray from '@common/util/object-to-array' import { ref, computed, onMounted, onBeforeUnmount, watch, toRef, onUpdated } from 'vue' import { type EditorCommands } from './App.vue' -import { hasMarkdownExt } from '@providers/fsal/util/is-md-or-code-file' +import { hasMarkdownExt } from '@common/util/file-extention-checks' import { DP_EVENTS, type OpenDocument } from '@dts/common/documents' import { CITEPROC_MAIN_DB } from '@dts/common/citeproc' import { type EditorConfigOptions } from '@common/modules/markdown-editor/util/configuration' From cb8d3d374f50e4d8ee2a9ef30374096508c755c4 Mon Sep 17 00:00:00 2001 From: Hendrik Erz Date: Fri, 22 Nov 2024 14:25:29 +0100 Subject: [PATCH 03/53] chore: Delete dead code --- .../fsal/util/is-md-or-code-file.ts | 69 ------------------- 1 file changed, 69 deletions(-) delete mode 100644 source/app/service-providers/fsal/util/is-md-or-code-file.ts diff --git a/source/app/service-providers/fsal/util/is-md-or-code-file.ts b/source/app/service-providers/fsal/util/is-md-or-code-file.ts deleted file mode 100644 index 7081424c82..0000000000 --- a/source/app/service-providers/fsal/util/is-md-or-code-file.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * BEGIN HEADER - * - * Contains: Utility function - * CVM-Role: - * Maintainer: Hendrik Erz - * License: GNU GPL v3 - * - * Description: This function checks if some file exists and is a Markdown file - * - * END HEADER - */ - -import { lstatSync } from 'fs' -import { mdFileExtensions, codeFileExtensions } from './valid-file-extensions' - -const MD_FILES = mdFileExtensions(true) -const CODE_FILES = codeFileExtensions(true) - -/** - * Returns true if the file is either a Markdown or a recognized code file - * - * @param {string} p The path to the file. - * - * @return {boolean} True or false. - */ -export function isMdOrCodeFile (p: string): boolean { - try { - const stat = lstatSync(p) - return stat.isFile() && hasMdOrCodeExt(p) - } catch (err) { - return false - } -} - -/** - * Returns true if the given path has a valid Markdown or Code extension - * - * @param {string} p The path to check - * - * @return {boolean} True or false - */ -export function hasMdOrCodeExt (p: string): boolean { - return hasMarkdownExt(p) || hasCodeExt(p) -} - -/** - * Has the given path a valid Markdown file extension? - * - * @param {string} p The path to check - * - * @return {boolean} True or false - */ -export function hasMarkdownExt (p: string): boolean { - const ext = p.substring(p.lastIndexOf('.')).toLowerCase() - return MD_FILES.includes(ext) -} - -/** - * Has the given path a valid Code file extension? - * - * @param {string} p The path to check - * - * @return {boolean} True or false - */ -export function hasCodeExt (p: string): boolean { - const ext = p.substring(p.lastIndexOf('.')).toLowerCase() - return CODE_FILES.includes(ext) -} From 900f58455dffc6c10b2f647cc9cc5620c870f692 Mon Sep 17 00:00:00 2001 From: Hendrik Erz Date: Mon, 25 Nov 2024 20:48:16 +0100 Subject: [PATCH 04/53] fix: SVG Image preview Fixes #5496 --- CHANGELOG.md | 2 +- source/app/util/custom-protocols.ts | 41 ++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad365dadde..39520c7f99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## GUI and Functionality -(nothing here) +- Fix SVG image preview (#5496) ## Under the Hood diff --git a/source/app/util/custom-protocols.ts b/source/app/util/custom-protocols.ts index 3fc1b93196..22fdffba13 100644 --- a/source/app/util/custom-protocols.ts +++ b/source/app/util/custom-protocols.ts @@ -14,6 +14,39 @@ import type LogProvider from '@providers/log' import { protocol } from 'electron' import { promises as fs } from 'fs' +import path from 'path' + +// Reference if we need more: https://www.iana.org/assignments/media-types/media-types.xhtml +const CONTENT_TYPE_MAP = new Map([ + [ '.svg', 'image/svg+xml' ], + [ '.jpg', 'image/jpeg' ], + [ '.jpeg', 'image/jpeg' ], + [ '.gif', 'image/gif' ], + [ '.tiff', 'image/tiff' ], + [ '.tif', 'image/tiff' ], + [ '.ico', 'image/vnd.microsoft.icon' ], + [ '.png', 'image/png' ], + [ '.webp', 'image/webp' ], + [ '.mp4', 'video/mp4' ], + [ '.webm', 'video/webm' ], + [ '.pdf', 'application/pdf' ] +]) + +/** + * Returns file-type-specific content type headers, suitable for use in Response + * + * @param {string} filePath The file's path + * + * @return {Record} A record of headers + */ +function headersForFileType (filePath: string): Record { + const headers: Record = {} + const type = CONTENT_TYPE_MAP.get(path.extname(filePath)) + if (type !== undefined) { + headers['Content-type'] = type + } + return headers +} export default function registerCustomProtocols (logger: LogProvider): void { // Make it possible to safely load external files @@ -38,7 +71,13 @@ export default function registerCustomProtocols (logger: LogProvider): void { return new Response(fileBuffer, { status: 200, // Prevent that local files are cached - headers: { 'Cache-control': 'no-store', pragma: 'no-cache' } + headers: { + 'Cache-control': 'no-store', + pragma: 'no-cache', + // Headers are important, since otherwise, e.g., SVG images or PDFs + // aren't properly rendered (see #5496) + ...headersForFileType(pathName) + } }) } catch (err: any) { const msg = `Error loading external file: ${err.message as string}` From 5791178987883e80d398e5ead5477ec0ae9dcb85 Mon Sep 17 00:00:00 2001 From: Hendrik Erz Date: Mon, 25 Nov 2024 21:43:54 +0100 Subject: [PATCH 05/53] fix: Toggling task returns focus Fixes #5246 --- CHANGELOG.md | 2 ++ .../common/modules/markdown-editor/renderers/render-tasks.ts | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39520c7f99..7aefd8231a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and can detect a variety of additional groups of files (previously only Markdown and code files; now also images, PDFs, MS and Open Office files as well as data files) +- Checking task-list checkboxes now returns the focus back to the editor + immediately (#5246) # 3.3.1 diff --git a/source/common/modules/markdown-editor/renderers/render-tasks.ts b/source/common/modules/markdown-editor/renderers/render-tasks.ts index 12154cc99b..55669af2c6 100644 --- a/source/common/modules/markdown-editor/renderers/render-tasks.ts +++ b/source/common/modules/markdown-editor/renderers/render-tasks.ts @@ -33,10 +33,9 @@ class TaskWidget extends WidgetType { elem.setAttribute('type', 'checkbox') elem.checked = this.isChecked elem.addEventListener('click', (event) => { - event.preventDefault() - event.stopPropagation() const insert = this.isChecked ? '[ ]' : '[x]' view.dispatch({ changes: [{ from: this.node.from, to: this.node.to, insert }] }) + view.contentDOM.focus() }) return elem } From 4e3545fc5df67e67bc9a78d57ca53b9f647a8fd4 Mon Sep 17 00:00:00 2001 From: Hendrik Erz Date: Mon, 25 Nov 2024 21:45:28 +0100 Subject: [PATCH 06/53] fix: Adapt FSAL to new filetype detection --- source/app/service-providers/fsal/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/service-providers/fsal/index.ts b/source/app/service-providers/fsal/index.ts index 7203e2f963..ed865f8e00 100644 --- a/source/app/service-providers/fsal/index.ts +++ b/source/app/service-providers/fsal/index.ts @@ -34,7 +34,7 @@ import type { SearchTerm } from '@dts/common/search' import ProviderContract from '@providers/provider-contract' import { app } from 'electron' import type LogProvider from '@providers/log' -import { hasCodeExt, hasMarkdownExt } from './util/is-md-or-code-file' +import { hasMarkdownExt, hasCodeExt } from '@common/util/file-extention-checks' import getMarkdownFileParser from './util/file-parser' import type ConfigProvider from '@providers/config' import { promises as fs, constants as FS_CONSTANTS } from 'fs' From 8518705755451318186fb6dd9c8e589b484d998f Mon Sep 17 00:00:00 2001 From: Hendrik Erz Date: Mon, 25 Nov 2024 21:46:34 +0100 Subject: [PATCH 07/53] fix: Selects can be disabled now --- CHANGELOG.md | 5 +++-- source/common/vue/form/elements/SelectControl.vue | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aefd8231a..7bf7d0737e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## GUI and Functionality - Fix SVG image preview (#5496) +- Checking task-list checkboxes now returns the focus back to the editor + immediately (#5246) ## Under the Hood @@ -10,8 +12,7 @@ and can detect a variety of additional groups of files (previously only Markdown and code files; now also images, PDFs, MS and Open Office files as well as data files) -- Checking task-list checkboxes now returns the focus back to the editor - immediately (#5246) +- Select controls can be disabled now # 3.3.1 diff --git a/source/common/vue/form/elements/SelectControl.vue b/source/common/vue/form/elements/SelectControl.vue index b9e35901fc..d0218b2ca6 100644 --- a/source/common/vue/form/elements/SelectControl.vue +++ b/source/common/vue/form/elements/SelectControl.vue @@ -4,6 +4,7 @@