initial commit
This commit is contained in:
commit
fe8a832d74
12
.config/dotnet-tools.json
Normal file
12
.config/dotnet-tools.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"fable": {
|
||||
"version": "4.1.4",
|
||||
"commands": [
|
||||
"fable"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
bin/
|
||||
obj/
|
||||
out/
|
||||
fable_modules/
|
||||
|
||||
*.fs.js
|
1700
Obsidian.fs
Normal file
1700
Obsidian.fs
Normal file
File diff suppressed because it is too large
Load Diff
266
Program.fs
Normal file
266
Program.fs
Normal file
@ -0,0 +1,266 @@
|
||||
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";
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Obsidian Sample Plugin F\#
|
||||
|
||||
This is a recreation of the [official Obsidian sample plugin](https://github.com/obsidianmd/obsidian-sample-plugin) in F\# using Fable as a proof of concept.
|
11
justfile
Executable file
11
justfile
Executable file
@ -0,0 +1,11 @@
|
||||
build:
|
||||
dotnet fable --lang javascript --noCache
|
||||
esbuild ./Program.fs.js --bundle --external:obsidian --outfile=./out/main.js --format=cjs
|
||||
cp -a ./meta/. ./out/
|
||||
|
||||
clean:
|
||||
rm -rf ./fable_modules/ ./bin/ ./obj/ ./out/ *.fs.js
|
||||
|
||||
|
||||
|
||||
|
10
meta/manifest.json
Normal file
10
meta/manifest.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": "obsidian-sample-plugin-fs",
|
||||
"name": "FSharp Sample Plugin",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Sample project to demonstrate how to use F# and Fable to create an Obsidian plugin.",
|
||||
"author": "Aaron Manning",
|
||||
"authorUrl": "https://aaronmanning.net",
|
||||
"isDesktopOnly": false
|
||||
}
|
3
meta/versions.json
Normal file
3
meta/versions.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"1.0.0": "0.15.0"
|
||||
}
|
22
obsidian-sample-plugin-fs.fsproj
Normal file
22
obsidian-sample-plugin-fs.fsproj
Normal file
@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<RootNamespace>obsidian_sample_plugin_fs</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Obsidian.fs" />
|
||||
<Compile Include="Program.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fable.Browser.Css" Version="2.3.0" />
|
||||
<PackageReference Include="Fable.Browser.Dom" Version="2.14.0" />
|
||||
<PackageReference Include="Fable.Browser.XMLHttpRequest" Version="1.3.0" />
|
||||
<PackageReference Include="Fable.Core" Version="4.0.0" />
|
||||
<PackageReference Include="Fable.Promise" Version="3.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Loading…
Reference in New Issue
Block a user