enhanced_copy
key./
)!",
6 | "frontmatter": "Properties",
7 | "is": "",
8 | "not": "NOT EQUAL TO",
9 | "or": "OR",
10 | "path": "Path",
11 | "placeholder": "Value to match",
12 | "tag": "Tags",
13 | "title": "Automatic use"
14 | },
15 | "callout": {
16 | "desc": "Format of the callout type. All format will be in blockquote.",
17 | "obsidian": "Use Obsidian's default callout format",
18 | "remove": "Remove the type & title",
19 | "removeKeepTitle": "Keeping the title but removing the type.",
20 | "strong": "Convert the type to a simple strong text, keep the title",
21 | "title": "Callout type"
22 | },
23 | "commands": {
24 | "all": "Copy selected text",
25 | "brute": "Native copy",
26 | "editor": "Copy selected text from edit view",
27 | "other": "Copy selected text from opened view",
28 | "reading": "Copy selected text from reading view"
29 | },
30 | "common": {
31 | "add": "Add",
32 | "cancel": "Cancel",
33 | "delete": "Delete",
34 | "from": "From",
35 | "pattern": "pattern",
36 | "profile": "Profile name",
37 | "replacement": "replacement",
38 | "save": "Save",
39 | "to": "To"
40 | },
41 | "convertDataview": {
42 | "block": "Code block",
43 | "djs": {
44 | "block": {
45 | },
46 | "inline": {
47 | },
48 | "title": "Dataview JS"
49 | },
50 | "dql": {
51 | "block": {
52 | },
53 | "inline": {
54 | },
55 | "title": "Dataview Query Language"
56 | },
57 | "global": {
58 | "desc": "Allows you to convert dataview queries (inline DQL, Djs...) to markdown. \nDoes not always work, use with moderation.",
59 | "title": "Convert Dataview queries to markdown"
60 | },
61 | "inline": "Inline"
62 | },
63 | "copyAsHTML": "Copy as HTML instead of Markdown. Some options may not be available.",
64 | "copyLinksAsText": {
65 | "desc": "Adjust the way links are formatted when copying to the clipboard.",
66 | "external": "Remove markup for internal links only",
67 | "keep": "Keep markup",
68 | "remove": "Remove markup for all links",
69 | "title": "Links format"
70 | },
71 | "debug": {
72 | "desc": "Show debug log in console",
73 | "title": "Debug information"
74 | },
75 | "delete": {
76 | },
77 | "edit": {
78 | "desc": "Edit view settings",
79 | "title": "Edit"
80 | },
81 | "global": {
82 | "copy": "Copy settings between profile",
83 | "title": "General parameters"
84 | },
85 | "hardBreaks": {
86 | "desc": "Add a standard markdown line break (two spaces at the end of the line) at the end of each line.",
87 | "title": "Hard breaks"
88 | },
89 | "highlight": {
90 | "desc": "Remove the highlight markup (==).",
91 | "title": "Highlight"
92 | },
93 | "hotkey": {
94 | "desc": "Create a separate command for each mode. This allows you to assign a different hotkey for each mode. ⚠️️ You need to reload the plugin to apply the changes.",
95 | "title": "Separate commands"
96 | },
97 | "links": "Links",
98 | "log": {
99 | "callout": {
100 | "remove": "Remove callout title",
101 | "title": "Convert callout"
102 | },
103 | "editMode": "Edit mode",
104 | "empty": "selectedText is empty // Fallback on activeWindow.getSelection().toString()",
105 | "noHardBreaks": "No hard breaks - Remove extra spaces at the end of each line",
106 | "readingMode": "Reading mode"
107 | },
108 | "modal": {
109 | "copyView": {
110 | "copy": "Copy",
111 | "editing": "Editing",
112 | "reading": "Reading"
113 | },
114 | "replaceText": {
115 | "down": "Move down",
116 | "title": "Replace Text",
117 | "up": "Move up"
118 | }
119 | },
120 | "openTextReplacer": "Open Text replacer",
121 | "other": "Other options",
122 | "overrideCopy": {
123 | "desc": "Replace the native copy (CTRL C, right-click...) with the enhanced copy (need a reload of Obsidian)",
124 | "title": "Override native copy"
125 | },
126 | "profile": {
127 | "add": {
128 | "desc": "Add a copy profile. \nAn Obsidian reload is required to use it as a command.",
129 | "title": "Add profile"
130 | },
131 | "delete": "Delete profile"
132 | },
133 | "reading": {
134 | "desc": "Reading view settings",
135 | "title": "Reading"
136 | },
137 | "removeFootnotesLinks": {
138 | "desc": "Adjust the way footnotes are formatted when copying them to the clipboard.",
139 | "format": "Remove all links but keep contents as [^1]",
140 | "keep": "Keep all links markup and contents",
141 | "remove": "Remove all links markup and contents",
142 | "title": "Footnotes format"
143 | },
144 | "spaceSize": {
145 | "desc": "Number of spaces to be used for indentation. Use -1 to disable the conversion.",
146 | "title": "Space size"
147 | },
148 | "tabSpaceSize": "Number of spaces to be used for indentation",
149 | "tabToSpace": "Convert tabs to spaces",
150 | "unconventionalMarkdown": {
151 | "desc": "Removed unconventional Markdown markup because they are not supported by all Markdown interpreters.",
152 | "title": "Unconventional Markdown"
153 | },
154 | "view": {
155 | "all": "All view",
156 | "desc": "Choose on which view the conversion should be applied.",
157 | "edit": "Edit view",
158 | "reading": "Reading view only",
159 | "title": "Apply settings"
160 | },
161 | "wikiToMarkdown": {
162 | "desc": "Mandatory to edit the links when copying them to the clipboard. If disabled, the settings for links format will be ignored.",
163 | "title": "Convert wiki links to Markdown link"
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/i18n/locales/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "auto": {
3 | "EQUAL": "ÉGAL À",
4 | "and": "ET",
5 | "desc": "Permet d'utiliser automatiquement ce profil en se basant sur le chemin, un tag ou les propriétés.enhanced_copy
./
) !",
6 | "frontmatter": "Propriétés",
7 | "is": "Activer uniquement SI VALIDE la condition",
8 | "not": "DIFFÉRENT DE",
9 | "or": "OU",
10 | "path": "Chemin",
11 | "placeholder": "Valeur à trouver",
12 | "tag": "Tags",
13 | "title": "Utilisation automatique"
14 | },
15 | "callout": {
16 | "desc": "Format du type de callout. Tous les formats seront sous formes de citations.",
17 | "obsidian": "Utiliser le format de callout d'Obsidian",
18 | "remove": "Suppression du type et titre",
19 | "removeKeepTitle": "Conservation du titre mais suppression du type",
20 | "strong": "Convertir le type en gras",
21 | "title": "Type de callout"
22 | },
23 | "commands": {
24 | "all": "Copier le texte sélectionné",
25 | "brute": "Copie native",
26 | "editor": "Copier le texte sélectionné depuis l'éditeur",
27 | "other": "Copier le texte sélectionné depuis la vue non markdown",
28 | "reading": "Copier le texte sélectionné depuis la vue de lecture"
29 | },
30 | "common": {
31 | "add": "Ajouter",
32 | "cancel": "Annuler",
33 | "delete": "Supprimer",
34 | "from": "Depuis",
35 | "pattern": "pattern",
36 | "profile": "Nom du profil",
37 | "replacement": "remplacement",
38 | "save": "Sauvegarder",
39 | "to": "Vers"
40 | },
41 | "convertDataview": {
42 | "block": "Bloc de code",
43 | "djs": {
44 | "block": {
45 | },
46 | "inline": {
47 | },
48 | "title": "Dataview JS"
49 | },
50 | "dql": {
51 | "block": {
52 | },
53 | "inline": {
54 | },
55 | "title": "Dataview Query Language"
56 | },
57 | "global": {
58 | "desc": "Permet de convertir les queries dataview (inline DQL, Djs...) en markdown. Ne fonctionne pas toujours, à utiliser avec modération.",
59 | "title": "Convertir les queries Dataview en markdown"
60 | },
61 | "inline": "Inline"
62 | },
63 | "copyAsHTML": "Copier en HTML au lieu de Markdown. Certaines options peuvent ne pas être disponibles.",
64 | "copyLinksAsText": {
65 | "desc": "Modifier l'aspect des liens lorsqu'ils sont copiés dans le presse-papiers.",
66 | "external": "Supprimer les balises pour les liens internes seulement",
67 | "keep": "Conserver toutes les balises",
68 | "remove": "Supprimer les balises pour tous les liens",
69 | "title": "Format des liens"
70 | },
71 | "debug": {
72 | "desc": "Affiche le journal de débogage dans la console",
73 | "title": "Informations de débogage"
74 | },
75 | "delete": {
76 | },
77 | "edit": {
78 | "desc": "Paramètre pour la vue d'édition",
79 | "title": "Édition"
80 | },
81 | "global": {
82 | "copy": "Copier les paramètres entre les profiles",
83 | "title": "Paramètres généraux"
84 | },
85 | "hardBreaks": {
86 | "desc": "Ajouter un saut de ligne strict (deux espaces en fin de ligne) à la fin de chaque ligne.",
87 | "title": "Saut de ligne strict"
88 | },
89 | "highlight": {
90 | "desc": "Suppression de la balise de surlignage (==)",
91 | "title": "Surlignage"
92 | },
93 | "hotkey": {
94 | "desc": "Créez une commande distincte pour chaque mode. Cela vous permet d'attribuer un raccourci clavier différent pour chaque mode. ⚠ Nécessite le rechargement du plugin pour prendre effet.",
95 | "title": "Séparer les commandes"
96 | },
97 | "links": "Liens",
98 | "log": {
99 | "callout": {
100 | "remove": "Supprimer le titre de l'accroche",
101 | "title": "Convertir l'accroche"
102 | },
103 | "editMode": "Mode édition",
104 | "empty": "selectedText est vide // Repli sur activeWindow.getSelection().toString()",
105 | "noHardBreaks": "Pas de pauses dures - Supprimez les espaces supplémentaires à la fin de chaque ligne",
106 | "readingMode": "Mode de lecture"
107 | },
108 | "modal": {
109 | "copyView": {
110 | "copy": "Copier",
111 | "editing": "Édition",
112 | "reading": "Lecture"
113 | },
114 | "replaceText": {
115 | "down": "Descendre",
116 | "title": "Remplacer du texte",
117 | "up": "Monter"
118 | }
119 | },
120 | "openTextReplacer": "Ouvrir le remplacement de texte",
121 | "other": "Autres options",
122 | "overrideCopy": {
123 | "desc": "Remplacer la copie native (CTRL + C, clic-droit...) par la copie améliorée (nécessite un rechargement d'Obsidian)",
124 | "title": "Prendre en charge la copie native"
125 | },
126 | "profile": {
127 | "add": {
128 | "desc": "Ajouter un profil de copy. Un rechargement d'Obsidian est nécessaire pour l'utiliser comme commande.",
129 | "title": "Ajouter un profile"
130 | },
131 | "delete": "Supprimer le profile"
132 | },
133 | "reading": {
134 | "desc": "Paramètre pour la vue de lecture",
135 | "title": "Lecture"
136 | },
137 | "removeFootnotesLinks": {
138 | "desc": "Modifier les balises des notes de bas de page lorsqu'elles sont copiées dans le presse-papiers.",
139 | "format": "Supprimer les liens, mais conserver le contenu sous forme de [^1]",
140 | "keep": "Conserver balises et contenu",
141 | "remove": "Supprimer balises et contenu",
142 | "title": "Format des footnotes"
143 | },
144 | "spaceSize": {
145 | "desc": "Modification du nombre d'espace pour la mise en retrait. Utiliser -1 pour désactiver la fonction de conversion.",
146 | "title": "Nombre d'espace"
147 | },
148 | "tabSpaceSize": "Nombre d'espaces à utiliser pour la mise en retrait",
149 | "tabToSpace": "Convertir les tabulations en espace",
150 | "unconventionalMarkdown": {
151 | "desc": "Suppression des balises Markdown non conventionnelles car elles ne sont pas prise en charge par tous les interpréteurs Markdown.",
152 | "title": "Markdown non conventionnel"
153 | },
154 | "view": {
155 | "all": "Tous les modes",
156 | "desc": "Choisissez le mode sur lequel les conversions doivent être appliqués.",
157 | "edit": "Mode Édition",
158 | "reading": "Mode lecture uniquement",
159 | "title": "Appliquer les paramètres"
160 | },
161 | "wikiToMarkdown": {
162 | "desc": "Obligatoire pour éditer les liens lors de leur copie dans le presse-papiers. Si cette option est désactivée, les paramètres pour le format des liens sera ignoré.",
163 | "title": "Convertir les liens Wiki en liens Markdown"
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/interface.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @file interface.ts
3 | * @description Interface for the plugin
4 | *
5 | */
6 |
7 | /**
8 | * How to convert the footnotes
9 | * @example `remove` : Remove all footnotes in markdown
10 | * `[[1]](#text)` -> ``
11 | * @example `format` : Keep the content of the footnote but remove the footnote format
12 | * `[[1]](#text)` -> `[^1]`
13 | * @example `keep` : Keep all footnotes in markdown
14 | * `[[1]](#text)` -> `[[1]](#text)`
15 | * @enum {string} ConversionOfFootnotes
16 | */
17 | export enum ConversionOfFootnotes {
18 | Keep = "keep",
19 | Remove = "remove",
20 | Format = "format",
21 | }
22 |
23 | /**
24 | * How to convert the links
25 | * @example `keep` : Keep all links in markdown
26 | * `[text](#link)` -> `[text](#link)`
27 | * @example `remove` : Remove all links in markdown
28 | * `[text](#link)` -> `text`
29 | * @example `external` : Remove all links in markdown except the external ones
30 | * `[text](#link)` -> `text`
31 | * `[text](https://example.com)` -> `[text](https://example.com)`
32 | * @enum {string} ConversionOfLinks
33 | */
34 | export enum ConversionOfLinks {
35 | Keep = "keep",
36 | Remove = "remove",
37 | External = "external",
38 | }
39 |
40 | /**
41 | * How to keep the title of the callout
42 | * @example `obsidian` : Keep the title as it is in Obsidian
43 | * `> [!note]` -> `> [!note]`
44 | * @example `strong` : Make the title bold
45 | * `> [!note]` -> `> **Note**`
46 | * @example `remove` : Remove the title
47 | * `> [!note]` -> `>`
48 | */
49 | export enum CalloutKeepType {
50 | Obsidian = "obsidian",
51 | Strong = "strong",
52 | Remove = "remove",
53 | RemoveKeepTitle = "removeKeepTitle",
54 | }
55 |
56 | /**
57 | * Where to apply the markdown conversion (reading, edit or both)
58 | * @example `all` : Apply the markdown conversion to both edit and reading mode
59 | * @example `reading` : Apply the markdown conversion to reading mode only
60 | * @example `edit` : Apply the markdown conversion to edit mode only
61 | */
62 | export enum ApplyingToView {
63 | All = "all",
64 | Reading = "reading",
65 | Edit = "edit",
66 | }
67 |
68 | export const COLLAPSE_INDICATOR = new RegExp(
69 | '',
70 | "gi"
71 | );
72 |
73 | export interface EnhancedCopySettings {
74 | copyAsHTML: boolean;
75 | applyingTo: ApplyingToView;
76 | separateHotkey: boolean;
77 | editing: GlobalSettings;
78 | reading: GlobalSettings;
79 | devMode: boolean;
80 | profiles: GlobalSettings[];
81 | }
82 |
83 | /**
84 | * Default settings of the plugin
85 | * @type {EnhancedCopySettings}
86 | * @constant
87 | * @default
88 | * @readonly
89 | */
90 | export const DEFAULT_SETTINGS: EnhancedCopySettings = {
91 | copyAsHTML: false,
92 | applyingTo: ApplyingToView.All,
93 | separateHotkey: false,
94 | editing: {
95 | footnotes: ConversionOfFootnotes.Keep,
96 | links: ConversionOfLinks.Keep,
97 | callout: CalloutKeepType.Obsidian,
98 | highlight: false,
99 | hardBreak: false,
100 | replaceText: [],
101 | overrideNativeCopy: false,
102 | },
103 | reading: {
104 | footnotes: ConversionOfFootnotes.Keep,
105 | links: ConversionOfLinks.Keep,
106 | callout: CalloutKeepType.Obsidian,
107 | highlight: false,
108 | hardBreak: false,
109 | replaceText: [],
110 | overrideNativeCopy: false,
111 | spaceReadingSize: -1, // -1 disabled
112 | wikiToMarkdown: false,
113 | },
114 | devMode: false,
115 | profiles: [],
116 | };
117 |
118 | export interface AutoRules {
119 | type: "path" | "tag" | "frontmatter";
120 | value: string;
121 | not?: boolean;
122 | }
123 |
124 | export interface ConvertDataview {
125 | enable: boolean;
126 | djs: {
127 | inline: boolean;
128 | block: boolean;
129 | };
130 | dql: {
131 | inline: boolean;
132 | block: boolean;
133 | };
134 | }
135 |
136 | export const DEFAULT_DATAVIEW_SETTINGS_DISABLED: ConvertDataview = {
137 | enable: false,
138 | djs: {
139 | inline: false,
140 | block: false,
141 | },
142 | dql: {
143 | inline: false,
144 | block: false,
145 | },
146 | };
147 |
148 | export const DEFAULT_DATAVIEW_SETTINGS_ENABLED: ConvertDataview = {
149 | enable: true,
150 | djs: {
151 | inline: true,
152 | block: true,
153 | },
154 | dql: {
155 | inline: true,
156 | block: true,
157 | },
158 | };
159 |
160 | export interface GlobalSettings {
161 | name?: string;
162 | copyAsHTML?: boolean;
163 | footnotes: ConversionOfFootnotes;
164 | links: ConversionOfLinks;
165 | callout: CalloutKeepType;
166 | convertDataview?: ConvertDataview;
167 | highlight: boolean;
168 | hardBreak: boolean;
169 | replaceText: ReplaceText[];
170 | overrideNativeCopy: boolean;
171 | spaceReadingSize?: number; //only work in reading mode
172 | tabToSpace?: boolean; //only work in edit mode
173 | tabSpaceSize?: number; //only work in edit mode
174 | wikiToMarkdown?: boolean;
175 | applyingTo?: ApplyingToView; //only work in profiles
176 | /**
177 | * Allow to automatically use this profile based on the path, tag or frontmatter
178 | */
179 | autoRules?: AutoRules[]; //only work in profiles
180 | }
181 |
182 | export interface ReplaceText {
183 | pattern: string;
184 | replacement: string;
185 | }
186 |
187 | export enum CopySettingsView {
188 | Reading = "reading",
189 | Editing = "editing",
190 | }
191 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { EditorView } from "@codemirror/view";
2 | import i18next from "i18next";
3 | import { around } from "monkey-around";
4 | import {
5 | getAllTags,
6 | ItemView,
7 | MarkdownView,
8 | Platform,
9 | Plugin,
10 | type WorkspaceLeaf,
11 | } from "obsidian";
12 | import merge from "ts-deepmerge";
13 |
14 | import { resources, translationLanguage } from "./i18n/i18next";
15 | import {
16 | ApplyingToView,
17 | type AutoRules,
18 | DEFAULT_SETTINGS,
19 | type EnhancedCopySettings,
20 | type GlobalSettings,
21 | } from "./interface";
22 | import { EnhancedCopySettingTab } from "./settings";
23 | import { convertEditMarkdown, convertMarkdown } from "./utils/conversion";
24 | import { removeDataBasePluginRelationShip } from "./utils/pluginFix";
25 | import {
26 | canvasSelectionText,
27 | copySelectionRange,
28 | getSelectionAsHTML,
29 | } from "./utils/selection";
30 |
31 | export default class EnhancedCopy extends Plugin {
32 | settings: EnhancedCopySettings = DEFAULT_SETTINGS;
33 | //eslint-disable-next-line @typescript-eslint/no-explicit-any
34 | activeMonkeys: Record93 | *99 | * ``` 100 | * to: 101 | * ```html 102 | *94 | *97 | * 95 | *info96 | *98 | *coucou
103 | * info 104 | *106 | * ``` 107 | * @param div {HTMLDivElement} The selected contents as HTML 108 | * @returns {HTMLDivElement} The div transformed 109 | */ 110 | function simplifyBlockquote(div: HTMLDivElement): HTMLDivElement { 111 | const blockquotes = div.querySelectorAll("blockquote"); 112 | blockquotes.forEach((blockquote) => { 113 | const modifiedDivElement = document.createElement("div"); 114 | const calloutTitleInner = blockquote.querySelector(".callout-title-inner"); 115 | const calloutContent = blockquote.querySelector(".callout-content"); 116 | if (!calloutTitleInner && !calloutContent) return; 117 | const calloutTitleInnerText = calloutTitleInner?.innerHTML 118 | ? `${calloutTitleInner!.innerHTML}coucou
105 | *
123 | ${calloutTitleInnerText} 124 | ${calloutContentHTML.replace("126 | `; 127 | //we need to replace the the blockquote by the new simplified blockquote 128 | blockquote.replaceWith(modifiedDivElement); 129 | }); 130 | return div; 131 | } 132 | 133 | function capitalize(string: string | undefined) { 134 | if (string === undefined) return ""; 135 | return string.charAt(0).toUpperCase() + string.slice(1); 136 | } 137 | -------------------------------------------------------------------------------- /src/utils/pluginFix.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Some div are not correctly converted to markdown, this function fix them 3 | * It is a fix for the plugin dbfolder (@RafaelGB/obsidian-db-folder) 4 | * As the formatting is broken when copying the table from the plugin. 5 | * @note More function like that can be added in the future if needed 6 | * @returns {string} The selected text (copying behavior of Obsidian) 7 | */ 8 | 9 | export function removeDataBasePluginRelationShip(): string { 10 | if (activeDocument.querySelector("div.database-plugin__container")) { 11 | const div = document.createElement("div"); 12 | const getSelection = activeWindow.getSelection(); 13 | if (!getSelection) return ""; 14 | const selection = getSelection.getRangeAt(0); 15 | const fragment = selection.cloneContents(); 16 | div.appendChild(fragment); 17 | const allSpan = div.querySelectorAll("span"); 18 | for (const span of allSpan) { 19 | //remove database-plugin__relationship class 20 | if (span.classList[0] === "database-plugin__relationship") { 21 | //remove entire span 22 | //replace by br 23 | span.innerHTML = "\n"; 24 | } 25 | } 26 | return div.innerText; 27 | } else { 28 | const getSelection = activeWindow.getSelection(); 29 | if (!getSelection) return ""; 30 | return getSelection.toString(); 31 | } 32 | } 33 | 34 | /** 35 | * Metabind is rendered like this, in the HTML: 36 | * ```html 37 | *", "").replace("
", "")} 125 |
xxx
38 | * ```
39 | * This return a strange formated code block in markdown, this function fix it, with converting to text
40 | * @param html
41 | */
42 | export function fixMetaBindCopy(html: string) {
43 | return html
44 | .replace(
45 | /(.*?)<\/div><\/code>/gim,
46 | "$1"
47 | )
48 | .replace(
49 | /