407 | );
408 | }
409 |
410 | export default App;
411 |
--------------------------------------------------------------------------------
/src/chromium/OptionsApp.js:
--------------------------------------------------------------------------------
1 | /* global chrome */
2 | import React, { useState, useEffect } from "react";
3 |
4 | const OptionsApp = () => {
5 | const [vault, setVault] = useState("");
6 | const [folder, setFolder] = useState("");
7 | const [showAdvancedFeatures, setShowAdvancedFeatures] = useState(false);
8 | const [noteContentFormat, setNoteContentFormat] = useState("");
9 |
10 | const defaultNoteContentFormat = "{url}\n\n{content}";
11 |
12 | useEffect(() => {
13 | // Load the settings from browser storage
14 | chrome.storage.sync.get(
15 | [
16 | "obsidianVault",
17 | "folderPath",
18 | "showAdvancedFeatures",
19 | "noteContentFormat",
20 | ],
21 | (result) => {
22 | if (result.obsidianVault) {
23 | setVault(result.obsidianVault);
24 | }
25 | if (result.folderPath) {
26 | setFolder(result.folderPath);
27 | }
28 | if (result.showAdvancedFeatures) {
29 | setShowAdvancedFeatures(result.showAdvancedFeatures);
30 | }
31 | if (result.noteContentFormat) {
32 | setNoteContentFormat(result.noteContentFormat);
33 | } else {
34 | // Set default note format if not found in storage
35 | setNoteContentFormat(defaultNoteContentFormat);
36 | }
37 | }
38 | );
39 | }, []);
40 |
41 | useEffect(() => {
42 | if (!showAdvancedFeatures) {
43 | setNoteContentFormat(defaultNoteContentFormat);
44 | }
45 | }, [showAdvancedFeatures]);
46 |
47 | const handleSave = () => {
48 | // Check if the required fields are empty
49 | if (vault.trim() === "" || folder.trim() === "") {
50 | alert(
51 | "Please provide a value for both Obsidian Vault Name and Clip Notes to fields."
52 | );
53 | return;
54 | }
55 |
56 | const invalidCharacterPattern = /[\\:*?"<>|]/;
57 |
58 | if (
59 | invalidCharacterPattern.test(vault) ||
60 | invalidCharacterPattern.test(folder)
61 | ) {
62 | alert(
63 | 'Invalid character detected. Please avoid using the following characters in the Vault Name or Folder Path: /, \\, :, *, ?, ", <, >, |'
64 | );
65 | return;
66 | }
67 |
68 | // Check if the folder path matches the pattern
69 | // const folderPattern = /^(\{title\}|[\w\s]+\/\{title\})$/;
70 | // const folderPattern = /^([\w\s().]+\/)*\{title\}$/;
71 | // const folderPattern = /^([\p{L}\p{Script=Hani}\s().]+\/)*\{title\}$/u;
72 | // const folderPattern = /^([\p{L}\p{Script=Hani}\s().\-_!@#$%^&()+={}[\];',~]+\/)*\{title\}$/u;
73 | // const folderPattern = /^(([\p{L}\p{Script=Hani}\s()\-_!@#$%^&()+={}[\];',~][\p{L}\p{Script=Hani}\s().\-_!@#$%^&()+={}[\];',~]*\/)*\{title\})$/u;
74 | // const folderPattern = /^(([\p{L}\p{Script=Hani}\p{Emoji}\s()\-_!@#$%^&()+={}[\];',~][\p{L}\p{Script=Hani}\p{Emoji}\s().\-_!@#$%^&(`+={}[\];',~]*\/)*\{title\})$/u;
75 | const folderPattern =
76 | /^(([\p{L}\p{N}\p{Emoji}\p{Emoji_Component}\s()\-_!@#$%^&()+={}[\];',~][\p{L}\p{N}\p{Emoji}\p{Emoji_Component}\s().\-_!@#$%^&()+={}[\];',~]*\/)*\{title\})$/u;
77 |
78 | if (!folderPattern.test(folder.trim())) {
79 | alert(
80 | "Invalid folder format. Please use '{title}' or 'Folder Name/{title}' as the folder path."
81 | );
82 | return;
83 | }
84 |
85 | // if the user has enabled advanced features and the noteContentFormat is empty, send an alert and reset it to the default format
86 | if (showAdvancedFeatures && noteContentFormat === "") {
87 | alert(
88 | "If you want to use advanced features, please provide a note content format."
89 | );
90 | setNoteContentFormat(defaultNoteContentFormat);
91 | return;
92 | }
93 |
94 | // Determine the noteContentFormat to save
95 | const savedNoteContentFormat = showAdvancedFeatures
96 | ? noteContentFormat
97 | : defaultNoteContentFormat;
98 |
99 | // Save the settings to browser storage
100 | chrome.storage.sync.set(
101 | {
102 | obsidianVault: vault,
103 | folderPath: folder,
104 | showAdvancedFeatures: showAdvancedFeatures,
105 | noteContentFormat: savedNoteContentFormat,
106 | },
107 | () => {
108 | if (chrome.runtime.lastError) {
109 | console.error(`Error: ${chrome.runtime.lastError}`);
110 | } else {
111 | alert(
112 | `Success!👌\n\nYour notes will be saved to the vault named "${vault}" using the format "${folder}".`
113 | );
114 | }
115 | }
116 | );
117 | };
118 |
119 | const handleTest = () => {
120 | // Check if the required fields are empty
121 | if (vault.trim() === "" || folder.trim() === "") {
122 | alert(
123 | "Please provide a value for both Obsidian Vault Name and Clip Notes to fields."
124 | );
125 | return;
126 | }
127 | const invalidCharacterPattern = /[\\:*?"<>|]/;
128 |
129 | if (
130 | invalidCharacterPattern.test(vault) ||
131 | invalidCharacterPattern.test(folder)
132 | ) {
133 | alert(
134 | 'Invalid character detected. Please avoid using the following characters in the Vault Name or Folder Path: /, \\, :, *, ?, ", <, >, |'
135 | );
136 | return;
137 | }
138 |
139 | // if the user has enabled advanced features and the noteContentFormat is empty, set it to the default format
140 | if (showAdvancedFeatures && noteContentFormat === "") {
141 | alert(
142 | "If you want to use advanced features, please provide a note content format."
143 | );
144 | setNoteContentFormat(defaultNoteContentFormat);
145 | return;
146 | }
147 |
148 | // Generate a test note based on the settings
149 | const title = "Obsidian Web Clipper Test Note";
150 | const url = "https://example.com";
151 | const content =
152 | "This is a test note generated by the Obsidian Web Clipper extension.";
153 | const date = new Date().toISOString().split("T")[0]; // Get current date in YYYY-MM-DD format
154 |
155 | let formattedContent;
156 | if (showAdvancedFeatures && noteContentFormat) {
157 | formattedContent = noteContentFormat
158 | .replace("{url}", url)
159 | .replace("{title}", title)
160 | .replace("{content}", content)
161 | .replace("{date}", date);
162 | } else {
163 | formattedContent = `${url}\n\n${content}`;
164 | }
165 |
166 | const folderPattern =
167 | /^(([\p{L}\p{N}\p{Emoji}\p{Emoji_Component}\s()\-_!@#$%^&()+={}[\];',~][\p{L}\p{N}\p{Emoji}\p{Emoji_Component}\s().\-_!@#$%^&()+={}[\];',~]*\/)*\{title\})$/u;
168 |
169 | if (!folderPattern.test(folder.trim())) {
170 | alert(
171 | "Invalid folder format. Please use '{title}' or 'Folder Name/{title}' as the folder path."
172 | );
173 | return;
174 | }
175 |
176 | let folderPath = folder.trim().replace("{title}", "");
177 | if (folderPath && !folderPath.endsWith("/")) {
178 | folderPath += "/";
179 | }
180 |
181 | const obsidianUri = `obsidian://new?vault=${encodeURIComponent(
182 | vault
183 | )}&file=${encodeURIComponent(
184 | folderPath + title
185 | )}&content=${encodeURIComponent(formattedContent)}`;
186 | // Check if vault is not empty before opening the URI
187 | if (vault.trim() !== "") {
188 | window.open(obsidianUri, "_blank");
189 | } else {
190 | alert("Please provide a valid Obsidian Vault Name.");
191 | }
192 | };
193 |
194 | return (
195 |
196 |
Obsidian Web Clipper
197 |
198 | This is an unofficial web clipper for Obsidian that allows you to easily
199 | create notes within a popup and save them directly to your Obsidian
200 | vault. By default, the extension designates the webpage title as your
201 | note title, and saves the webpage link at the top of your note's
202 | content.
203 |
204 |
205 | This extension is free, open-source, and available on{" "}
206 |
212 | GitHub
213 |
214 | . If you find this tool helpful and wish to support its development,
215 | you're welcome to make a donation through{" "}
216 |
222 | PayPal
223 |
224 | .
225 |
325 |
326 | If you're confused just copy either the default format or the
327 | frontmatter format and paste it in the text box below. Then click
328 | the "Test Settings" button to see how it looks.
329 |
330 |
331 |
355 |
362 | )}
363 |
364 |
371 |
378 |
379 |
380 | );
381 | };
382 |
383 | export default OptionsApp;
384 |
--------------------------------------------------------------------------------
/src/firefox/OptionsApp.js:
--------------------------------------------------------------------------------
1 | /* global browser */
2 | import React, { useState, useEffect } from "react";
3 |
4 | const OptionsApp = () => {
5 | const [vault, setVault] = useState("");
6 | const [folder, setFolder] = useState("");
7 | const [showAdvancedFeatures, setShowAdvancedFeatures] = useState(false);
8 | const [noteContentFormat, setNoteContentFormat] = useState("");
9 |
10 | const defaultNoteContentFormat = "{url}\n\n{content}";
11 |
12 | useEffect(() => {
13 | // Load the settings from browser storage
14 | browser.storage.sync.get(
15 | [
16 | "obsidianVault",
17 | "folderPath",
18 | "showAdvancedFeatures",
19 | "noteContentFormat",
20 | ],
21 | (result) => {
22 | if (result.obsidianVault) {
23 | setVault(result.obsidianVault);
24 | }
25 | if (result.folderPath) {
26 | setFolder(result.folderPath);
27 | }
28 | if (result.showAdvancedFeatures) {
29 | setShowAdvancedFeatures(result.showAdvancedFeatures);
30 | }
31 | if (result.noteContentFormat) {
32 | setNoteContentFormat(result.noteContentFormat);
33 | } else {
34 | // Set default note format if not found in storage
35 | setNoteContentFormat(defaultNoteContentFormat);
36 | }
37 | }
38 | );
39 | }, []);
40 |
41 | useEffect(() => {
42 | if (!showAdvancedFeatures) {
43 | setNoteContentFormat(defaultNoteContentFormat);
44 | }
45 | }, [showAdvancedFeatures]);
46 |
47 | const handleSave = () => {
48 | // Check if the required fields are empty
49 | if (vault.trim() === "" || folder.trim() === "") {
50 | alert(
51 | "Please provide a value for both Obsidian Vault Name and Clip Notes to fields."
52 | );
53 | return;
54 | }
55 |
56 | const invalidCharacterPattern = /[\\:*?"<>|]/;
57 |
58 | if (
59 | invalidCharacterPattern.test(vault) ||
60 | invalidCharacterPattern.test(folder)
61 | ) {
62 | alert(
63 | 'Invalid character detected. Please avoid using the following characters in the Vault Name or Folder Path: /, \\, :, *, ?, ", <, >, |'
64 | );
65 | return;
66 | }
67 |
68 | // Check if the folder path matches the pattern
69 | // const folderPattern = /^(\{title\}|[\w\s]+\/\{title\})$/;
70 | // const folderPattern = /^([\w\s().]+\/)*\{title\}$/;
71 | // const folderPattern = /^([\p{L}\p{Script=Hani}\s().]+\/)*\{title\}$/u;
72 | // const folderPattern = /^([\p{L}\p{Script=Hani}\s().\-_!@#$%^&()+={}[\];',~]+\/)*\{title\}$/u;
73 | // const folderPattern = /^(([\p{L}\p{Script=Hani}\s()\-_!@#$%^&()+={}[\];',~][\p{L}\p{Script=Hani}\s().\-_!@#$%^&()+={}[\];',~]*\/)*\{title\})$/u;
74 | // const folderPattern =
75 | // /^(([\p{L}\p{Script=Hani}\p{Extended_Pictographic}[\p{Emoji_Modifier}\p{M}\s()\-_!@#$%^&()+={}[\];',~][\p{L}\p{Script=Hani}\p{Extended_Pictographic}[\p{Emoji_Modifier}\p{M}\s().\-_!@#$%^&()+={}[\];',~]*\/)*\{title\})$/u;
76 |
77 | const folderPattern =
78 | /^(([\p{L}\p{N}\p{Script=Hani}\p{Extended_Pictographic}[\p{Emoji_Modifier}\p{M}\s()\-_!@#$%^&()+={}[\];',~][\p{L}\p{N}\p{Script=Hani}\p{Extended_Pictographic}[\p{Emoji_Modifier}\p{M}\s().\-_!@#$%^&()+={}[\];',~]*\/)*\{title\})$/u;
79 |
80 | if (!folderPattern.test(folder.trim())) {
81 | alert(
82 | "Invalid folder format. Please use '{title}' or 'Folder Name/{title}' as the folder path."
83 | );
84 | return;
85 | }
86 |
87 | // if the user has enabled advanced features and the noteContentFormat is empty, send an alert and reset it to the default format
88 | if (showAdvancedFeatures && noteContentFormat === "") {
89 | alert(
90 | "If you want to use advanced features, please provide a note content format."
91 | );
92 | setNoteContentFormat(defaultNoteContentFormat);
93 | return;
94 | }
95 |
96 | // Determine the noteContentFormat to save
97 | const savedNoteContentFormat = showAdvancedFeatures
98 | ? noteContentFormat
99 | : defaultNoteContentFormat;
100 |
101 | // Save the settings to browser storage
102 | browser.storage.sync.set(
103 | {
104 | obsidianVault: vault,
105 | folderPath: folder,
106 | showAdvancedFeatures: showAdvancedFeatures,
107 | noteContentFormat: savedNoteContentFormat,
108 | },
109 | () => {
110 | if (browser.runtime.lastError) {
111 | console.error(`Error: ${browser.runtime.lastError}`);
112 | } else {
113 | alert(
114 | `Success!👌\n\nYour notes will be saved to the vault named "${vault}" using the format "${folder}".`
115 | );
116 | }
117 | }
118 | );
119 | };
120 |
121 | const handleTest = () => {
122 | // Check if the required fields are empty
123 | if (vault.trim() === "" || folder.trim() === "") {
124 | alert(
125 | "Please provide a value for both Obsidian Vault Name and Clip Notes to fields."
126 | );
127 | return;
128 | }
129 | const invalidCharacterPattern = /[\\:*?"<>|]/;
130 |
131 | if (
132 | invalidCharacterPattern.test(vault) ||
133 | invalidCharacterPattern.test(folder)
134 | ) {
135 | alert(
136 | 'Invalid character detected. Please avoid using the following characters in the Vault Name or Folder Path: /, \\, :, *, ?, ", <, >, |'
137 | );
138 | return;
139 | }
140 |
141 | // if the user has enabled advanced features and the noteContentFormat is empty, set it to the default format
142 | if (showAdvancedFeatures && noteContentFormat === "") {
143 | alert(
144 | "If you want to use advanced features, please provide a note content format."
145 | );
146 | setNoteContentFormat(defaultNoteContentFormat);
147 | return;
148 | }
149 |
150 | // Generate a test note based on the settings
151 | const title = "Obsidian Web Clipper Test Note";
152 | const url = "https://example.com";
153 | const content =
154 | "This is a test note generated by the Obsidian Web Clipper extension.";
155 | const date = new Date().toISOString().split("T")[0]; // Get current date in YYYY-MM-DD format
156 |
157 | let formattedContent;
158 | if (showAdvancedFeatures && noteContentFormat) {
159 | formattedContent = noteContentFormat
160 | .replace("{url}", url)
161 | .replace("{title}", title)
162 | .replace("{content}", content)
163 | .replace("{date}", date);
164 | } else {
165 | formattedContent = `${url}\n\n${content}`;
166 | }
167 |
168 | const folderPattern =
169 | /^(([\p{L}\p{N}\p{Script=Hani}\p{Extended_Pictographic}[\p{Emoji_Modifier}\p{M}\s()\-_!@#$%^&()+={}[\];',~][\p{L}\p{N}\p{Script=Hani}\p{Extended_Pictographic}[\p{Emoji_Modifier}\p{M}\s().\-_!@#$%^&()+={}[\];',~]*\/)*\{title\})$/u;
170 |
171 | if (!folderPattern.test(folder.trim())) {
172 | alert(
173 | "Invalid folder format. Please use '{title}' or 'Folder Name/{title}' as the folder path."
174 | );
175 | return;
176 | }
177 |
178 | let folderPath = folder.trim().replace("{title}", "");
179 | if (folderPath && !folderPath.endsWith("/")) {
180 | folderPath += "/";
181 | }
182 |
183 | const obsidianUri = `obsidian://new?vault=${encodeURIComponent(
184 | vault
185 | )}&file=${encodeURIComponent(
186 | folderPath + title
187 | )}&content=${encodeURIComponent(formattedContent)}`;
188 | // Check if vault is not empty before opening the URI
189 | if (vault.trim() !== "") {
190 | window.open(obsidianUri, "_blank");
191 | } else {
192 | alert("Please provide a valid Obsidian Vault Name.");
193 | }
194 | };
195 |
196 | return (
197 |
198 |
Obsidian Web Clipper
199 |
200 | This is an unofficial web clipper for Obsidian that allows you to easily
201 | create notes within a popup and save them directly to your Obsidian
202 | vault. By default, the extension designates the webpage title as your
203 | note title, and saves the webpage link at the top of your note's
204 | content.
205 |
206 |
207 | This extension is free, open-source, and available on{" "}
208 |
214 | GitHub
215 |
216 | . If you find this tool helpful and wish to support its development,
217 | you're welcome to make a donation through{" "}
218 |
224 | PayPal
225 |
226 | .
227 |
327 |
328 | If you're confused just copy either the default format or the
329 | frontmatter format and paste it in the text box below. Then click
330 | the "Test Settings" button to see how it looks.
331 |
332 |