Compare commits

...

43 commits

Author SHA1 Message Date
Ell 0cf9067abf added discord to readme 2024-05-08 17:31:38 +02:00
Ell 3526aaf70c 2.4.7 2024-02-21 13:10:24 +01:00
Ell b0a325a04f fixed settings tab missing custom js 2024-02-21 13:08:46 +01:00
Afdzal Yunus df1ddd5c17
Improve preset styling for Google Keeps frame (#99)
* Improve preset styling for Google Keeps

* Remove pointer cursor from app logo

* Remove pointer cursor from app logo

---------

Co-authored-by: Ell <me@ellpeck.de>
2024-02-21 13:07:20 +01:00
WmeLuna 0ec307eab6
feat: Add Custom JS (#104)
* Add Custom JS

* Resolve requested changes
2023-10-11 22:01:03 +02:00
Ell bd10df45ee update known issues & roadmap 2023-08-25 14:00:18 +02:00
Ell 0fc0e9e18d ignore vault files 2023-08-11 19:28:02 +02:00
Fruffel 0cd6422f74
Update frame.ts - 'allow-downloads' added to sanbox attribute (#84) 2023-07-11 11:04:39 +02:00
Ell ab10797f39 cleanup 2023-05-22 19:37:17 +02:00
Ell 51b03d7bfc also remove google calendar logo padding 2023-05-10 19:14:22 +02:00
Ell c1f085d26e Improved default GCal css 2023-05-10 18:56:00 +02:00
Ell d96c8baf8c 2.4.6 2023-05-07 17:55:24 +02:00
Ell 3ffe20f441 fixed centered views having weird behavior with tabs
closes #67
2023-05-07 17:33:39 +02:00
Ell 21a8230e70 2.4.5 2023-01-30 12:17:38 +01:00
Ell 256f00e727 cleaned up google tasks preset 2023-01-30 12:12:56 +01:00
Lin, Yong Xiang 880d4c975c
Add google tasks preset (#63) 2023-01-30 12:12:14 +01:00
Ell 3ec5caf330 fixed custom frames in new windows
closes #43 closes #46
2023-01-30 12:11:19 +01:00
Ell 2363a5e338 fixed more options not displaying in sidebar tabs
Closes #66
2023-01-30 11:55:45 +01:00
Ell 72c14f63d7 2.4.4 2022-09-17 18:30:45 +02:00
Ell 4340d08e29 focus a frame's content when it gets opened 2022-09-17 18:28:06 +02:00
Ell 33f3407afe don't detach existing nodes when opening from the sidebar
Closes #58
2022-09-17 17:42:34 +02:00
Ell 2e5cbe42f5 don't sync new workspace for test vault 2022-09-17 17:38:24 +02:00
Ell 92db9fe55d improved google calendar settings
Closes #39
2022-09-17 17:37:23 +02:00
Ell 01ad91c8d4 fixed google calendar preset forcing day view 2022-09-17 17:34:41 +02:00
Ell 8db94a85f9 2.4.3 2022-08-21 18:22:15 +02:00
Ell f2c73ef189 added a security disclaimer
Closes #54
2022-08-21 18:13:38 +02:00
Ell 0a70f83d46 untrack main.js 2022-08-21 18:01:02 +02:00
Ell a91735dc44 added a unique class to each custom frame
Closes #53
2022-08-21 17:59:12 +02:00
firinael b425e9671f
add Detexify frame (#56) 2022-08-21 17:52:04 +02:00
Ell 14b01b67e5 fixed untracked files 2022-08-21 17:50:27 +02:00
Ell 8b7c80d218 some cleanup 2022-08-21 17:49:57 +02:00
Ell b805078fb1 fixed test vault data being ignored 2022-08-21 17:41:40 +02:00
Ell e826e93d27 added a test vault 2022-08-21 17:37:20 +02:00
Ell 5022385ee1 updated to new obsidian API 2022-06-19 23:42:29 +02:00
Ell c37dff03fd 2.4.2 2022-06-01 15:24:57 +02:00
Ell dd8f3054ee Added "open in browser" button
Closes #32
2022-06-01 15:22:33 +02:00
Ell cd0285c2dd Allow adding a URL suffix to Markdown mode, closes #37 2022-06-01 15:15:43 +02:00
Ell 52c1005440 allow holding control to open a center frame in a new split
Closes #33
2022-06-01 15:04:53 +02:00
Ell 5a0597d930 Merge branch 'master' of https://github.com/Ellpeck/ObsidianCustomFrames 2022-05-21 16:27:30 +02:00
Ell 0b397da89e improve the readme a bit 2022-05-21 16:27:29 +02:00
Ell fcdda42106 removed some outdated known issues 2022-05-05 13:28:53 +02:00
Ell c557ce548f 2.4.1 2022-04-26 13:11:13 +02:00
Ell 126000617f added a setting to force iframe usage
Closes #25
2022-04-26 13:05:22 +02:00
26 changed files with 2299 additions and 2592 deletions

View file

@ -1,9 +1,9 @@
# top-most EditorConfig file
root = true
[*]
charset = utf-8
insert_final_newline = true
indent_style = tab
indent_size = 4
tab_width = 4
# top-most EditorConfig file
root = true
[*]
charset = utf-8
insert_final_newline = true
indent_style = space
indent_size = 4
tab_width = 4

45
.gitignore vendored
View file

@ -1,22 +1,23 @@
# 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
# 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
workspace
workspace.json
# Exclude macOS Finder (System Explorer) View States
.DS_Store

148
README.md
View file

@ -1,73 +1,75 @@
# Obsidian Custom Frames
An Obsidian plugin that turns web apps into panes using iframes with custom styling. Also comes with presets for Google Keep, Todoist and more.
⚠️⚠️⚠️ **For header-heavy sites like Google Keep and Todoist to work, this plugin requires Obsidian 0.14.3.** ⚠️⚠️⚠️
![A screenshot of the plugin in action, where you can see Google Keep attached as a narrow side pane on the right](https://raw.githubusercontent.com/Ellpeck/ObsidianCustomFrames/master/screenshot.png)
![A screenshot of the plugin in action, where you can see Google Calendar opened in the center, and the mouse hovering over the corresponding ribbon button](https://raw.githubusercontent.com/Ellpeck/ObsidianCustomFrames/master/screenshot-big.png)
![A screenshot of the plugin's settings](https://raw.githubusercontent.com/Ellpeck/ObsidianCustomFrames/master/settings.png)
## 🤔 Usage
To use this plugin, simply go into its settings and add a new frame, either from a preset shipped with the plugin, or a custom one that you can edit yourself. Each frame's pane can be opened using the "Custom Frames: Open" command.
There are also plenty of settings to customize your frame further, including adding custom CSS to the site, adding a ribbon icon, displaying the frame in the center of the editor, and more.
### 🗒️ Markdown Mode
You can also display your custom frames in your Markdown documents. Custom Frames adds a special code block syntax that transforms the code block into a custom frame in Live Preview and Reading mode. Your code block should look like this:
~~~
```custom-frames
frame: YOUR FRAME'S NAME
```
~~~
Optionally, you can also pass custom style settings to the embed, which allows you to change things like the embed's height:
~~~
```custom-frames
frame: YOUR FRAME'S NAME
style: SOME CSS
~~~
Here's an example that creates a very tall embed using the [Google Keep preset](#-presets):
~~~
```custom-frames
frame: Google Keep
style: height: 1000px;
```
~~~
### 📱 On Obsidian Mobile
Unfortunately, Obsidian Mobile does not run on [Electron](https://www.electronjs.org/), which is what allows iframes and [webviews](https://www.electronjs.org/docs/latest/api/webview-tag) to be displayed with very few restrictions related to cookies, cross-origin resource sharing, and so on. This means that a lot of sites won't work there, especially ones that you have to log in to. However, when you create a frame, you can toggle the "Disable on Mobile" option to hide a Desktop-only frame in Obsidian mobile.
## 📦 Presets
By default, Custom Frames comes with a few presets that allow you to get new panes for popular sites up and running quickly.
- [Obsidian Forum](https://forum.obsidian.md/)
- [Google Keep](https://keep.google.com), optimized for a narrow pane on the side
- [Google Calendar](https://calendar.google.com/calendar/u/0/r/day), optimized by removing some buttons. Close side panel with top-left button.
- [Todoist](https://todoist.com), optimized for a narrow (half-height) side panel by removing some buttons and slimming margins.
- [Notion](https://www.notion.so/) (it's recommended to close Notion's sidebar if used as a side pane)
- [Twitter](https://twitter.com)
If you create a frame that you think other people would like, don't hesitate to create a pull request with [a new preset](https://github.com/Ellpeck/ObsidianCustomFrames/blob/master/src/settings.ts#L5).
## 🛣️ Roadmap
- ~~Allow setting a custom icon for each pane~~
- ~~Allow displaying custom frames in Markdown code blocks~~
- ~~Add the ability to add a ribbon button for a frame that opens it in the main view~~
- Allow creating links that open in a custom frame rather than the browser
- Possibly allow executing custom JavaScript in iframes (though security implications still need to be explored)
- Add a global setting that causes popups to be opened in a new Obsidian window rather than the default browser
- Add more options to Markdown mode, like allowing for back and forward buttons
## ⚠️ Known Issues
There are a few known issues with Custom Frames. If you encounter any of these, please **don't** report it on the issue tracker.
- In older versions of Obsidian, specifically **0.14.2 and lower**, a lot of websites don't function properly in custom frames. This is due to these older versions not having features in place that allow for frames to have special, additional functionality related to cookies and headers.
- When dragging or moving a pane, hovering the mouse over a custom frame will cause it to get stuck or behave unexpectedly. This is an issue with Obsidian itself, which is being investigated.
## 🙏 Acknowledgements
Thanks to [lishid](https://github.com/lishid) for their help with making iframes work in Obsidian for a purpose like this. Also thanks to them for *motivating* me to turn Obsidian Keep into a more versatile plugin, which is how Custom Frames was born.
If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!
[![Support me (if you want), via Patreon, Ko-fi or GitHub Sponsors](https://ellpeck.de/res/generalsupport.png)](https://ellpeck.de/support)
# Obsidian Custom Frames
An Obsidian plugin that turns web apps into panes using iframes with custom styling. Also comes with presets for Google Keep, Todoist and more.
![A screenshot of the plugin in action, where you can see Google Keep attached as a narrow side pane on the right](https://raw.githubusercontent.com/Ellpeck/ObsidianCustomFrames/master/screenshot.png)
![A screenshot of the plugin in action, where you can see Google Calendar opened in the center, and the mouse hovering over the corresponding ribbon button](https://raw.githubusercontent.com/Ellpeck/ObsidianCustomFrames/master/screenshot-big.png)
![A screenshot of the plugin's settings](https://raw.githubusercontent.com/Ellpeck/ObsidianCustomFrames/master/settings.png)
## 🤔 Usage
To use this plugin, simply go into its settings and add a new frame, either from a preset shipped with the plugin, or a custom one that you can edit yourself.
### 🪟 Pane Mode
To open a Custom Frame as a pane, you can use the "Custom Frames: Open" command.
There are also plenty of settings to customize your frame further, including adding custom CSS to the site, adding a ribbon icon, displaying the frame in the center of the editor, and more.
### 🗒️ Markdown Mode
You can also display your custom frames in your Markdown documents. Custom Frames adds a special code block syntax that transforms the code block into a custom frame in Live Preview and Reading mode. Your code block should look like this:
~~~
```custom-frames
frame: YOUR FRAME'S NAME
```
~~~
Optionally, you can also pass custom style settings to the embed, which allows you to change things like the embed's height, as well as an additional suffix that will be appended to the frame's regular URL, which can be useful for things like displaying a specific note in Google Keep.
Here's an example using the [Google Keep preset](#-presets):
~~~
```custom-frames
frame: Google Keep
style: height: 1000px;
urlSuffix: #reminders
```
~~~
### 📱 On Obsidian Mobile
Unfortunately, Obsidian Mobile does not run on [Electron](https://www.electronjs.org/), which is what allows iframes and [webviews](https://www.electronjs.org/docs/latest/api/webview-tag) to be displayed with very few restrictions related to cookies, cross-origin resource sharing, and so on. This means that a lot of sites won't work there, especially ones that you have to log in to. However, when you create a frame, you can toggle the "Disable on Mobile" option to hide a Desktop-only frame in Obsidian mobile.
Need help using the plugin? Feel free to join the Discord server!
[![Join the Discord server](https://ellpeck.de/res/discord-wide.png)](https://link.ellpeck.de/discordweb)
## 📦 Presets
By default, Custom Frames comes with a few presets that allow you to get new panes for popular sites up and running quickly.
- [Obsidian Forum](https://forum.obsidian.md/)
- [Google Keep](https://keep.google.com), optimized for a narrow pane on the side
- [Google Calendar](https://calendar.google.com/calendar/u/0/r/day), optimized by removing some buttons. Close side panel with top-left button.
- [Todoist](https://todoist.com), optimized for a narrow (half-height) side panel by removing some buttons and slimming margins.
- [Notion](https://www.notion.so/) (it's recommended to close Notion's sidebar if used as a side pane)
- [Twitter](https://twitter.com)
If you create a frame that you think other people would like, don't hesitate to create a pull request with [a new preset](https://github.com/Ellpeck/ObsidianCustomFrames/blob/master/src/settings.ts#L5).
## 🛣️ Roadmap
- ~~Allow setting a custom icon for each pane~~
- ~~Allow displaying custom frames in Markdown code blocks~~
- ~~Add the ability to add a ribbon button for a frame that opens it in the main view~~
- Allow creating links outside of Obsidian that open in a custom frame
- Possibly allow executing custom JavaScript in iframes (though security implications still need to be explored)
- Add a global setting that causes popups to be opened in a new Obsidian window rather than the default browser
- Add more options to Markdown mode, like allowing for back and forward buttons
- Possibly allow extracting selected text into a note similar to how the Note composer plugin works, and potentially allow using a note template that includes the link to the site extracted from
## ⚠️ Known Issues
There are a few known issues with Custom Frames. If you encounter any of these, please **don't** report it on the issue tracker.
- Popups and new tabs are currently opened in the default browser rather than the custom frame. You can find more info, including workarounds for logging in to certain sites, in [this issue](https://github.com/Ellpeck/ObsidianCustomFrames/issues/40).
- Some links refuse to open from within custom frames, especially before Obsidian 1.3.7. You can find more info in [this issue](https://github.com/Ellpeck/ObsidianCustomFrames/issues/76).
## 🙏 Acknowledgements
Thanks to [lishid](https://github.com/lishid) for their help with making iframes work in Obsidian for a purpose like this. Also thanks to them for *motivating* me to turn Obsidian Keep into a more versatile plugin, which is how Custom Frames was born.
If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!
[![Support me (if you want), via Patreon, Ko-fi or GitHub Sponsors](https://ellpeck.de/res/generalsupport-wide.png)](https://ellpeck.de/support)

View file

@ -1,6 +1,7 @@
import esbuild from "esbuild";
import process from "process";
import builtins from 'builtin-modules';
import {copy} from 'esbuild-plugin-copy';
const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
@ -10,42 +11,50 @@ if you want to view the source, please visit the github repository of this plugi
const prod = (process.argv[2] === 'production');
esbuild.build({
banner: {
js: banner,
},
entryPoints: ['src/main.ts'],
bundle: true,
external: [
'obsidian',
'electron',
'@codemirror/autocomplete',
'@codemirror/closebrackets',
'@codemirror/collab',
'@codemirror/commands',
'@codemirror/comment',
'@codemirror/fold',
'@codemirror/gutter',
'@codemirror/highlight',
'@codemirror/history',
'@codemirror/language',
'@codemirror/lint',
'@codemirror/matchbrackets',
'@codemirror/panel',
'@codemirror/rangeset',
'@codemirror/rectangular-selection',
'@codemirror/search',
'@codemirror/state',
'@codemirror/stream-parser',
'@codemirror/text',
'@codemirror/tooltip',
'@codemirror/view',
...builtins
],
format: 'cjs',
watch: !prod,
target: 'es2016',
logLevel: "info",
sourcemap: prod ? false : 'inline',
treeShaking: true,
outfile: 'main.js',
banner: {
js: banner,
},
entryPoints: ['src/main.ts'],
bundle: true,
external: [
'obsidian',
'electron',
'@codemirror/autocomplete',
'@codemirror/closebrackets',
'@codemirror/collab',
'@codemirror/commands',
'@codemirror/comment',
'@codemirror/fold',
'@codemirror/gutter',
'@codemirror/highlight',
'@codemirror/history',
'@codemirror/language',
'@codemirror/lint',
'@codemirror/matchbrackets',
'@codemirror/panel',
'@codemirror/rangeset',
'@codemirror/rectangular-selection',
'@codemirror/search',
'@codemirror/state',
'@codemirror/stream-parser',
'@codemirror/text',
'@codemirror/tooltip',
'@codemirror/view',
...builtins
],
plugins: [
copy({
assets: [{
from: ["./manifest.json", "./main.js", "./styles.css"],
to: ["./test-vault/.obsidian/plugins/obsidian-custom-frames/."]
}]
}),
],
format: 'cjs',
watch: !prod,
target: 'es2016',
logLevel: "info",
sourcemap: prod ? false : 'inline',
treeShaking: true,
outfile: 'main.js',
}).catch(() => process.exit(1));

View file

@ -1,10 +1,10 @@
{
"id": "obsidian-custom-frames",
"name": "Custom Frames",
"version": "2.4.0",
"minAppVersion": "0.14.5",
"description": "A plugin that turns web apps into panes using iframes with custom styling. Also comes with presets for Google Keep, Todoist and more.",
"author": "Ellpeck",
"authorUrl": "https://ellpeck.de",
"isDesktopOnly": false
}
{
"id": "obsidian-custom-frames",
"name": "Custom Frames",
"version": "2.4.7",
"minAppVersion": "1.2.0",
"description": "A plugin that turns web apps into panes using iframes with custom styling. Also comes with presets for Google Keep, Todoist and more.",
"author": "Ellpeck",
"authorUrl": "https://ellpeck.de",
"isDesktopOnly": false
}

3881
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,23 +1,24 @@
{
"name": "obsidian-custom-frames",
"version": "2.4.0",
"description": "An Obsidian plugin that turns web apps into panes using iframes with custom styling. Also comes with presets for Google Keep, Todoist and more.",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
"author": "Ellpeck",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",
"builtin-modules": "^3.2.0",
"electron": "^13.6.2",
"esbuild": "0.13.12",
"obsidian": "latest",
"tslib": "2.3.1",
"typescript": "4.4.4"
}
"name": "obsidian-custom-frames",
"version": "2.4.7",
"description": "An Obsidian plugin that turns web apps into panes using iframes with custom styling. Also comes with presets for Google Keep, Todoist and more.",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
"author": "Ellpeck",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",
"builtin-modules": "^3.2.0",
"electron": "^20.3.9",
"esbuild": "0.14.0",
"esbuild-plugin-copy": "^1.3.0",
"obsidian": "latest",
"tslib": "2.3.1",
"typescript": "4.4.4"
}
}

View file

@ -1,5 +1,5 @@
import { Platform } from "obsidian";
import { CustomFrameSettings, CustomFramesSettings } from "./settings";
import { CustomFrameSettings, CustomFramesSettings, getId } from "./settings";
export class CustomFrame {
@ -12,31 +12,48 @@ export class CustomFrame {
this.data = data;
}
public create(additionalStyle: string = undefined): any {
create(parent: HTMLElement, additionalStyle: string = undefined, urlSuffix: string = undefined): void {
let style = `padding: ${this.settings.padding}px;`;
if (additionalStyle)
style += additionalStyle;
if (Platform.isDesktopApp) {
this.frame = document.createElement("webview");
if (Platform.isDesktopApp && !this.data.forceIframe) {
let frameDoc = parent.doc;
this.frame = frameDoc.createElement("webview");
parent.appendChild(this.frame);
this.frame.setAttribute("allowpopups", "");
this.frame.addEventListener("dom-ready", () => {
this.frame.setZoomFactor(this.data.zoomLevel);
this.frame.insertCSS(this.data.customCss);
this.frame.executeJavaScript(this.data.customJs)
});
}
else {
this.frame = document.createElement("iframe");
this.frame.setAttribute("sandbox", "allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts allow-top-navigation-by-user-activation");
this.frame.addEventListener("destroyed", () => {
// recreate the webview if it was moved to a new window
if (frameDoc != parent.doc) {
this.frame.detach();
this.create(parent, additionalStyle, urlSuffix);
}
});
} else {
this.frame = parent.doc.createElement("iframe");
parent.appendChild(this.frame);
this.frame.setAttribute("sandbox", "allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts allow-top-navigation-by-user-activation allow-downloads");
this.frame.setAttribute("allow", "encrypted-media; fullscreen; oversized-images; picture-in-picture; sync-xhr; geolocation;");
style += `transform: scale(${this.data.zoomLevel}); transform-origin: 0 0;`;
}
this.frame.addClass("custom-frames-frame");
this.frame.addClass(`custom-frames-${getId(this.data)}`);
this.frame.setAttribute("style", style);
this.frame.setAttribute("src", this.data.url);
return this.frame;
let src = this.data.url;
if (urlSuffix) {
if (!urlSuffix.startsWith("/"))
src += "/";
src += urlSuffix;
}
this.frame.setAttribute("src", src);
}
public refresh(): void {
refresh(): void {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.location.reload();
} else {
@ -44,7 +61,7 @@ export class CustomFrame {
}
}
public return(): void {
return(): void {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.open(this.data.url);
} else {
@ -52,37 +69,41 @@ export class CustomFrame {
}
}
public goBack(): void {
goBack(): void {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.history.back();
}
else {
} else {
this.frame.goBack();
}
}
public goForward(): void {
goForward(): void {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.history.forward();
}
else {
} else {
this.frame.goForward();
}
}
public toggleDevTools(): void {
toggleDevTools(): void {
if (!(this.frame instanceof HTMLIFrameElement)) {
if (!this.frame.isDevToolsOpened()) {
this.frame.openDevTools();
}
else {
} else {
this.frame.closeDevTools();
}
}
}
public copyLink(): void {
let link = this.frame instanceof HTMLIFrameElement ? this.frame.contentWindow.location.href : this.frame.getURL();
navigator.clipboard.writeText(link);
getCurrentUrl(): string {
return this.frame instanceof HTMLIFrameElement ? this.frame.contentWindow.location.href : this.frame.getURL();
}
}
focus(): void {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.focus();
} else {
this.frame.focus();
}
}
}

View file

@ -1,89 +1,97 @@
import { Plugin, Platform } from "obsidian";
import { Plugin, Platform, WorkspaceLeaf } from "obsidian";
import { CustomFrame } from "./frame";
import { CustomFramesSettings, defaultSettings, getIcon } from "./settings";
import { CustomFramesSettings, defaultSettings, getIcon, getId } from "./settings";
import { CustomFramesSettingTab } from "./settings-tab";
import { CustomFrameView } from "./view";
export default class CustomFramesPlugin extends Plugin {
settings: CustomFramesSettings;
settings: CustomFramesSettings;
async onload(): Promise<void> {
await this.loadSettings();
async onload(): Promise<void> {
await this.loadSettings();
for (let frame of this.settings.frames) {
if (!frame.url || !frame.displayName)
continue;
let name = `custom-frames-${frame.displayName.toLowerCase().replace(/\s/g, "-")}`;
if (Platform.isMobileApp && frame.hideOnMobile) {
console.log(`Skipping frame ${name} which is hidden on mobile`);
continue;
}
try {
console.log(`Registering frame ${name} for URL ${frame.url}`);
for (let frame of this.settings.frames) {
if (!frame.url || !frame.displayName)
continue;
let name = `custom-frames-${getId(frame)}`;
if (Platform.isMobileApp && frame.hideOnMobile) {
console.log(`Skipping frame ${name} which is hidden on mobile`);
continue;
}
try {
console.log(`Registering frame ${name} for URL ${frame.url}`);
this.registerView(name, l => new CustomFrameView(l, this.settings, frame, name));
this.addCommand({
id: `open-${name}`,
name: `Open ${frame.displayName}`,
callback: () => this.openLeaf(name, frame.openInCenter),
});
this.registerView(name, l => new CustomFrameView(l, this.settings, frame, name));
this.addCommand({
id: `open-${name}`,
name: `Open ${frame.displayName}`,
callback: () => this.openLeaf(name, frame.openInCenter, false),
});
if (frame.addRibbonIcon)
this.addRibbonIcon(getIcon(frame), `Open ${frame.displayName}`, () => this.openLeaf(name, frame.openInCenter));
} catch {
console.error(`Couldn't register frame ${name}, is there already one with the same name?`);
}
}
if (frame.addRibbonIcon)
this.addRibbonIcon(getIcon(frame), `Open ${frame.displayName}`,
e => this.openLeaf(name, frame.openInCenter, Platform.isMacOS ? e.metaKey : e.ctrlKey));
} catch {
console.error(`Couldn't register frame ${name}, is there already one with the same name?`);
}
}
this.addSettingTab(new CustomFramesSettingTab(this.app, this));
this.addSettingTab(new CustomFramesSettingTab(this.app, this));
this.registerMarkdownCodeBlockProcessor("custom-frames", (s, e) => {
e.empty();
e.addClass("custom-frames-view-file");
this.registerMarkdownCodeBlockProcessor("custom-frames", (s, e) => {
e.empty();
e.addClass("custom-frames-view-file");
let frameMatch = /frame:([^\n]+)/gi.exec(s);
let frameName = frameMatch && frameMatch[1].trim();
if (!frameName) {
e.createSpan({ text: "Couldn't parse frame name" });
return;
}
let data = this.settings.frames.find(f => f.displayName == frameName);
if (!data) {
e.createSpan({ text: `Couldn't find a frame with name ${frameName}` });
return;
}
if (Platform.isMobileApp && data.hideOnMobile) {
e.createSpan({ text: `${frameName} is hidden on mobile` });
return;
}
let frameMatch = /frame:([^\n]+)/gi.exec(s);
let frameName = frameMatch && frameMatch[1].trim();
if (!frameName) {
e.createSpan({ text: "Couldn't parse frame name" });
return;
}
let data = this.settings.frames.find(f => f.displayName == frameName);
if (!data) {
e.createSpan({ text: `Couldn't find a frame with name ${frameName}` });
return;
}
if (Platform.isMobileApp && data.hideOnMobile) {
e.createSpan({ text: `${frameName} is hidden on mobile` });
return;
}
let styleMatch = /style:([^\n]+)/gi.exec(s);
let style = styleMatch && styleMatch[1].trim();
style ||= "height: 600px;";
let styleMatch = /style:([^\n]+)/gi.exec(s);
let style = styleMatch && styleMatch[1].trim();
style ||= "height: 600px;";
let frame = new CustomFrame(this.settings, data);
e.appendChild(frame.create(style));
});
}
let urlSuffixMatch = /urlsuffix:([^\n]+)/gi.exec(s);
let urlSuffix = urlSuffixMatch && urlSuffixMatch[1].trim();
urlSuffix ||= "";
async loadSettings() {
this.settings = Object.assign({}, defaultSettings, await this.loadData());
}
let frame = new CustomFrame(this.settings, data);
frame.create(e, style, urlSuffix);
});
}
async saveSettings() {
await this.saveData(this.settings);
}
async loadSettings() {
this.settings = Object.assign({}, defaultSettings, await this.loadData());
}
private async openLeaf(name: string, center: boolean): Promise<void> {
if (center) {
this.app.workspace.detachLeavesOfType(name);
await this.app.workspace.getUnpinnedLeaf().setViewState({ type: name });
}
else {
if (!this.app.workspace.getLeavesOfType(name).length)
await this.app.workspace.getRightLeaf(false).setViewState({ type: name });
}
this.app.workspace.revealLeaf(this.app.workspace.getLeavesOfType(name)[0]);
}
}
async saveSettings() {
await this.saveData(this.settings);
}
private async openLeaf(name: string, center: boolean, split: boolean): Promise<void> {
let leaf: WorkspaceLeaf;
if (center) {
leaf = this.app.workspace.getLeaf(split);
await leaf.setViewState({ type: name, active: true });
} else {
if (!this.app.workspace.getLeavesOfType(name).length)
await this.app.workspace.getRightLeaf(false).setViewState({ type: name, active: true });
leaf = this.app.workspace.getLeavesOfType(name)[0];
this.app.workspace.revealLeaf(leaf);
}
if (leaf.view instanceof CustomFrameView)
leaf.view.focus();
}
}

View file

@ -14,17 +14,14 @@ export class CustomFramesSettingTab extends PluginSettingTab {
display(): void {
this.containerEl.empty();
this.containerEl.createEl("h2", { text: "Custom Frames Settings" });
this.containerEl.createEl("p", { text: "Note that Obsidian has to be restarted or reloaded for most of these settings to take effect.", cls: "mod-warning" });
this.containerEl.createEl("p", {
text: "Please note that Obsidian has to be restarted or reloaded for most of these settings to take effect.",
cls: "mod-warning"
});
new Setting(this.containerEl)
.setName("Frame Padding")
.setDesc(createFragment(f => {
f.createSpan({ text: "The padding that should be left around the inside of custom frame panes, in pixels." });
f.createEl("br");
f.createEl("em", { text: "Note that decreasing this padding too much will increase the likelihood of a " });
f.createEl("a", { text: "known issue", href: "https://github.com/Ellpeck/ObsidianCustomFrames#%EF%B8%8F-known-issues" });
f.createEl("em", { text: "." });
}))
.setDesc("The padding that should be left around the inside of custom frame panes, in pixels.")
.addText(t => {
t.inputEl.type = "number";
t.setValue(String(this.plugin.settings.padding));
@ -111,6 +108,20 @@ export class CustomFramesSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
});
});
new Setting(content)
.setName("Force iframe")
.setDesc(createFragment(f => {
f.createSpan({ text: "Whether this frame should use iframes on desktop as opposed to Electron webviews." });
f.createEl("br");
f.createEl("em", { text: "Only enable this setting if the frame is causing issues or frequent crashes. This setting causes all Desktop-only settings to be ignored." });
}))
.addToggle(t => {
t.setValue(frame.forceIframe);
t.onChange(async v => {
frame.forceIframe = v;
await this.plugin.saveSettings();
});
});
new Setting(content)
.setName("Page Zoom")
.setDesc("The zoom that this frame's page should be displayed with, as a percentage.")
@ -138,6 +149,22 @@ export class CustomFramesSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
});
});
new Setting(content)
.setName("Additional JavaScript")
.setDesc(createFragment(f => {
f.createSpan({ text: "A snippet of additional JavaScript that should be applied to this frame." });
f.createEl("br");
f.createEl("em", { text: "Note that this is only applied on Desktop." });
}))
.addTextArea(t => {
t.inputEl.rows = 5;
t.inputEl.cols = 50;
t.setValue(frame.customJs);
t.onChange(async v => {
frame.customJs = v;
await this.plugin.saveSettings();
});
});
new ButtonComponent(content)
.setButtonText("Remove Frame")
.onClick(async () => {
@ -148,9 +175,7 @@ export class CustomFramesSettingTab extends PluginSettingTab {
}
this.containerEl.createEl("hr");
let info = this.containerEl.createEl("p", { text: "Create a new frame, either from a preset shipped with the plugin, or a custom one that you can edit yourself. Each frame's pane can be opened using the \"Custom Frames: Open\" command." });
info.createEl("br");
info.createSpan({ text: "Note that Obsidian has to be restarted or reloaded to activate a newly added frame.", cls: "mod-warning" });
this.containerEl.createEl("p", { text: "Create a new frame, either from a preset shipped with the plugin, or a custom one that you can edit yourself. Each frame's pane can be opened using the \"Custom Frames: Open\" command." });
let addDiv = this.containerEl.createDiv();
let dropdown = new DropdownComponent(addDiv);
@ -171,19 +196,31 @@ export class CustomFramesSettingTab extends PluginSettingTab {
addRibbonIcon: false,
openInCenter: false,
zoomLevel: 1,
customCss: ""
forceIframe: false,
customCss: "",
customJs: ""
});
}
else {
} else {
this.plugin.settings.frames.push(presets[option]);
}
await this.plugin.saveSettings();
this.display();
});
let disclaimer = this.containerEl.createEl("p", { cls: "mod-warning" });
disclaimer.createSpan({ text: "Please be advised that, when adding a site as a custom frame, you potentially expose personal information you enter to other plugins you have installed. For more information, see " });
disclaimer.createEl("a", {
text: "this discussion",
href: "https://github.com/Ellpeck/ObsidianCustomFrames/issues/54#issuecomment-1210879685",
cls: "mod-warning"
});
disclaimer.createSpan({ text: "." });
this.containerEl.createEl("hr");
this.containerEl.createEl("p", { text: "If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!" });
this.containerEl.createEl("a", { href: "https://ellpeck.de/support" })
.createEl("img", { attr: { src: "https://ellpeck.de/res/generalsupport.png" }, cls: "custom-frames-support" });
this.containerEl.createEl("a", { href: "https://ellpeck.de/support" }).createEl("img", {
attr: { src: "https://ellpeck.de/res/generalsupport.png" },
cls: "custom-frames-support"
});
}
}
}

View file

@ -11,26 +11,44 @@ export const presets: Record<string, CustomFrameSettings> = {
addRibbonIcon: true,
openInCenter: true,
zoomLevel: 1,
customCss: ""
forceIframe: false,
customCss: "",
customJs: ""
},
"detexify": {
url: "https://detexify.kirelabs.org/classify.html",
displayName: "Detexify",
icon: "type",
hideOnMobile: true,
addRibbonIcon: true,
openInCenter: false,
zoomLevel: .95,
forceIframe: false,
customCss: `/* hide info clutter and ad banner */
#classify--info-area,
.adsbygoogle {
display: none !important
}`,
customJs: ""
},
"calendar": {
url: "https://calendar.google.com/calendar/u/0/r/day",
url: "https://calendar.google.com/calendar",
displayName: "Google Calendar",
icon: "calendar",
hideOnMobile: true,
addRibbonIcon: true,
openInCenter: true,
zoomLevel: 1,
customCss: `/* hide right-side menu, and some buttons */
div.d6McF,
div.pw6cBb,
div.gb_Td.gb_Va.gb_Id,
div.Kk7lMc-QWPxkf-LgbsSe-haAclf,
div.h8Aqhb,
div.gboEAb,
div.dwlvNd {
forceIframe: false,
customCss: `/* hide the menu bar "Calendar" text and remove minimum width */
div[style*="min-width: 238px"] {
min-width: 0 !important;
padding-right: 0 !important;
}
div[style*="min-width: 238px"] span[role*="heading"] {
display: none !important;
}`
}`,
customJs: ""
},
"keep": {
url: "https://keep.google.com",
@ -40,11 +58,18 @@ div.dwlvNd {
addRibbonIcon: false,
openInCenter: false,
zoomLevel: 1,
customCss: `/* hide the menu bar and the "Keep" text */
html > body > div:nth-child(2) > div:nth-child(2) > div:first-child,
html > body > div:first-child > header:first-child > div > div:first-child > div > div:first-child > a:first-child > span {
display: none !important;
}`
forceIframe: false,
customCss: `/* hide the menu bar, the "Keep" text and the Google Apps button */
html > body > div:nth-child(2) > div:nth-child(2) > div:first-child,
html > body > div:first-child > header:first-child > div > div:first-child > div > div:first-child > a:first-child > span,
html > body > div:first-child > header:first-child > div:nth-child(2) > div:first-child > div:first-child,
html > body > div:first-child > header:first-child > div:nth-child(2) > div:nth-child(3) > div:first-child > div:first-child > div:first-child {
display: none !important;
}
html > body > div:first-child > header:first-child > div > div:first-child > div > div:first-child > a:first-child {
cursor: default;
}`,
customJs: ""
},
"todoist": {
url: "https://todoist.com",
@ -54,6 +79,7 @@ html > body > div:first-child > header:first-child > div > div:first-child > div
addRibbonIcon: false,
openInCenter: false,
zoomLevel: 1,
forceIframe: false,
customCss: `/* hide the help, home, search, and productivity overview buttons, create extra space, and prevent toast pop-up from acting weird */
[aria-label="Go to Home view"], #quick_find, [aria-label="Productivity"], [aria-label="Help & Feedback"] {
display: none !important;
@ -70,7 +96,8 @@ html > body > div:first-child > header:first-child > div > div:first-child > div
.undo_toast {
width: 95%;
}`
}`,
customJs: ""
},
"notion": {
url: "https://www.notion.so/",
@ -80,7 +107,9 @@ html > body > div:first-child > header:first-child > div > div:first-child > div
addRibbonIcon: true,
openInCenter: true,
zoomLevel: 1,
customCss: ""
forceIframe: false,
customCss: "",
customJs: ""
},
"twitter": {
url: "https://twitter.com",
@ -90,7 +119,21 @@ html > body > div:first-child > header:first-child > div > div:first-child > div
addRibbonIcon: false,
openInCenter: false,
zoomLevel: 1,
customCss: ""
forceIframe: false,
customCss: "",
customJs: ""
},
"tasks": {
url: "https://tasks.google.com/embed/?origin=https://calendar.google.com&fullWidth=1",
displayName: "Google Tasks",
icon: "list-checks",
hideOnMobile: true,
addRibbonIcon: false,
openInCenter: false,
zoomLevel: 1,
forceIframe: false,
customCss: "",
customJs: ""
}
};
@ -107,9 +150,15 @@ export interface CustomFrameSettings {
addRibbonIcon: boolean;
openInCenter: boolean;
zoomLevel: number;
forceIframe: boolean;
customCss: string;
customJs: string;
}
export function getIcon(settings: CustomFrameSettings) {
return settings.icon ? `lucide-${settings.icon}` : "documents";
}
}
export function getId(settings: CustomFrameSettings) {
return settings.displayName.toLowerCase().replace(/\s/g, "-");
}

View file

@ -16,7 +16,11 @@ export class CustomFrameView extends ItemView {
}, {
name: "Copy link",
icon: "link",
action: v => v.frame.copyLink()
action: v => navigator.clipboard.writeText(v.frame.getCurrentUrl())
}, {
name: "Open in browser",
icon: "globe",
action: v => open(v.frame.getCurrentUrl())
}, {
name: "Refresh",
icon: "refresh-cw",
@ -41,6 +45,7 @@ export class CustomFrameView extends ItemView {
this.data = data;
this.name = name;
this.frame = new CustomFrame(settings, data);
this.navigation = data.openInCenter;
for (let action of CustomFrameView.actions)
this.addAction(action.icon, action.name, () => action.action(this));
@ -49,11 +54,11 @@ export class CustomFrameView extends ItemView {
onload(): void {
this.contentEl.empty();
this.contentEl.addClass("custom-frames-view");
this.contentEl.appendChild(this.frame.create());
this.frame.create(this.contentEl);
}
onHeaderMenu(menu: Menu): void {
super.onHeaderMenu(menu);
onPaneMenu(menu: Menu, source: string): void {
super.onPaneMenu(menu, source);
for (let action of CustomFrameView.actions) {
menu.addItem(i => {
i.setTitle(action.name);
@ -74,10 +79,14 @@ export class CustomFrameView extends ItemView {
getIcon(): string {
return getIcon(this.data);
}
focus(): void {
this.frame.focus();
}
}
interface Action {
name: string;
icon: string;
action: (view: CustomFrameView) => any;
}
}

View file

@ -1,31 +1,31 @@
.custom-frames-view {
padding: 0 !important;
overflow: hidden !important;
padding: 0 !important;
overflow: hidden !important;
}
.custom-frames-view-file {
padding: 0;
overflow: auto;
padding: 0;
overflow: auto;
}
.custom-frames-frame {
width: 100%;
height: 100%;
border: none;
background-color: white;
background-clip: content-box;
width: 100%;
height: 100%;
border: none;
background-color: white;
background-clip: content-box;
}
.custom-frames-add {
margin-left: 10px;
margin-left: 10px;
}
.custom-frames-show {
margin-bottom: 18px;
margin-bottom: 18px;
}
.custom-frames-support {
max-width: 50%;
width: 400px;
height: auto;
max-width: 50%;
width: 400px;
height: auto;
}

3
test-vault/.obsidian/app.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"promptDelete": false
}

3
test-vault/.obsidian/appearance.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"accentColor": ""
}

View file

@ -0,0 +1,3 @@
[
"obsidian-custom-frames"
]

View file

@ -0,0 +1,30 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"outgoing-link": true,
"tag-pane": true,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"starred": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": false,
"sync": false,
"canvas": true,
"bookmarks": true
}

20
test-vault/.obsidian/core-plugins.json vendored Normal file
View file

@ -0,0 +1,20 @@
[
"file-explorer",
"global-search",
"switcher",
"graph",
"backlink",
"canvas",
"outgoing-link",
"tag-pane",
"page-preview",
"daily-notes",
"templates",
"note-composer",
"command-palette",
"editor-status",
"bookmarks",
"outline",
"word-count",
"file-recovery"
]

22
test-vault/.obsidian/graph.json vendored Normal file
View file

@ -0,0 +1,22 @@
{
"collapse-filter": true,
"search": "",
"showTags": false,
"showAttachments": false,
"hideUnresolved": false,
"showOrphans": true,
"collapse-color-groups": true,
"colorGroups": [],
"collapse-display": true,
"showArrow": false,
"textFadeMultiplier": 0,
"nodeSizeMultiplier": 1,
"lineSizeMultiplier": 1,
"collapse-forces": true,
"centerStrength": 0.518713248970312,
"repelStrength": 10,
"linkStrength": 1,
"linkDistance": 250,
"scale": 1,
"close": false
}

11
test-vault/.obsidian/hotkeys.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"app:reload": [
{
"modifiers": [
"Mod",
"Shift"
],
"key": "R"
}
]
}

View file

@ -0,0 +1,3 @@
*
!.gitignore
!data.json

View file

@ -0,0 +1,49 @@
{
"frames": [
{
"url": "https://forum.obsidian.md/",
"displayName": "Obsidian Forum",
"icon": "edit",
"hideOnMobile": true,
"addRibbonIcon": true,
"openInCenter": true,
"zoomLevel": 1,
"forceIframe": false,
"customCss": ""
},
{
"url": "https://keep.google.com",
"displayName": "Google Keep",
"icon": "files",
"hideOnMobile": true,
"addRibbonIcon": false,
"openInCenter": false,
"zoomLevel": 1,
"forceIframe": false,
"customCss": "/* hide the menu bar, the \"Keep\" text and the Google Apps button */\nhtml > body > div:nth-child(2) > div:nth-child(2) > div:first-child, \nhtml > body > div:first-child > header:first-child > div > div:first-child > div > div:first-child > a:first-child > span, \nhtml > body > div:first-child > header:first-child > div:nth-child(2) > div:first-child > div:first-child, \nhtml > body > div:first-child > header:first-child > div:nth-child(2) > div:nth-child(3) > div:first-child > div:first-child > div:first-child { \n\tdisplay: none !important; \n}\nhtml > body > div:first-child > header:first-child > div > div:first-child > div > div:first-child > a:first-child {\n\tcursor: default; \n}"
},
{
"url": "https://calendar.google.com/calendar",
"displayName": "Google Calendar",
"icon": "calendar",
"hideOnMobile": true,
"addRibbonIcon": true,
"openInCenter": true,
"zoomLevel": 1,
"forceIframe": false,
"customCss": "/* hide the menu bar, \"Keep\" text, and logo */\nhtml > body > div:nth-child(2) > div:nth-child(2) > div:first-child[class*=\" \"],\nhtml > body > div:first-child > header:first-child > div > div:first-child > div > div:first-child,\nhtml > body > div:nth-child(2) > div:nth-child(2) > div:first-child > div:first-child {\ndisplay: none !important;\n}"
},
{
"url": "https://scholar.google.com",
"displayName": "Scholar",
"icon": "book",
"hideOnMobile": false,
"addRibbonIcon": true,
"openInCenter": false,
"zoomLevel": 1,
"forceIframe": false,
"customCss": ""
}
],
"padding": 5
}

View file

@ -0,0 +1,6 @@
{
"nodes":[
{"id":"98d23d47aafe2b68","x":-480,"y":-360,"width":795,"height":535,"type":"link","url":"https://calendar.google.com"}
],
"edges":[]
}

0
test-vault/note.md Normal file
View file

View file

@ -1,23 +1,23 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": 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,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
}

View file

@ -1,10 +1,17 @@
{
"2.0.0": "0.13.33",
"2.0.1": "0.13.33",
"2.1.0": "0.13.33",
"2.2.0": "0.14.3",
"2.2.1": "0.14.3",
"2.2.2": "0.14.3",
"2.3.0": "0.14.5",
"2.4.0": "0.14.5"
}
{
"2.0.0": "0.13.33",
"2.0.1": "0.13.33",
"2.1.0": "0.13.33",
"2.2.0": "0.14.3",
"2.2.1": "0.14.3",
"2.2.2": "0.14.3",
"2.3.0": "0.14.5",
"2.4.0": "0.14.5",
"2.4.1": "0.14.5",
"2.4.2": "0.14.5",
"2.4.3": "0.15.5",
"2.4.4": "0.16.0",
"2.4.5": "1.1.0",
"2.4.6": "1.2.0",
"2.4.7": "1.2.0"
}