2022-03-26 15:57:09 +01:00
import { App , ButtonComponent , DropdownComponent , ItemView , Plugin , PluginSettingTab , Setting , WorkspaceLeaf , Platform , Menu } from "obsidian" ;
2022-03-19 20:21:16 +01:00
2022-03-22 12:03:48 +01:00
const defaultSettings : CustomFramesSettings = {
2022-03-22 13:17:10 +01:00
frames : [ ] ,
padding : 5
} ;
const presets : Record < string , CustomFrame > = {
2022-03-26 16:11:30 +01:00
"obsidian" : {
url : "https://forum.obsidian.md/" ,
displayName : "Obsidian Forum" ,
icon : "edit" ,
hideOnMobile : true ,
minimumWidth : 367 ,
customCss : ""
} ,
2022-03-22 13:17:10 +01:00
"keep" : {
url : "https://keep.google.com" ,
displayName : "Google Keep" ,
2022-03-23 15:26:30 +01:00
icon : "files" ,
2022-03-26 15:35:09 +01:00
hideOnMobile : true ,
2022-03-22 13:17:10 +01:00
minimumWidth : 370 ,
customCss : ` /* hide the menu bar and the "Keep" text */
2022-03-22 01:06:16 +01:00
. PvRhvb - qAWA2 , . gb_2d . gb_Zc {
2022-03-20 13:55:19 +01:00
display : none ! important ;
} `
2022-03-26 15:35:09 +01:00
} ,
"notion" : {
url : "https://www.notion.so/" ,
displayName : "Notion" ,
icon : "box" ,
hideOnMobile : true ,
minimumWidth : 400 ,
customCss : ""
2022-03-22 13:17:10 +01:00
}
2022-03-22 00:51:28 +01:00
} ;
2022-03-20 13:55:19 +01:00
2022-03-22 12:03:48 +01:00
interface CustomFramesSettings {
2022-03-22 13:17:10 +01:00
frames : CustomFrame [ ] ;
2022-03-20 13:55:19 +01:00
padding : number ;
2022-03-22 13:17:10 +01:00
}
interface CustomFrame {
url : string ;
displayName : string ;
2022-03-23 15:26:30 +01:00
icon : string ;
2022-03-26 15:23:51 +01:00
hideOnMobile : boolean ;
2022-03-22 13:17:10 +01:00
minimumWidth : number ;
customCss : string ;
2022-03-20 13:55:19 +01:00
}
2022-03-19 20:21:16 +01:00
2022-03-22 12:03:48 +01:00
export default class CustomFramesPlugin extends Plugin {
2022-03-20 13:55:19 +01:00
2022-03-22 12:03:48 +01:00
settings : CustomFramesSettings ;
2022-03-19 20:21:16 +01:00
2022-03-20 00:20:09 +01:00
async onload ( ) : Promise < void > {
2022-03-20 13:55:19 +01:00
await this . loadSettings ( ) ;
2022-03-19 20:21:16 +01:00
2022-03-22 13:17:10 +01:00
for ( let frame of this . settings . frames ) {
if ( ! frame . url || ! frame . displayName )
continue ;
let name = ` custom-frames- ${ frame . displayName . toLowerCase ( ) . replace ( /\s/g , "-" ) } ` ;
2022-03-26 15:23:51 +01:00
if ( Platform . isMobileApp && frame . hideOnMobile ) {
console . log ( ` Skipping frame ${ name } which is hidden on mobile ` ) ;
continue ;
}
2022-03-22 13:17:10 +01:00
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 } ` ,
2022-03-22 13:22:58 +01:00
callback : ( ) = > this . openLeaf ( name ) ,
2022-03-22 13:17:10 +01:00
} ) ;
} catch {
console . error ( ` Couldn't register frame ${ name } , is there already one with the same name? ` ) ;
}
}
2022-03-19 20:21:16 +01:00
2022-03-22 13:17:10 +01:00
this . addSettingTab ( new CustomFramesSettingTab ( this . app , this ) ) ;
2022-03-20 00:20:09 +01:00
}
2022-03-20 13:55:19 +01:00
async loadSettings() {
this . settings = Object . assign ( { } , defaultSettings , await this . loadData ( ) ) ;
}
async saveSettings() {
await this . saveData ( this . settings ) ;
}
2022-03-22 13:22:58 +01:00
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 ] ) ;
}
2022-03-20 00:20:09 +01:00
}
2022-03-19 20:21:16 +01:00
2022-03-22 12:03:48 +01:00
class CustomFrameView extends ItemView {
2022-03-19 20:21:16 +01:00
2022-03-26 15:57:09 +01:00
private readonly settings : CustomFramesSettings ;
private readonly data : CustomFrame ;
private readonly name : string ;
private frame : HTMLIFrameElement | any ;
2022-03-19 20:21:16 +01:00
2022-03-26 15:57:09 +01:00
constructor ( leaf : WorkspaceLeaf , settings : CustomFramesSettings , data : CustomFrame , name : string ) {
2022-03-20 13:55:19 +01:00
super ( leaf ) ;
this . settings = settings ;
2022-03-26 15:57:09 +01:00
this . data = data ;
2022-03-22 13:17:10 +01:00
this . name = name ;
2022-03-26 15:57:09 +01:00
this . addAction ( "refresh-cw" , "Refresh" , ( ) = > this . refresh ( ) ) ;
this . addAction ( "home" , "Return to original page" , ( ) = > this . return ( ) ) ;
2022-03-29 13:03:19 +02:00
this . addAction ( "arrow-left" , "Go back" , ( ) = > this . goBack ( ) ) ;
this . addAction ( "arrow-right" , "Go forward" , ( ) = > this . goForward ( ) ) ;
2022-03-20 13:55:19 +01:00
}
2022-03-22 00:51:28 +01:00
onload ( ) : void {
this . contentEl . empty ( ) ;
2022-03-22 13:17:10 +01:00
this . contentEl . addClass ( "custom-frames-view" ) ;
2022-03-22 00:51:28 +01:00
2022-03-26 15:23:51 +01:00
if ( Platform . isDesktopApp ) {
2022-03-26 15:57:09 +01:00
this . frame = document . createElement ( "webview" ) ;
this . frame . setAttribute ( "allowpopups" , "" ) ;
this . frame . addEventListener ( "dom-ready" , ( ) = > {
this . frame . insertCSS ( this . data . customCss ) ;
2022-03-26 15:23:51 +01:00
2022-03-26 15:57:09 +01:00
if ( this . data . minimumWidth ) {
2022-03-26 15:23:51 +01:00
let parent = this . contentEl . closest < HTMLElement > ( ".workspace-split.mod-horizontal" ) ;
if ( parent ) {
2022-03-26 15:57:09 +01:00
let minWidth = ` ${ this . data . minimumWidth + 2 * this . settings . padding } px ` ;
2022-03-26 15:23:51 +01:00
if ( parent . style . width < minWidth )
parent . style . width = minWidth ;
}
}
} ) ;
}
else {
2022-03-26 15:57:09 +01:00
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;" ) ;
2022-03-26 15:23:51 +01:00
}
2022-03-26 15:57:09 +01:00
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 ( ) ) ;
} ) ;
2022-03-29 13:03:19 +02:00
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 ( ) ) ;
} ) ;
2022-03-20 00:20:09 +01:00
}
2022-03-19 20:21:16 +01:00
2022-03-22 00:51:28 +01:00
getViewType ( ) : string {
2022-03-22 13:17:10 +01:00
return this . name ;
2022-03-22 00:51:28 +01:00
}
getDisplayText ( ) : string {
2022-03-26 15:57:09 +01:00
return this . data . displayName ;
2022-03-20 00:20:09 +01:00
}
2022-03-19 20:21:16 +01:00
2022-03-22 00:51:28 +01:00
getIcon ( ) : string {
2022-03-26 15:57:09 +01:00
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 ) ;
}
2022-03-19 20:21:16 +01:00
}
2022-03-29 13:03:19 +02:00
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 ( ) ;
}
}
2022-03-20 13:55:19 +01:00
}
2022-03-22 12:03:48 +01:00
class CustomFramesSettingTab extends PluginSettingTab {
2022-03-20 13:55:19 +01:00
2022-03-22 12:03:48 +01:00
plugin : CustomFramesPlugin ;
2022-03-20 13:55:19 +01:00
2022-03-22 12:03:48 +01:00
constructor ( app : App , plugin : CustomFramesPlugin ) {
2022-03-20 13:55:19 +01:00
super ( app , plugin ) ;
2022-03-20 13:57:02 +01:00
this . plugin = plugin ;
2022-03-20 13:55:19 +01:00
}
display ( ) : void {
this . containerEl . empty ( ) ;
2022-03-22 13:17:10 +01:00
this . containerEl . createEl ( "h2" , { text : "Custom Frames Settings" } ) ;
2022-03-23 15:26:30 +01:00
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" } ) ;
2022-03-20 13:55:19 +01:00
new Setting ( this . containerEl )
2022-03-22 13:17:10 +01:00
. setName ( "Frame Padding" )
2022-03-23 15:26:30 +01:00
. setDesc ( "The padding that should be left around the inside of custom frame panes, in pixels." )
2022-03-20 19:32:21 +01:00
. addText ( t = > {
t . inputEl . type = "number" ;
t . setValue ( String ( this . plugin . settings . padding ) ) ;
t . onChange ( async v = > {
2022-03-21 19:39:03 +01:00
this . plugin . settings . padding = v . length ? Number ( v ) : defaultSettings . padding ;
2022-03-20 19:32:21 +01:00
await this . plugin . saveSettings ( ) ;
} ) ;
} ) ;
2022-03-22 13:17:10 +01:00
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 ( ) ;
} ) ;
} ) ;
2022-03-23 15:26:30 +01:00
new Setting ( this . containerEl )
. setName ( "Icon" )
. setDesc ( createFragment ( f = > {
2022-03-26 15:23:51 +01:00
f . createSpan ( { text : "The icon that this frame's pane should have. The names of any " } ) ;
2022-03-23 15:26:30 +01:00
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 ( ) ;
} ) ;
} ) ;
2022-03-22 13:17:10 +01:00
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 ( ) ;
} ) ;
} ) ;
2022-03-26 15:23:51 +01:00
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 ( ) ;
} ) ;
} ) ;
2022-03-22 13:17:10 +01:00
new Setting ( this . containerEl )
. setName ( "Minimum Width" )
2022-03-26 15:23:51 +01:00
. 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." } ) ;
} ) )
2022-03-22 13:17:10 +01:00
. 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" )
2022-03-26 15:23:51 +01:00
. 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." } ) ;
} ) )
2022-03-22 13:17:10 +01:00
. addTextArea ( t = > {
2022-03-22 13:18:53 +01:00
t . inputEl . rows = 5 ;
2022-03-22 13:17:10 +01:00
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 ) ;
2022-03-20 13:57:02 +01:00
await this . plugin . saveSettings ( ) ;
2022-03-22 13:17:10 +01:00
this . display ( ) ;
2022-03-20 13:57:02 +01:00
} ) ;
2022-03-22 13:17:10 +01:00
}
this . containerEl . createEl ( "hr" ) ;
2022-03-23 16:13:11 +01:00
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." } ) ;
2022-03-23 15:26:30 +01:00
info . createEl ( "br" ) ;
info . createSpan ( { text : "Note that Obsidian has to be restarted or reloaded to activate a newly added frame." , cls : "mod-warning" } ) ;
2022-03-22 13:17:10 +01:00
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 ) )
2022-03-23 16:09:31 +01:00
dropdown . addOption ( key , presets [ key ] . displayName ) ;
2022-03-22 13:17:10 +01:00
new ButtonComponent ( addDiv )
. setButtonText ( "Add Frame" )
. onClick ( async ( ) = > {
let option = dropdown . getValue ( ) ;
if ( option == "new" ) {
this . plugin . settings . frames . push ( {
url : "" ,
2022-03-26 15:23:51 +01:00
displayName : "New Frame" ,
2022-03-23 15:26:30 +01:00
icon : "" ,
2022-03-22 13:17:10 +01:00
minimumWidth : 0 ,
2022-03-26 15:23:51 +01:00
customCss : "" ,
2022-03-26 15:35:09 +01:00
hideOnMobile : true
2022-03-22 13:17:10 +01:00
} ) ;
}
else {
this . plugin . settings . frames . push ( presets [ option ] ) ;
}
await this . plugin . saveSettings ( ) ;
this . display ( ) ;
2022-03-20 13:55:19 +01:00
} ) ;
2022-03-23 18:06:12 +01:00
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" } ) ;
2022-03-20 13:55:19 +01:00
}
2022-03-20 00:20:09 +01:00
}