split up the code into multiple files

This commit is contained in:
Ell 2022-03-29 13:18:25 +02:00
parent ec922e8031
commit 683e245197
6 changed files with 382 additions and 374 deletions

View file

@ -3,7 +3,7 @@ import process from "process";
import builtins from 'builtin-modules' import builtins from 'builtin-modules'
const banner = const banner =
`/* `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin if you want to view the source, please visit the github repository of this plugin
*/ */
@ -15,7 +15,7 @@ esbuild.build({
banner: { banner: {
js: banner, js: banner,
}, },
entryPoints: ['main.ts'], entryPoints: ['src/main.ts'],
bundle: true, bundle: true,
external: [ external: [
'obsidian', 'obsidian',
@ -41,7 +41,8 @@ esbuild.build({
'@codemirror/text', '@codemirror/text',
'@codemirror/tooltip', '@codemirror/tooltip',
'@codemirror/view', '@codemirror/view',
...builtins], ...builtins
],
format: 'cjs', format: 'cjs',
watch: !prod, watch: !prod,
target: 'es2016', target: 'es2016',

371
main.ts
View file

@ -1,371 +0,0 @@
import { App, ButtonComponent, DropdownComponent, ItemView, Plugin, PluginSettingTab, Setting, WorkspaceLeaf, Platform, Menu } from "obsidian";
const defaultSettings: CustomFramesSettings = {
frames: [],
padding: 5
};
const presets: Record<string, CustomFrame> = {
"obsidian": {
url: "https://forum.obsidian.md/",
displayName: "Obsidian Forum",
icon: "edit",
hideOnMobile: true,
minimumWidth: 367,
customCss: ""
},
"keep": {
url: "https://keep.google.com",
displayName: "Google Keep",
icon: "files",
hideOnMobile: true,
minimumWidth: 370,
customCss: `/* hide the menu bar and the "Keep" text */
.PvRhvb-qAWA2, .gb_2d.gb_Zc {
display: none !important;
}`
},
"notion": {
url: "https://www.notion.so/",
displayName: "Notion",
icon: "box",
hideOnMobile: true,
minimumWidth: 400,
customCss: ""
}
};
interface CustomFramesSettings {
frames: CustomFrame[];
padding: number;
}
interface CustomFrame {
url: string;
displayName: string;
icon: string;
hideOnMobile: boolean;
minimumWidth: number;
customCss: string;
}
export default class CustomFramesPlugin extends Plugin {
settings: CustomFramesSettings;
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}`);
this.registerView(name, l => new CustomFrameView(l, this.settings, frame, name));
this.addCommand({
id: `open-${name}`,
name: `Open ${frame.displayName}`,
callback: () => this.openLeaf(name),
});
} catch {
console.error(`Couldn't register frame ${name}, is there already one with the same name?`);
}
}
this.addSettingTab(new CustomFramesSettingTab(this.app, this));
}
async loadSettings() {
this.settings = Object.assign({}, defaultSettings, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
private async openLeaf(name: string): Promise<void> {
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]);
}
}
class CustomFrameView extends ItemView {
private readonly settings: CustomFramesSettings;
private readonly data: CustomFrame;
private readonly name: string;
private frame: HTMLIFrameElement | any;
constructor(leaf: WorkspaceLeaf, settings: CustomFramesSettings, data: CustomFrame, name: string) {
super(leaf);
this.settings = settings;
this.data = data;
this.name = name;
this.addAction("refresh-cw", "Refresh", () => this.refresh());
this.addAction("home", "Return to original page", () => this.return());
this.addAction("arrow-left", "Go back", () => this.goBack());
this.addAction("arrow-right", "Go forward", () => this.goForward());
}
onload(): void {
this.contentEl.empty();
this.contentEl.addClass("custom-frames-view");
if (Platform.isDesktopApp) {
this.frame = document.createElement("webview");
this.frame.setAttribute("allowpopups", "");
this.frame.addEventListener("dom-ready", () => {
this.frame.insertCSS(this.data.customCss);
if (this.data.minimumWidth) {
let parent = this.contentEl.closest<HTMLElement>(".workspace-split.mod-horizontal");
if (parent) {
let minWidth = `${this.data.minimumWidth + 2 * this.settings.padding}px`;
if (parent.style.width < minWidth)
parent.style.width = minWidth;
}
}
});
}
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.setAttribute("allow", "encrypted-media; fullscreen; oversized-images; picture-in-picture; sync-xhr; geolocation;");
}
this.frame.addClass("custom-frames-frame");
this.frame.setAttribute("style", `padding: ${this.settings.padding}px`);
this.frame.setAttribute("src", this.data.url);
this.contentEl.appendChild(this.frame);
}
onHeaderMenu(menu: Menu): void {
super.onHeaderMenu(menu);
menu.addItem(i => {
i.setTitle("Refresh");
i.setIcon("refresh-cw");
i.onClick(() => this.refresh());
});
menu.addItem(i => {
i.setTitle("Return to original page");
i.setIcon("home");
i.onClick(() => this.return());
});
menu.addItem(i => {
i.setTitle("Go back");
i.setIcon("arrow-left");
i.onClick(() => this.goBack());
});
menu.addItem(i => {
i.setTitle("Go forward");
i.setIcon("arrow-right");
i.onClick(() => this.goForward());
});
}
getViewType(): string {
return this.name;
}
getDisplayText(): string {
return this.data.displayName;
}
getIcon(): string {
return this.data.icon ? `lucide-${this.data.icon}` : "documents";
}
private refresh(): void {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.location.reload();
} else {
this.frame.reload();
}
}
private return(): void {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.open(this.data.url);
} else {
this.frame.loadURL(this.data.url);
}
}
private goBack(): void {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.history.back();
}
else {
this.frame.goBack();
}
}
private goForward() {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.history.forward();
}
else {
this.frame.goForward();
}
}
}
class CustomFramesSettingTab extends PluginSettingTab {
plugin: CustomFramesPlugin;
constructor(app: App, plugin: CustomFramesPlugin) {
super(app, plugin);
this.plugin = plugin;
}
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" });
new Setting(this.containerEl)
.setName("Frame Padding")
.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));
t.onChange(async v => {
this.plugin.settings.padding = v.length ? Number(v) : defaultSettings.padding;
await this.plugin.saveSettings();
});
});
for (let frame of this.plugin.settings.frames) {
let heading = this.containerEl.createEl("h3", { text: frame.displayName || "Unnamed Frame" });
new Setting(this.containerEl)
.setName("Display Name")
.setDesc("The display name that this frame should have.")
.addText(t => {
t.setValue(frame.displayName);
t.onChange(async v => {
frame.displayName = v;
heading.setText(frame.displayName || "Unnamed Frame");
await this.plugin.saveSettings();
});
});
new Setting(this.containerEl)
.setName("Icon")
.setDesc(createFragment(f => {
f.createSpan({ text: "The icon that this frame's pane should have. The names of any " });
f.createEl("a", { text: "Lucide icons", href: "https://lucide.dev/" });
f.createSpan({ text: " can be used." });
}))
.addText(t => {
t.setValue(frame.icon);
t.onChange(async v => {
frame.icon = v;
await this.plugin.saveSettings();
});
});
new Setting(this.containerEl)
.setName("URL")
.setDesc("The URL that should be opened in this frame.")
.addText(t => {
t.setValue(frame.url);
t.onChange(async v => {
frame.url = v;
await this.plugin.saveSettings();
});
});
new Setting(this.containerEl)
.setName("Disable on Mobile")
.setDesc("Custom Frames is a lot more restricted on mobile devices and doesn't allow for the same types of content to be displayed. If a frame doesn't work as expected on mobile, it can be disabled.")
.addToggle(t => {
t.setValue(frame.hideOnMobile);
t.onChange(async v => {
frame.hideOnMobile = v;
await this.plugin.saveSettings();
});
});
new Setting(this.containerEl)
.setName("Minimum Width")
.setDesc(createFragment(f => {
f.createSpan({ text: "The width that this frame's pane should be adjusted to automatically if it is lower. Set to 0 to disable." });
f.createEl("br");
f.createEl("em", { text: "Note that this is only applied on Desktop." });
}))
.addText(t => {
t.inputEl.type = "number";
t.setValue(String(frame.minimumWidth));
t.onChange(async v => {
frame.minimumWidth = v.length ? Number(v) : 0;
await this.plugin.saveSettings();
});
});
new Setting(this.containerEl)
.setName("Additional CSS")
.setDesc(createFragment(f => {
f.createSpan({ text: "A snippet of additional CSS 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.customCss);
t.onChange(async v => {
frame.customCss = v;
await this.plugin.saveSettings();
});
});
new ButtonComponent(this.containerEl)
.setButtonText("Remove Frame")
.onClick(async () => {
this.plugin.settings.frames.remove(frame);
await this.plugin.saveSettings();
this.display();
});
}
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" });
let addDiv = this.containerEl.createDiv();
addDiv.addClass("custom-frames-add");
let dropdown = new DropdownComponent(addDiv);
dropdown.addOption("new", "Custom");
for (let key of Object.keys(presets))
dropdown.addOption(key, presets[key].displayName);
new ButtonComponent(addDiv)
.setButtonText("Add Frame")
.onClick(async () => {
let option = dropdown.getValue();
if (option == "new") {
this.plugin.settings.frames.push({
url: "",
displayName: "New Frame",
icon: "",
minimumWidth: 0,
customCss: "",
hideOnMobile: true
});
}
else {
this.plugin.settings.frames.push(presets[option]);
}
await this.plugin.saveSettings();
this.display();
});
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" });
}
}

51
src/main.ts Normal file
View file

@ -0,0 +1,51 @@
import { Plugin, Platform } from "obsidian";
import { CustomFramesSettings, defaultSettings } from "./settings";
import { CustomFramesSettingTab } from "./settings-tab";
import { CustomFrameView } from "./view";
export default class CustomFramesPlugin extends Plugin {
settings: CustomFramesSettings;
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}`);
this.registerView(name, l => new CustomFrameView(l, this.settings, frame, name));
this.addCommand({
id: `open-${name}`,
name: `Open ${frame.displayName}`,
callback: () => this.openLeaf(name),
});
} catch {
console.error(`Couldn't register frame ${name}, is there already one with the same name?`);
}
}
this.addSettingTab(new CustomFramesSettingTab(this.app, this));
}
async loadSettings() {
this.settings = Object.assign({}, defaultSettings, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
private async openLeaf(name: string): Promise<void> {
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]);
}
}

157
src/settings-tab.ts Normal file
View file

@ -0,0 +1,157 @@
import { App, ButtonComponent, DropdownComponent, PluginSettingTab, Setting } from "obsidian";
import { defaultSettings, presets } from "./settings";
import CustomFramesPlugin from "./main";
export class CustomFramesSettingTab extends PluginSettingTab {
plugin: CustomFramesPlugin;
constructor(app: App, plugin: CustomFramesPlugin) {
super(app, plugin);
this.plugin = plugin;
}
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" });
new Setting(this.containerEl)
.setName("Frame Padding")
.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));
t.onChange(async v => {
this.plugin.settings.padding = v.length ? Number(v) : defaultSettings.padding;
await this.plugin.saveSettings();
});
});
for (let frame of this.plugin.settings.frames) {
let heading = this.containerEl.createEl("h3", { text: frame.displayName || "Unnamed Frame" });
new Setting(this.containerEl)
.setName("Display Name")
.setDesc("The display name that this frame should have.")
.addText(t => {
t.setValue(frame.displayName);
t.onChange(async v => {
frame.displayName = v;
heading.setText(frame.displayName || "Unnamed Frame");
await this.plugin.saveSettings();
});
});
new Setting(this.containerEl)
.setName("Icon")
.setDesc(createFragment(f => {
f.createSpan({ text: "The icon that this frame's pane should have. The names of any " });
f.createEl("a", { text: "Lucide icons", href: "https://lucide.dev/" });
f.createSpan({ text: " can be used." });
}))
.addText(t => {
t.setValue(frame.icon);
t.onChange(async v => {
frame.icon = v;
await this.plugin.saveSettings();
});
});
new Setting(this.containerEl)
.setName("URL")
.setDesc("The URL that should be opened in this frame.")
.addText(t => {
t.setValue(frame.url);
t.onChange(async v => {
frame.url = v;
await this.plugin.saveSettings();
});
});
new Setting(this.containerEl)
.setName("Disable on Mobile")
.setDesc("Custom Frames is a lot more restricted on mobile devices and doesn't allow for the same types of content to be displayed. If a frame doesn't work as expected on mobile, it can be disabled.")
.addToggle(t => {
t.setValue(frame.hideOnMobile);
t.onChange(async v => {
frame.hideOnMobile = v;
await this.plugin.saveSettings();
});
});
new Setting(this.containerEl)
.setName("Minimum Width")
.setDesc(createFragment(f => {
f.createSpan({ text: "The width that this frame's pane should be adjusted to automatically if it is lower. Set to 0 to disable." });
f.createEl("br");
f.createEl("em", { text: "Note that this is only applied on Desktop." });
}))
.addText(t => {
t.inputEl.type = "number";
t.setValue(String(frame.minimumWidth));
t.onChange(async v => {
frame.minimumWidth = v.length ? Number(v) : 0;
await this.plugin.saveSettings();
});
});
new Setting(this.containerEl)
.setName("Additional CSS")
.setDesc(createFragment(f => {
f.createSpan({ text: "A snippet of additional CSS 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.customCss);
t.onChange(async v => {
frame.customCss = v;
await this.plugin.saveSettings();
});
});
new ButtonComponent(this.containerEl)
.setButtonText("Remove Frame")
.onClick(async () => {
this.plugin.settings.frames.remove(frame);
await this.plugin.saveSettings();
this.display();
});
}
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" });
let addDiv = this.containerEl.createDiv();
addDiv.addClass("custom-frames-add");
let dropdown = new DropdownComponent(addDiv);
dropdown.addOption("new", "Custom");
for (let key of Object.keys(presets))
dropdown.addOption(key, presets[key].displayName);
new ButtonComponent(addDiv)
.setButtonText("Add Frame")
.onClick(async () => {
let option = dropdown.getValue();
if (option == "new") {
this.plugin.settings.frames.push({
url: "",
displayName: "New Frame",
icon: "",
minimumWidth: 0,
customCss: "",
hideOnMobile: true
});
}
else {
this.plugin.settings.frames.push(presets[option]);
}
await this.plugin.saveSettings();
this.display();
});
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" });
}
}

47
src/settings.ts Normal file
View file

@ -0,0 +1,47 @@
export const defaultSettings: CustomFramesSettings = {
frames: [],
padding: 5
};
export const presets: Record<string, CustomFrame> = {
"obsidian": {
url: "https://forum.obsidian.md/",
displayName: "Obsidian Forum",
icon: "edit",
hideOnMobile: true,
minimumWidth: 367,
customCss: ""
},
"keep": {
url: "https://keep.google.com",
displayName: "Google Keep",
icon: "files",
hideOnMobile: true,
minimumWidth: 370,
customCss: `/* hide the menu bar and the "Keep" text */
.PvRhvb-qAWA2, .gb_2d.gb_Zc {
display: none !important;
}`
},
"notion": {
url: "https://www.notion.so/",
displayName: "Notion",
icon: "box",
hideOnMobile: true,
minimumWidth: 400,
customCss: ""
}
};
export interface CustomFramesSettings {
frames: CustomFrame[];
padding: number;
}
export interface CustomFrame {
url: string;
displayName: string;
icon: string;
hideOnMobile: boolean;
minimumWidth: number;
customCss: string;
}

123
src/view.ts Normal file
View file

@ -0,0 +1,123 @@
import { ItemView, WorkspaceLeaf, Platform, Menu } from "obsidian";
import { CustomFrame, CustomFramesSettings } from "./settings";
export class CustomFrameView extends ItemView {
private readonly settings: CustomFramesSettings;
private readonly data: CustomFrame;
private readonly name: string;
private frame: HTMLIFrameElement | any;
constructor(leaf: WorkspaceLeaf, settings: CustomFramesSettings, data: CustomFrame, name: string) {
super(leaf);
this.settings = settings;
this.data = data;
this.name = name;
this.addAction("refresh-cw", "Refresh", () => this.refresh());
this.addAction("home", "Return to original page", () => this.return());
this.addAction("arrow-left", "Go back", () => this.goBack());
this.addAction("arrow-right", "Go forward", () => this.goForward());
}
onload(): void {
this.contentEl.empty();
this.contentEl.addClass("custom-frames-view");
if (Platform.isDesktopApp) {
this.frame = document.createElement("webview");
this.frame.setAttribute("allowpopups", "");
this.frame.addEventListener("dom-ready", () => {
this.frame.insertCSS(this.data.customCss);
if (this.data.minimumWidth) {
let parent = this.contentEl.closest<HTMLElement>(".workspace-split.mod-horizontal");
if (parent) {
let minWidth = `${this.data.minimumWidth + 2 * this.settings.padding}px`;
if (parent.style.width < minWidth)
parent.style.width = minWidth;
}
}
});
}
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.setAttribute("allow", "encrypted-media; fullscreen; oversized-images; picture-in-picture; sync-xhr; geolocation;");
}
this.frame.addClass("custom-frames-frame");
this.frame.setAttribute("style", `padding: ${this.settings.padding}px`);
this.frame.setAttribute("src", this.data.url);
this.contentEl.appendChild(this.frame);
}
onHeaderMenu(menu: Menu): void {
super.onHeaderMenu(menu);
menu.addItem(i => {
i.setTitle("Refresh");
i.setIcon("refresh-cw");
i.onClick(() => this.refresh());
});
menu.addItem(i => {
i.setTitle("Return to original page");
i.setIcon("home");
i.onClick(() => this.return());
});
menu.addItem(i => {
i.setTitle("Go back");
i.setIcon("arrow-left");
i.onClick(() => this.goBack());
});
menu.addItem(i => {
i.setTitle("Go forward");
i.setIcon("arrow-right");
i.onClick(() => this.goForward());
});
}
getViewType(): string {
return this.name;
}
getDisplayText(): string {
return this.data.displayName;
}
getIcon(): string {
return this.data.icon ? `lucide-${this.data.icon}` : "documents";
}
private refresh(): void {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.location.reload();
} else {
this.frame.reload();
}
}
private return(): void {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.open(this.data.url);
} else {
this.frame.loadURL(this.data.url);
}
}
private goBack(): void {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.history.back();
}
else {
this.frame.goBack();
}
}
private goForward() {
if (this.frame instanceof HTMLIFrameElement) {
this.frame.contentWindow.history.forward();
}
else {
this.frame.goForward();
}
}
}