obsidian-sample-plugin-f-sharp/Program.fs
2024-02-06 22:09:59 +11:00

267 lines
8.7 KiB
Forth

module Program
open Obsidian
open Fable.Core
open Fable.Core.JsInterop
[<Fable.Core.ImportAll(from = "obsidian")>]
let obsidian : Obsidian.IExports = jsNative
type SamplePluginSettings = {
mutable mySetting : string
}
let defaultSettings : SamplePluginSettings = {
mySetting = "default"
}
[<Fable.Core.Import("Command", from = "obsidian")>]
type DefaultCommand() =
do ()
let defaultCommand () =
let mutable mid = ""
let mutable mname = ""
let mutable _cb = None
let mutable _ccb = None
let mutable _hotkeys = None
let mutable _editorCallback = None
let mutable _editorCheckCallback = None
{ new Command with
member this.callback
with get () = _cb
and set v = _cb <- v
member this.checkCallback
with get () = _ccb
and set v = _ccb <- v
member this.editorCallback
with get () = _editorCallback
and set v = _editorCallback <- v
member this.editorCheckCallback
with get () = _editorCheckCallback
and set v = _editorCheckCallback <- v
member this.hotkeys
with get () = _hotkeys
and set v = _hotkeys <- v
member this.icon: string option = None
member this.icon
with set (v: string option): unit = ()
member this.id: string = mid
member this.id
with set (v: string): unit = mid <- v
member this.mobileOnly: bool option = None
member this.mobileOnly
with set (v: bool option): unit = ()
member this.name: string = mname
member this.name
with set (v: string): unit = mname <- v
}
[<Fable.Core.Import("PluginSettingTab", from = "obsidian")>]
type DefaultPluginSettingTab(app, plugin) =
do ()
type SamplePluginSettingTab(app, plugin) as instance =
inherit DefaultPluginSettingTab(app, plugin)
let settings =
instance :> obj :?> PluginSettingTab
let init () =
settings?plugin <- plugin
instance?display <- fun () ->
let containerEl : HTMLElement = settings?containerEl
containerEl.empty ()
let mutable setting : Setting = obsidian.Setting.Create(containerEl)
setting <- setting
.setName(U2.Case1 "Setting #1")
.setDesc(U2.Case1 "It's a secret")
.addText(fun text ->
text
.setPlaceholder("Enter your secret")
.setValue(plugin?settings.mySetting)
.onChange(fun value ->
settings?plugin?settings.mySetting <- value
settings?plugin?saveSettings ()
Some ()
) |> ignore
Some ()
)
do init ()
[<Fable.Core.Import("Modal", from = "obsidian")>]
type DefaultModal(app) =
do ()
type SampleModal(app) as instance =
inherit DefaultModal(app)
let modal =
instance :> obj :?> Modal
let init () =
modal?onOpen <- fun () ->
let contentEl : HTMLElement = modal?contentEl
U2.Case1 "Woah!" |> contentEl.setText
modal?onClose <- fun () ->
let contentEl : HTMLElement = modal?contentEl
contentEl.empty ()
do init ()
// This workaround allows the members to be defined dynamically, without implementing
// the whole interface.
[<Fable.Core.Import("Plugin", from = "obsidian")>]
type DefaultPlugin(app, manifest) =
do ()
// Note: This cannot be named "Plugin", or the import from the Obsidian API will be renamed
type SamplePlugin(app, manifest) as instance =
inherit DefaultPlugin(app, manifest)
// This is what is actually holding the plugin data
// It would be better to have the main plugin implement this interface, but I can't quite
// get it to work right without an absurd amount of boilerplate
let plugin =
instance :> obj :?> Plugin
let init () : unit =
plugin?loadSettings <- (fun _ ->
promise {
let! data = plugin.loadData ()
match data with
| None -> plugin?settings <- defaultSettings
| Some v ->
try
plugin?settings <- v :?> SamplePluginSettings
with
| _ ->
plugin?settings <- defaultSettings
}
)
plugin?saveSettings <- (fun () ->
promise {
do! plugin.saveData (Some(!!plugin?settings))
}
)
// This creates an icon in the left ribbon.
plugin?ribbonIconEl <-
// Called when the user clicks the icon.
let callback (evt : Browser.Types.MouseEvent) : Obsidian.Notice =
obsidian.Notice.Create(U2.Case1 "This is a notice!")
in
plugin?addRibbonIcon("dice", "Sample Plugin", callback)
// Perform additional things with the ribbon
(plugin?ribbonIconEl : Element).addClass (
let array = new ResizeArray<string>(1)
array.Add("my-plugin-ribbon-class")
array
)
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
let statusBarItemEl : Element = plugin?addStatusBarItem ()
"Status Bar Text" |> U2.Case1 |> statusBarItemEl.setText
// This adds a simple command that can be triggered anywhere
plugin?addCommand (
let mutable cmd = defaultCommand ()
cmd.id <- "open-sample-modal-simple"
cmd.name <- "Open sample modal (simple)"
cmd.callback <- Some (fun _ ->
Some((new SampleModal(plugin?app) :> obj :?> Modal).``open`` ())
)
cmd
)
// This adds an editor command that can perform some operation on the current editor instance
plugin?addCommand (
let mutable cmd = defaultCommand ()
cmd.id <- "sample-editor-command"
cmd.name <- "Sample editor command"
cmd.editorCallback <- Some (fun (editor : Editor) (view : MarkdownView) ->
printf "%A" (editor.getSelection ())
let replacement = editor.replaceSelection ("Sample Editor Command")
Some replacement
)
cmd
)
// This adds a complex command that can check whether the current state of the app allows execution of the command
plugin?addCommand (
let mutable cmd = defaultCommand ()
cmd.id <- "open-sample-modal-complex"
cmd.name <- "Open sample modal (complex)"
cmd.checkCallback <- Some (fun checking ->
// Conditions to check
let app : App = plugin?app
let markdownView =
obsidian.MarkdownView :?> Constructor<MarkdownView>
|> app.workspace.getActiveViewOfType
match markdownView with
| Some markdownView ->
// If checking is true, we're simply "checking" if the command can be run.
// If checking is false, then we want to actually perform the operation.
if checking |> not then
Some((new SampleModal(plugin?app) :> obj :?> Modal).``open`` ())
|> ignore
// This command will only show up in Command Palette when the check function returns true
true |> U2.Case1
| None ->
false |> U2.Case1
)
cmd
)
// This adds a settings tab so the user can configure various aspects of the plugin
plugin?addSettingTab (new SamplePluginSettingTab (plugin?app, plugin))
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
// Using this function will automatically remove the event listener when this plugin is disabled.
plugin?registerDomEvent (Browser.Dom.document, "click", fun (evt : Browser.Types.MouseEvent) -> (
printfn "click: %A" evt
))
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
plugin?registerInterval Browser.Dom.window.setInterval (fun () -> printfn "setInterval") (5 * 60 * 1000)
()
let onload: unit -> unit = fun _ -> plugin?settings <- plugin?loadSettings ()
do init ()
do plugin?onload <- onload
// DO NOT REMOVE
// Change the name here to match the type name used above
emitJsStatement "" "module.exports = SamplePlugin";