├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TODO.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts ├── src ├── main │ ├── kotlin │ │ ├── KotlinDialog.kt │ │ ├── KotlinPlugin.kt │ │ ├── KotlinPluginSettings.kt │ │ ├── KotlinPluginSettingsTab.kt │ │ └── obsidian │ │ │ ├── ES5.kt │ │ │ ├── ObsidianApi.kt │ │ │ └── builders │ │ │ └── CommandBuilder.kt │ └── resources │ │ ├── manifest.json │ │ └── styles.css └── test │ └── kotlin │ └── KotlinPluginTest.kt ├── versions.json └── webpack.config.d └── externals.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | .gradle/ 26 | .idea/ 27 | build/ 28 | src/main/kotlin/externals/ 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | I am definitely willing to accept help in implementing missing features of the API. Also, if anyone has suggestions for improvements I would love to hear them (better patterns, structure, etc). 3 | 4 | ## Development 5 | Developing this is just like developing an Obsidian Plugin. The Gradle task `browserWebpack` is the main task to create a build suitable for use in Obsidian, and is the that is used most often. 6 | 7 | I do have the default KotlinJS task `browserTest` disabled as that starts a web browser for testing, which is not useful for Obsidian Plugins. This does seem to fully disable running any tests, which is something that I plan on looking into in the short term. 8 | 9 | ### My Workflow 10 | 1. `./gradlew browserWebpack` will create the distribution files for use in Obsidian 11 | 2. symlink the `build/distributions` to the Obsidian plugins folder. 12 | - e.g. `ln -s ~/projects/obsidian-kotlin-plugin/build/distrubitions /vault_location/.obsidian/plugins/kotlin-plugin` 13 | 3. Safe Mode needs to be off in Obsidian 14 | 4. Refresh `Installed plugins` under `Community plugins` 15 | 5. Enable `Kotlin Sample Plugin` 16 | 17 | When creating a new build, disable and re-enable the plugin under `Installed plugins` to pull in any changes. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mike Osterlie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # obsidian-kotlin-plugin 2 | Base Obsidian plugin in Kotlin 3 | 4 | ## Usage 5 | Right now the plugin only outputs "KotlinPlugin onload()" and "KotlinPlugin onunload()" to the console if it is loaded and unloaded successfully, respectively. 6 | 7 | **NOTE:** I plan on making this repo a Template, but leaving that off for now as the bindings will be improved in the short-term. I will try to make breaking changes minimal so people can pull changes into their forked repository without too much trouble. 8 | 9 | To use this for developing a plugin: 10 | 1. Fork the repo 11 | 2. Rename `KotlinPlugin` to something more descriptive 12 | 3. Rename `KotlinPluginTest` to match the plugin name 13 | 4. Rename `KotlinPluginSettings` to match the plugin name 14 | 5. Rename `KotlinPluginSettingsTab` to match the plugin name 15 | 6. Edit `src/main/resources/manifest.json` to match your plugin information 16 | 7. Edit `build.gradle.kts` to change the group name 17 | 8. Edit `settings.gradle.kts` to match the rootProject name 18 | 19 | In this example plugin, everything that needs to change is in the root package, while everything under the `obsidian` package is for the API and should not need to change when developing a plugin (unless implementing something that is currently missing). 20 | 21 | ### API 22 | `ObsidianApi` is the main API file, containing many of the classes from the Obsidian TypeScript definition file. `ES5` contains a few of the DOM-related API needed for the current implementation of the Obsidian API. 23 | 24 | The `obsidian.builders` package contains helper classes for building up the Obsidian API data. This is mainly to help with building data that the API tags as `interface`, since those cannot be instantiated directly and Kotlin isn't as free as Javascript for parameters. Right now the only builder is `CommandBuilder` for constructing a `Command` object, but any further builders needed will follow the same pattern. 25 | 26 | ## Obsidian API Bindings 27 | The Kotlin bindings for Obsidian were created by using [Dukat](https://github.com/Kotlin/dukat) on the `obsidian.d.ts` file and copying over enough to get the main `Plugin` and `App` classes to compile with much of the functionality in place. There were errors in the CodeMirror and DOM files so none of the CodeMirror items are available in the API for now, and only enough of the DOM items were copied over to fix any compilation errors in the API itself. 28 | 29 | CodeMirror had two errors, only one of which was something not immediately fixable. Once the plugin itself is expanded a bit more I plan on revisiting the CodeMirror bindings and pulling those in as well. 30 | 31 | I was hoping that Dukat could be fully integrated into the build but there were a LOT of errors in the DOM bindings, so at least for now the bindings are a manual process. 32 | 33 | Thanks to the answer on [Stack Overflow](https://stackoverflow.com/questions/68293035/how-to-convert-javascript-exported-class-to-kotlin-js) for getting me through the last issues I was having. 34 | 35 | ## Contributing 36 | Please see the [contributing doc](CONTRIBUTING.md) for more information on helping out if you would like to. 37 | 38 | ## Onegoing Development 39 | There is a Project setup in GitHub to track issues and what is being worked on. That is available at the [Development Board](https://github.com/darthmachina/obsidian-kotlin-plugin/projects/1) 40 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO Items 2 | - [x] Create an example Settings page 3 | - [x] Create an example Command 4 | - [ ] Get tests working 5 | - `browserTest` is excluded which seems to exclude `test` as well 6 | - [ ] Make repository a Template 7 | - [x] Can `index.html` be removed? 8 | - [ ] Replace basic settings check with version check 9 | 10 | ## Obsidian API Bindings 11 | - [ ] Pull in the full CodeMirror bindings generated by Dukat 12 | - [ ] Need to fix the missing `Partial` class that is referenced 13 | - [ ] Integrate Dukat into the build process 14 | - Uses the `generateExternals = true` directive on the `implementation(npm(...))` dependency which tags the Dukat output as generated source. 15 | - `lib.dom.kt` currently produces a ton of errors (haven't investigated at all), so this is a wishlist item for now 16 | - [ ] Turn Obsidian bindings into a library on its own? 17 | - [ ] Fix `DevTools failed to load SourceMap: Could not parse content for app://obsidian.md/main.js.map: Unexpected end of JSON input` in console 18 | - [ ] Figure out how to use `app.workspace.getActiveViewOfType` 19 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("js") version "1.6.0" 3 | } 4 | 5 | group = "io.github.darthmachina" 6 | version = "0.2.0-SNAPSHOT" 7 | 8 | repositories { 9 | mavenCentral() 10 | maven { url = uri("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") } 11 | } 12 | 13 | dependencies { 14 | implementation(npm("obsidian", "0.12.17")) 15 | 16 | val kotlinxHtmlVersion = "0.7.3" 17 | implementation("org.jetbrains.kotlinx:kotlinx-html-js:$kotlinxHtmlVersion") 18 | implementation("org.jetbrains.kotlinx:kotlinx-html:$kotlinxHtmlVersion") 19 | 20 | testImplementation("org.jetbrains.kotlin:kotlin-test:1.6.0") 21 | } 22 | 23 | kotlin { 24 | js(IR) { 25 | binaries.executable() 26 | browser { 27 | useCommonJs() 28 | commonWebpackConfig { 29 | cssSupport.enabled = true 30 | } 31 | webpackTask { 32 | output.libraryTarget = "commonjs" 33 | output.library = null 34 | outputFileName = "main.js" 35 | } 36 | } 37 | } 38 | } 39 | 40 | // OptIn to JsExport annotation 41 | tasks.withType().configureEach { 42 | kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" 43 | } 44 | 45 | project.gradle.startParameter.excludedTaskNames.add("browserTest") 46 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin.js.generate.executable.default=false 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = "obsidian-kotlin-plugin" 3 | 4 | -------------------------------------------------------------------------------- /src/main/kotlin/KotlinDialog.kt: -------------------------------------------------------------------------------- 1 | class KotlinDialog(override var app: App) : Modal(app) { 2 | override fun onOpen() { 3 | contentEl.innerText = "Woah!" 4 | } 5 | 6 | override fun onClose() { 7 | while(contentEl.firstChild != null) { 8 | contentEl.lastChild?.let { contentEl.removeChild(it) } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/KotlinPlugin.kt: -------------------------------------------------------------------------------- 1 | import obsidian.builders.CommandBuilder 2 | 3 | @OptIn(ExperimentalJsExport::class) 4 | @JsExport 5 | @JsName("default") 6 | class KotlinPlugin(app: App, manifest: PluginManifest) : Plugin(app, manifest) { 7 | var settings : KotlinPluginSettings = KotlinPluginSettings.default() 8 | 9 | override fun onload() { 10 | loadSettings() 11 | 12 | // This adds a simple command that can be triggered anywhere 13 | addCommand(CommandBuilder( 14 | "open-kotlin-modal-simple", 15 | "Open Kotlin modal (simple)") 16 | .callback { 17 | KotlinDialog(app).open() 18 | } 19 | .build()) 20 | 21 | // This adds an editor command that can perform some operation on the current editor instance 22 | addCommand(CommandBuilder( 23 | "kotlin-editor-command", 24 | "Kotlin editor command") 25 | .editorCallback { editor, _ -> 26 | console.log("Editor selection", editor.getSelection()) 27 | editor.replaceSelection("Sample Editor Command") 28 | } 29 | .build()) 30 | 31 | // This adds a complex command that can check whether the current state of the app allows execution of the command 32 | // TODO Figure out how to use getActiveViewOfType 33 | 34 | // Add Settings tab 35 | addSettingTab(KotlinPluginSettingsTab(app, this)) 36 | console.log("KotlinPlugin onload()") 37 | } 38 | 39 | override fun onunload() { 40 | console.log("KotlinPlugin onunload()") 41 | } 42 | 43 | private fun loadSettings() { 44 | // TODO: implement exmaple of versioned settings 45 | loadData().then {result -> 46 | val loadedSettings = KotlinPluginSettings.fromJson(result as String) 47 | console.log("loadedSettings: ", loadedSettings) 48 | // TODO Replace with a version check 49 | if (loadedSettings.mySetting != "") { 50 | console.log("Saving loaded settings") 51 | settings = loadedSettings 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/kotlin/KotlinPluginSettings.kt: -------------------------------------------------------------------------------- 1 | @OptIn(ExperimentalJsExport::class) 2 | @JsExport 3 | data class KotlinPluginSettings( 4 | var mySetting: String 5 | ) { 6 | companion object { 7 | fun default() : KotlinPluginSettings{ 8 | return KotlinPluginSettings("default") 9 | } 10 | 11 | fun toJson(settings: KotlinPluginSettings) : String { 12 | return JSON.stringify(settings) 13 | } 14 | 15 | fun fromJson(json: String) : KotlinPluginSettings { 16 | return JSON.parse(json) 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/KotlinPluginSettingsTab.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.dom.clear 2 | import kotlinx.html.dom.append 3 | import kotlinx.html.js.h2 4 | import org.w3c.dom.HTMLElement 5 | 6 | @OptIn(ExperimentalJsExport::class) 7 | @JsExport 8 | class KotlinPluginSettingsTab(override var app: App, var plugin: KotlinPlugin) : PluginSettingTab(app, plugin) { 9 | override fun display() { 10 | while(containerEl.firstChild != null) { 11 | containerEl.lastChild?.let { containerEl.removeChild(it) } 12 | } 13 | 14 | containerEl.append.h2 { +"Settings for my awesome plugin." } 15 | createSampleSetting(containerEl) 16 | } 17 | 18 | private fun createSampleSetting(containerEl: HTMLElement) : Setting { 19 | return Setting(containerEl) 20 | .setName("Setting #1") 21 | .setDesc("It's a secret") 22 | .addText { text -> 23 | text.setPlaceholder("Enter your secret") 24 | .setValue(plugin.settings.mySetting) 25 | .onChange { value -> 26 | console.log("Secret: $value") 27 | plugin.settings.mySetting = value 28 | saveSettings() 29 | } 30 | } 31 | } 32 | 33 | private fun saveSettings() { 34 | console.log("saveSettings: ", KotlinPluginSettings.toJson(plugin.settings)) 35 | plugin.saveData(KotlinPluginSettings.toJson(plugin.settings)) 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/obsidian/ES5.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused", "UNUSED_TYPEALIAS_PARAMETER", "INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS",) 2 | 3 | package obsidian 4 | 5 | import org.w3c.dom.DataTransfer 6 | import org.w3c.dom.events.Event 7 | import org.w3c.dom.events.MouseEvent 8 | 9 | typealias Record = Any 10 | 11 | external interface ClipboardEvent : Event { 12 | var clipboardData: DataTransfer? 13 | } 14 | 15 | external interface PointerEvent : MouseEvent { 16 | var height: Number 17 | var isPrimary: Boolean 18 | var pointerId: Number 19 | var pointerType: String 20 | var pressure: Number 21 | var tangentialPressure: Number 22 | var tiltX: Number 23 | var tiltY: Number 24 | var twist: Number 25 | var width: Number 26 | } 27 | -------------------------------------------------------------------------------- /src/main/kotlin/obsidian/ObsidianApi.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused", "INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") 2 | @file:JsModule("obsidian") 3 | @file:JsNonModule 4 | 5 | import obsidian.ClipboardEvent 6 | import kotlin.js.Promise 7 | import obsidian.Record 8 | import org.khronos.webgl.ArrayBuffer 9 | import org.w3c.dom.* 10 | import org.w3c.dom.events.KeyboardEvent 11 | import org.w3c.dom.events.MouseEvent 12 | 13 | open external class Component { 14 | open fun load() 15 | open fun onload() 16 | open fun unload() 17 | open fun onunload() 18 | open fun addChild(component: T): T 19 | open fun removeChild(component: T): T 20 | open fun register(cb: () -> Any) 21 | open fun registerEvent(eventRef: EventRef) 22 | open fun registerDomEvent(el: Window, type: String, callback: (self: HTMLElement, ev: Any) -> Any) 23 | open fun registerDomEvent(el: Document, type: String, callback: (self: HTMLElement, ev: Any) -> Any) 24 | open fun registerDomEvent(el: HTMLElement, type: K, callback: (self: HTMLElement, ev: Any) -> Any) 25 | open fun registerScopeEvent(keyHandler: KeymapEventHandler) 26 | open fun registerInterval(id: Number) 27 | } 28 | 29 | open external class Plugin(app: App, manifest: PluginManifest) : Component { 30 | open var app: App 31 | open var manifest: PluginManifest 32 | open fun addRibbonIcon(icon: String, title: String, callback: (evt: MouseEvent) -> Any): HTMLElement 33 | open fun addStatusBarItem(): HTMLElement 34 | open fun addCommand(command: Command): Command 35 | open fun addSettingTab(settingTab: PluginSettingTab) 36 | open fun registerView(type: String, viewCreator: (leaf: WorkspaceLeaf) -> View) 37 | open fun registerExtensions(extensions: Array, viewType: String) 38 | open fun registerMarkdownPostProcessor(postProcessor: MarkdownPostProcessor): MarkdownPostProcessor 39 | open fun registerMarkdownCodeBlockProcessor(language: String, handler: (source: String, el: HTMLElement, ctx: MarkdownPostProcessorContext) -> Any): MarkdownPostProcessor 40 | // open fun registerCodeMirror(callback: (cm: CodeMirror.Editor) -> Any) 41 | // open fun registerObsidianProtocolHandler(action: String, handler: ObsidianProtocolHandler) 42 | open fun registerEditorSuggest(editorSuggest: EditorSuggest) 43 | open fun loadData(): Promise 44 | open fun saveData(data: Any): Promise 45 | } 46 | 47 | open external class PluginSettingTab(app: App, plugin: Plugin) : SettingTab 48 | 49 | open external class SettingTab { 50 | open var app: App 51 | open var containerEl: HTMLElement 52 | open fun display(): Any 53 | open fun hide(): Any 54 | } 55 | 56 | open external class App { 57 | open var workspace: Workspace 58 | open var vault: Vault 59 | open var metadataCache: MetadataCache 60 | open var fileManager: FileManager 61 | open var lastEvent: dynamic /* MouseEvent? | KeyboardEvent? | TouchEvent? | PointerEvent? */ 62 | } 63 | 64 | open external class Workspace : Events { 65 | open var leftSplit: dynamic /* WorkspaceSidedock | WorkspaceMobileDrawer */ 66 | open var rightSplit: dynamic /* WorkspaceSidedock | WorkspaceMobileDrawer */ 67 | open var leftRibbon: WorkspaceRibbon 68 | open var rightRibbon: WorkspaceRibbon 69 | open var rootSplit: WorkspaceSplit 70 | open var activeLeaf: WorkspaceLeaf? 71 | open var containerEl: HTMLElement 72 | open var layoutReady: Boolean 73 | open var requestSaveLayout: () -> Unit 74 | open var requestSaveHistory: () -> Unit 75 | open fun onLayoutReady(callback: () -> Any) 76 | open fun changeLayout(workspace: Any): Promise 77 | open fun getLayout(): Any 78 | open fun createLeafInParent(parent: WorkspaceSplit, index: Number): WorkspaceLeaf 79 | open fun splitLeaf(source: WorkspaceItem, newLeaf: WorkspaceItem, direction: String /* "vertical" | "horizontal" */ = definedExternally, before: Boolean = definedExternally) 80 | open fun createLeafBySplit(leaf: WorkspaceLeaf, direction: String /* "vertical" | "horizontal" */ = definedExternally, before: Boolean = definedExternally): WorkspaceLeaf 81 | open fun splitActiveLeaf(direction: String /* "vertical" | "horizontal" */ = definedExternally): WorkspaceLeaf 82 | open fun splitLeafOrActive(leaf: WorkspaceLeaf = definedExternally, direction: String /* "vertical" | "horizontal" */ = definedExternally): WorkspaceLeaf 83 | open fun duplicateLeaf(leaf: WorkspaceLeaf, direction: String /* "vertical" | "horizontal" */ = definedExternally): Promise 84 | open fun getUnpinnedLeaf(type: String = definedExternally): WorkspaceLeaf 85 | open fun getLeaf(newLeaf: Boolean = definedExternally): WorkspaceLeaf 86 | open fun openLinkText(linktext: String, sourcePath: String, newLeaf: Boolean = definedExternally, openViewState: OpenViewState = definedExternally): Promise 87 | open fun setActiveLeaf(leaf: WorkspaceLeaf, pushHistory: Boolean = definedExternally, focus: Boolean = definedExternally) 88 | open fun getLeafById(id: String): WorkspaceLeaf 89 | open fun getGroupLeaves(group: String): Array 90 | open fun getMostRecentLeaf(): WorkspaceLeaf 91 | open fun getLeftLeaf(split: Boolean): WorkspaceLeaf 92 | open fun getRightLeaf(split: Boolean): WorkspaceLeaf 93 | open fun getActiveViewOfType(type: Constructor): T? 94 | open fun getActiveFile(): TFile? 95 | open fun iterateRootLeaves(callback: (leaf: WorkspaceLeaf) -> Any) 96 | open fun iterateAllLeaves(callback: (leaf: WorkspaceLeaf) -> Any) 97 | open fun getLeavesOfType(viewType: String): Array 98 | open fun detachLeavesOfType(viewType: String) 99 | open fun revealLeaf(leaf: WorkspaceLeaf) 100 | open fun getLastOpenFiles(): Array 101 | // open fun iterateCodeMirrors(callback: (cm: CodeMirror.Editor) -> Any) 102 | open fun on(name: String /* "quick-preview" */, callback: (file: TFile, data: String) -> Any, ctx: Any = definedExternally): EventRef 103 | open fun on(name: String /* "quick-preview" */, callback: (file: TFile, data: String) -> Any): EventRef 104 | open fun on(name: String /* "resize" | "click" | "layout-change" | "css-change" */, callback: () -> Any, ctx: Any = definedExternally): EventRef 105 | open fun on(name: String /* "resize" | "click" | "layout-change" | "css-change" */, callback: () -> Any): EventRef 106 | open fun on(name: String /* "active-leaf-change" */, callback: (leaf: WorkspaceLeaf?) -> Any, ctx: Any = definedExternally): EventRef 107 | open fun on(name: String /* "active-leaf-change" */, callback: (leaf: WorkspaceLeaf?) -> Any): EventRef 108 | open fun on(name: String /* "file-open" */, callback: (file: TFile?) -> Any, ctx: Any = definedExternally): EventRef 109 | open fun on(name: String /* "file-open" */, callback: (file: TFile?) -> Any): EventRef 110 | open fun on(name: String /* "file-menu" */, callback: (menu: Menu, file: TAbstractFile, source: String, leaf: WorkspaceLeaf) -> Any, ctx: Any = definedExternally): EventRef 111 | open fun on(name: String /* "file-menu" */, callback: (menu: Menu, file: TAbstractFile, source: String, leaf: WorkspaceLeaf) -> Any): EventRef 112 | open fun on(name: String /* "editor-menu" */, callback: (menu: Menu, editor: Editor, view: MarkdownView) -> Any, ctx: Any = definedExternally): EventRef 113 | open fun on(name: String /* "editor-menu" */, callback: (menu: Menu, editor: Editor, view: MarkdownView) -> Any): EventRef 114 | open fun on(name: String /* "editor-change" */, callback: (editor: Editor, markdownView: MarkdownView) -> Any, ctx: Any = definedExternally): EventRef 115 | open fun on(name: String /* "editor-change" */, callback: (editor: Editor, markdownView: MarkdownView) -> Any): EventRef 116 | open fun on(name: String /* "editor-paste" */, callback: (evt: ClipboardEvent, editor: Editor, markdownView: MarkdownView) -> Any, ctx: Any = definedExternally): EventRef 117 | open fun on(name: String /* "editor-paste" */, callback: (evt: ClipboardEvent, editor: Editor, markdownView: MarkdownView) -> Any): EventRef 118 | open fun on(name: String /* "editor-drop" */, callback: (evt: DragEvent, editor: Editor, markdownView: MarkdownView) -> Any, ctx: Any = definedExternally): EventRef 119 | open fun on(name: String /* "editor-drop" */, callback: (evt: DragEvent, editor: Editor, markdownView: MarkdownView) -> Any): EventRef 120 | // open fun on(name: String /* "codemirror" */, callback: (cm: CodeMirror.Editor) -> Any, ctx: Any = definedExternally): EventRef 121 | // open fun on(name: String /* "codemirror" */, callback: (cm: CodeMirror.Editor) -> Any): EventRef 122 | open fun on(name: String /* "quit" */, callback: (tasks: Tasks) -> Any, ctx: Any = definedExternally): EventRef 123 | open fun on(name: String /* "quit" */, callback: (tasks: Tasks) -> Any): EventRef 124 | } 125 | 126 | open external class Vault : Events { 127 | open var adapter: DataAdapter 128 | open var configDir: String 129 | open fun getName(): String 130 | open fun getAbstractFileByPath(path: String): TAbstractFile? 131 | open fun getRoot(): TFolder 132 | open fun create(path: String, data: String, options: DataWriteOptions = definedExternally): Promise 133 | open fun createBinary(path: String, data: ArrayBuffer, options: DataWriteOptions = definedExternally): Promise 134 | open fun createFolder(path: String): Promise 135 | open fun read(file: TFile): Promise 136 | open fun cachedRead(file: TFile): Promise 137 | open fun readBinary(file: TFile): Promise 138 | open fun getResourcePath(file: TFile): String 139 | open fun delete(file: TAbstractFile, force: Boolean = definedExternally): Promise 140 | open fun trash(file: TAbstractFile, system: Boolean): Promise 141 | open fun rename(file: TAbstractFile, newPath: String): Promise 142 | open fun modify(file: TFile, data: String, options: DataWriteOptions = definedExternally): Promise 143 | open fun modifyBinary(file: TFile, data: ArrayBuffer, options: DataWriteOptions = definedExternally): Promise 144 | open fun copy(file: TFile, newPath: String): Promise 145 | open fun getAllLoadedFiles(): Array 146 | open fun getMarkdownFiles(): Array 147 | open fun getFiles(): Array 148 | open fun on(name: String /* "create" | "modify" | "delete" */, callback: (file: TAbstractFile) -> Any, ctx: Any = definedExternally): EventRef 149 | open fun on(name: String /* "create" | "modify" | "delete" */, callback: (file: TAbstractFile) -> Any): EventRef 150 | open fun on(name: String /* "rename" */, callback: (file: TAbstractFile, oldPath: String) -> Any, ctx: Any = definedExternally): EventRef 151 | open fun on(name: String /* "rename" */, callback: (file: TAbstractFile, oldPath: String) -> Any): EventRef 152 | open fun on(name: String /* "closed" */, callback: () -> Any, ctx: Any = definedExternally): EventRef 153 | open fun on(name: String /* "closed" */, callback: () -> Any): EventRef 154 | 155 | companion object { 156 | fun recurseChildren(root: TFolder, cb: (file: TAbstractFile) -> Any) 157 | } 158 | } 159 | 160 | external interface Command { 161 | var id: String 162 | var name: String 163 | var icon: String? 164 | get() = definedExternally 165 | set(value) = definedExternally 166 | var mobileOnly: Boolean? 167 | get() = definedExternally 168 | set(value) = definedExternally 169 | var callback: (() -> Any)? 170 | get() = definedExternally 171 | set(value) = definedExternally 172 | var checkCallback: ((checking: Boolean) -> dynamic)? 173 | get() = definedExternally 174 | set(value) = definedExternally 175 | var editorCallback: ((editor: Editor, view: MarkdownView) -> Any)? 176 | get() = definedExternally 177 | set(value) = definedExternally 178 | var editorCheckCallback: ((checking: Boolean, editor: Editor, view: MarkdownView) -> dynamic)? 179 | get() = definedExternally 180 | set(value) = definedExternally 181 | var hotkeys: Array? 182 | get() = definedExternally 183 | set(value) = definedExternally 184 | } 185 | 186 | open external class Setting(containerEl: HTMLElement) { 187 | open var settingEl: HTMLElement 188 | open var infoEl: HTMLElement 189 | open var nameEl: HTMLElement 190 | open var descEl: HTMLElement 191 | open var controlEl: HTMLElement 192 | open var components: Array 193 | open fun setName(name: String): Setting /* this */ 194 | open fun setName(name: DocumentFragment): Setting /* this */ 195 | open fun setDesc(desc: String): Setting /* this */ 196 | open fun setDesc(desc: DocumentFragment): Setting /* this */ 197 | open fun setClass(cls: String): Setting /* this */ 198 | open fun setTooltip(tooltip: String): Setting /* this */ 199 | open fun setHeading(): Setting /* this */ 200 | open fun setDisabled(disabled: Boolean): Setting /* this */ 201 | open fun addButton(cb: (component: ButtonComponent) -> Any): Setting /* this */ 202 | open fun addExtraButton(cb: (component: ExtraButtonComponent) -> Any): Setting /* this */ 203 | open fun addToggle(cb: (component: ToggleComponent) -> Any): Setting /* this */ 204 | open fun addText(cb: (component: TextComponent) -> Any): Setting /* this */ 205 | open fun addSearch(cb: (component: SearchComponent) -> Any): Setting /* this */ 206 | open fun addTextArea(cb: (component: TextAreaComponent) -> Any): Setting /* this */ 207 | open fun addMomentFormat(cb: (component: MomentFormatComponent) -> Any): Setting /* this */ 208 | open fun addDropdown(cb: (component: DropdownComponent) -> Any): Setting /* this */ 209 | open fun addSlider(cb: (component: SliderComponent) -> Any): Setting /* this */ 210 | open fun then(cb: (setting: Setting /* this */) -> Any): Setting /* this */ 211 | } 212 | 213 | open external class MetadataCache : Events { 214 | open fun getFirstLinkpathDest(linkpath: String, sourcePath: String): TFile? 215 | open fun getFileCache(file: TFile): CachedMetadata? 216 | open fun getCache(path: String): CachedMetadata 217 | open fun fileToLinktext(file: TFile, sourcePath: String, omitMdExtension: Boolean = definedExternally): String 218 | open var resolvedLinks: Record> 219 | open var unresolvedLinks: Record> 220 | open fun on(name: String /* "changed" | "resolve" */, callback: (file: TFile) -> Any, ctx: Any = definedExternally): EventRef 221 | open fun on(name: String /* "changed" | "resolve" */, callback: (file: TFile) -> Any): EventRef 222 | open fun on(name: String /* "resolved" */, callback: () -> Any, ctx: Any = definedExternally): EventRef 223 | open fun on(name: String /* "resolved" */, callback: () -> Any): EventRef 224 | } 225 | 226 | open external class FileManager { 227 | open fun getNewFileParent(sourcePath: String): TFolder 228 | open fun renameFile(file: TAbstractFile, newPath: String): Promise 229 | open fun generateMarkdownLink(file: TFile, sourcePath: String, subpath: String = definedExternally, alias: String = definedExternally): String 230 | } 231 | 232 | external interface DataAdapter { 233 | fun getName(): String 234 | fun exists(normalizedPath: String, sensitive: Boolean = definedExternally): Promise 235 | fun stat(normalizedPath: String): Promise 236 | fun list(normalizedPath: String): Promise 237 | fun read(normalizedPath: String): Promise 238 | fun readBinary(normalizedPath: String): Promise 239 | fun write(normalizedPath: String, data: String, options: DataWriteOptions = definedExternally): Promise 240 | fun writeBinary(normalizedPath: String, data: ArrayBuffer, options: DataWriteOptions = definedExternally): Promise 241 | fun getResourcePath(normalizedPath: String): String 242 | fun mkdir(normalizedPath: String): Promise 243 | fun trashSystem(normalizedPath: String): Promise 244 | fun trashLocal(normalizedPath: String): Promise 245 | fun rmdir(normalizedPath: String, recursive: Boolean): Promise 246 | fun remove(normalizedPath: String): Promise 247 | fun rename(normalizedPath: String, normalizedNewPath: String): Promise 248 | fun copy(normalizedPath: String, normalizedNewPath: String): Promise 249 | } 250 | 251 | external class Notice(message: String, timeout: Int? = definedExternally) 252 | 253 | external interface Stat { 254 | var type: String /* "file" | "folder" */ 255 | var ctime: Number 256 | var mtime: Number 257 | var size: Number 258 | } 259 | 260 | external interface ListedFiles { 261 | var files: Array 262 | var folders: Array 263 | } 264 | 265 | open external class TAbstractFile { 266 | open var vault: Vault 267 | open var path: String 268 | open var name: String 269 | open var parent: TFolder 270 | } 271 | 272 | open external class TFolder : TAbstractFile { 273 | open var children: Array 274 | open fun isRoot(): Boolean 275 | } 276 | 277 | open external class TFile : TAbstractFile { 278 | open var stat: FileStats 279 | open var basename: String 280 | open var extension: String 281 | } 282 | 283 | external interface FileStats { 284 | var ctime: Number 285 | var mtime: Number 286 | var size: Number 287 | } 288 | 289 | external interface DataWriteOptions { 290 | var ctime: Number? 291 | get() = definedExternally 292 | set(value) = definedExternally 293 | var mtime: Number? 294 | get() = definedExternally 295 | set(value) = definedExternally 296 | } 297 | 298 | external interface CachedMetadata { 299 | var links: Array? 300 | get() = definedExternally 301 | set(value) = definedExternally 302 | var embeds: Array? 303 | get() = definedExternally 304 | set(value) = definedExternally 305 | var tags: Array? 306 | get() = definedExternally 307 | set(value) = definedExternally 308 | var headings: Array? 309 | get() = definedExternally 310 | set(value) = definedExternally 311 | var sections: Array? 312 | get() = definedExternally 313 | set(value) = definedExternally 314 | var listItems: Array? 315 | get() = definedExternally 316 | set(value) = definedExternally 317 | var frontmatter: FrontMatterCache? 318 | get() = definedExternally 319 | set(value) = definedExternally 320 | var blocks: Record? 321 | get() = definedExternally 322 | set(value) = definedExternally 323 | } 324 | 325 | external interface LinkCache : ReferenceCache 326 | 327 | external interface EmbedCache : ReferenceCache 328 | 329 | external interface TagCache : CacheItem { 330 | var tag: String 331 | } 332 | 333 | external interface HeadingCache : CacheItem { 334 | var heading: String 335 | var level: Number 336 | } 337 | 338 | external interface SectionCache : CacheItem { 339 | var id: String? 340 | get() = definedExternally 341 | set(value) = definedExternally 342 | var type: String 343 | } 344 | 345 | external interface ListItemCache : CacheItem { 346 | var id: String? 347 | get() = definedExternally 348 | set(value) = definedExternally 349 | var task: String? 350 | get() = definedExternally 351 | set(value) = definedExternally 352 | 353 | /** 354 | * The line number of the parent list item for a nested list. 355 | * - negative means there is no parent 356 | */ 357 | var parent: Number 358 | } 359 | 360 | external interface FrontMatterCache : CacheItem { 361 | @Suppress("DEPRECATION") 362 | @nativeGetter 363 | operator fun get(key: String): Any? 364 | @Suppress("DEPRECATION") 365 | @nativeSetter 366 | operator fun set(key: String, value: Any) 367 | } 368 | 369 | external interface BlockCache : CacheItem { 370 | var id: String 371 | } 372 | 373 | open external class WorkspaceRibbon 374 | 375 | open external class WorkspaceItem : Events { 376 | open fun getRoot(): WorkspaceItem 377 | } 378 | 379 | open external class WorkspaceParent : WorkspaceItem 380 | 381 | open external class WorkspaceSplit : WorkspaceParent 382 | 383 | open external class WorkspaceTabs : WorkspaceParent 384 | 385 | open external class WorkspaceLeaf : WorkspaceItem { 386 | open var view: View 387 | open fun openFile(file: TFile, openState: OpenViewState = definedExternally): Promise 388 | open fun open(view: View): Promise 389 | open fun getViewState(): ViewState 390 | open fun setViewState(viewState: ViewState, eState: Any = definedExternally): Promise 391 | open fun getEphemeralState(): Any 392 | open fun setEphemeralState(state: dynamic) 393 | open fun togglePinned() 394 | open fun setPinned(pinned: Boolean) 395 | open fun setGroupMember(other: WorkspaceLeaf) 396 | open fun setGroup(group: String) 397 | open fun detach() 398 | open fun getIcon(): String 399 | open fun getDisplayText(): String 400 | open fun onResize() 401 | open fun on(name: String /* "pinned-change" */, callback: (pinned: Boolean) -> Any, ctx: Any = definedExternally): EventRef 402 | open fun on(name: String /* "pinned-change" */, callback: (pinned: Boolean) -> Any): EventRef 403 | open fun on(name: String /* "group-change" */, callback: (group: String) -> Any, ctx: Any = definedExternally): EventRef 404 | open fun on(name: String /* "group-change" */, callback: (group: String) -> Any): EventRef 405 | } 406 | 407 | open external class View(leaf: WorkspaceLeaf) : Component { 408 | open var app: App 409 | open var icon: String 410 | open var navigation: Boolean 411 | open var leaf: WorkspaceLeaf 412 | open var containerEl: HTMLElement 413 | open fun onOpen(): Promise 414 | open fun onClose(): Promise 415 | open fun getViewType(): String 416 | open fun getState(): Any 417 | open fun setState(state: Any, result: ViewStateResult): Promise 418 | open fun getEphemeralState(): Any 419 | open fun setEphemeralState(state: Any) 420 | open fun getIcon(): String 421 | open fun onResize() 422 | open fun getDisplayText(): String 423 | open fun onHeaderMenu(menu: Menu) 424 | } 425 | 426 | external interface MarkdownPostProcessor { 427 | @Suppress("DEPRECATION") 428 | @nativeInvoke 429 | operator fun invoke(el: HTMLElement, ctx: MarkdownPostProcessorContext): dynamic /* Promise | Unit */ 430 | var sortOrder: Number? 431 | get() = definedExternally 432 | set(value) = definedExternally 433 | } 434 | 435 | external interface MarkdownPostProcessorContext { 436 | var docId: String 437 | var sourcePath: String 438 | var frontmatter: Any? 439 | fun addChild(child: MarkdownRenderChild) 440 | fun getSectionInfo(el: HTMLElement): MarkdownSectionInformation? 441 | } 442 | 443 | external interface MarkdownSectionInformation { 444 | var text: String 445 | var lineStart: Number 446 | var lineEnd: Number 447 | } 448 | 449 | open external class MarkdownView(leaf: WorkspaceLeaf) : TextFileView { 450 | open var editor: Editor 451 | open var previewMode: MarkdownPreviewView 452 | open var currentMode: MarkdownSubView 453 | override fun getViewType(): String 454 | open fun getMode(): String /* "source" | "preview" | "live" */ 455 | override fun getViewData(): String 456 | override fun clear() 457 | override fun setViewData(data: String, clear: Boolean) 458 | open fun showSearch(replace: Boolean = definedExternally) 459 | } 460 | 461 | open external class MarkdownPreviewView(containerEl: HTMLElement) : MarkdownRenderer, MarkdownSubView, MarkdownPreviewEvents { 462 | override var containerEl: HTMLElement 463 | override fun get(): String 464 | override fun set(data: String, clear: Boolean) 465 | open fun clear() 466 | open fun rerender(full: Boolean = definedExternally) 467 | override fun getScroll(): Number 468 | override fun applyScroll(scroll: Number) 469 | } 470 | 471 | external interface MarkdownSubView { 472 | fun getScroll(): Number 473 | fun applyScroll(scroll: Number) 474 | fun get(): String 475 | fun set(data: String, clear: Boolean) 476 | } 477 | 478 | open external class MarkdownRenderer(containerEl: HTMLElement) : MarkdownRenderChild, MarkdownPreviewEvents, HoverParent { 479 | companion object { 480 | fun renderMarkdown(markdown: String, el: HTMLElement, sourcePath: String, component: Component): Promise 481 | } 482 | } 483 | 484 | open external class MarkdownRenderChild(containerEl: HTMLElement) : Component { 485 | open var containerEl: HTMLElement 486 | } 487 | 488 | external interface MarkdownPreviewEvents : Component 489 | 490 | open external class TextFileView(leaf: WorkspaceLeaf) : EditableFileView { 491 | open var data: String 492 | open var requestSave: () -> Unit 493 | override fun onUnloadFile(file: TFile): Promise 494 | override fun onLoadFile(file: TFile): Promise 495 | open fun save(clear: Boolean = definedExternally): Promise 496 | open fun getViewData(): String 497 | open fun setViewData(data: String, clear: Boolean) 498 | open fun clear() 499 | } 500 | 501 | open external class EditableFileView(leaf: WorkspaceLeaf) : FileView 502 | 503 | open external class FileView(leaf: WorkspaceLeaf) : ItemView { 504 | open var allowNoFile: Boolean 505 | open var file: TFile 506 | override fun getDisplayText(): String 507 | override fun onload() 508 | override fun getState(): Any 509 | override fun setState(state: Any, result: ViewStateResult): Promise 510 | open fun onLoadFile(file: TFile): Promise 511 | open fun onUnloadFile(file: TFile): Promise 512 | override fun onMoreOptionsMenu(menu: Menu) 513 | override fun onHeaderMenu(menu: Menu) 514 | open fun canAcceptExtension(extension: String): Boolean 515 | } 516 | 517 | open external class ItemView(leaf: WorkspaceLeaf) : View { 518 | open var contentEl: HTMLElement 519 | open fun onMoreOptionsMenu(menu: Menu) 520 | open fun addAction(icon: String, title: String, callback: (evt: MouseEvent) -> Any, size: Number = definedExternally): HTMLElement 521 | override fun onHeaderMenu(menu: Menu) 522 | } 523 | 524 | open external class Menu(app: App) : Component { 525 | open fun setNoIcon(): Menu /* this */ 526 | open fun addItem(cb: (item: MenuItem) -> Any): Menu /* this */ 527 | open fun addSeparator(): Menu /* this */ 528 | open fun showAtMouseEvent(evt: MouseEvent): Menu /* this */ 529 | open fun showAtPosition(position: Point): Menu /* this */ 530 | open fun hide(): Menu /* this */ 531 | open fun onHide(callback: () -> Any) 532 | } 533 | 534 | open external class MenuItem(menu: Menu) { 535 | open fun setTitle(title: String): MenuItem /* this */ 536 | open fun setTitle(title: DocumentFragment): MenuItem /* this */ 537 | open fun setIcon(icon: String?, size: Number = definedExternally): MenuItem /* this */ 538 | open fun setActive(active: Boolean): MenuItem /* this */ 539 | open fun setDisabled(disabled: Boolean): MenuItem /* this */ 540 | open fun setIsLabel(isLabel: Boolean): MenuItem /* this */ 541 | open fun onClick(callback: (evt: Any /* MouseEvent | KeyboardEvent */) -> Any): MenuItem /* this */ 542 | } 543 | 544 | open external class Editor { 545 | open fun getDoc(): Editor /* this */ 546 | open fun refresh() 547 | open fun getValue(): String 548 | open fun setValue(content: String) 549 | open fun getLine(line: Number): String 550 | open fun setLine(n: Number, text: String) 551 | open fun lineCount(): Number 552 | open fun lastLine(): Number 553 | open fun getSelection(): String 554 | open fun somethingSelected(): Boolean 555 | open fun getRange(from: EditorPosition, to: EditorPosition): String 556 | open fun replaceSelection(replacement: String, origin: String = definedExternally) 557 | open fun replaceRange(replacement: String, from: EditorPosition, to: EditorPosition = definedExternally, origin: String = definedExternally) 558 | open fun getCursor(string: String /* "from" | "to" | "head" | "anchor" */ = definedExternally): EditorPosition 559 | open fun listSelections(): Array 560 | open fun setCursor(pos: EditorPosition, ch: Number = definedExternally) 561 | open fun setCursor(pos: EditorPosition) 562 | open fun setCursor(pos: Number, ch: Number = definedExternally) 563 | open fun setCursor(pos: Number) 564 | open fun setSelection(anchor: EditorPosition, head: EditorPosition = definedExternally) 565 | open fun setSelections(ranges: Array, main: Number = definedExternally) 566 | open fun focus() 567 | open fun blur() 568 | open fun hasFocus(): Boolean 569 | open fun getScrollInfo(): `T$1` 570 | open fun scrollTo(x: Number? = definedExternally, y: Number? = definedExternally) 571 | open fun scrollIntoView(range: EditorRange, margin: Number = definedExternally) 572 | open fun undo() 573 | open fun redo() 574 | open fun exec(command: String /* "goUp" | "goDown" | "goLeft" | "goRight" | "goStart" | "goEnd" | "indentMore" | "indentLess" | "newlineAndIndent" | "swapLineUp" | "swapLineDown" | "deleteLine" | "toggleFold" | "foldAll" | "unfoldAll" */) 575 | open fun transaction(tx: EditorTransaction) 576 | open fun posToOffset(pos: EditorPosition): Number 577 | open fun offsetToPos(offset: Number): EditorPosition 578 | } 579 | 580 | open external class Modal(app: App) : CloseableComponent { 581 | open var app: App 582 | open var scope: Scope 583 | open var containerEl: HTMLElement 584 | open var modalEl: HTMLElement 585 | open var titleEl: HTMLElement 586 | open var contentEl: HTMLElement 587 | open var shouldRestoreSelection: Boolean 588 | open fun open() 589 | override fun close() 590 | open fun onOpen() 591 | open fun onClose() 592 | } 593 | 594 | external interface `T$1` { 595 | var top: Number 596 | var left: Number 597 | } 598 | 599 | external interface EditorChange : EditorRangeOrCaret { 600 | var text: String 601 | } 602 | 603 | external interface EditorPosition { 604 | var line: Number 605 | var ch: Number 606 | } 607 | 608 | external interface EditorRange { 609 | var from: EditorPosition 610 | var to: EditorPosition 611 | } 612 | 613 | external interface EditorRangeOrCaret { 614 | var from: EditorPosition 615 | var to: EditorPosition? 616 | get() = definedExternally 617 | set(value) = definedExternally 618 | } 619 | 620 | external interface EditorSelection { 621 | var anchor: EditorPosition 622 | var head: EditorPosition 623 | } 624 | 625 | external interface EditorSelectionOrCaret { 626 | var anchor: EditorPosition 627 | var head: EditorPosition? 628 | get() = definedExternally 629 | set(value) = definedExternally 630 | } 631 | 632 | open external class EditorSuggest(app: App) : PopoverSuggest { 633 | open var context: EditorSuggestContext? 634 | open var limit: Number 635 | open fun onTrigger(cursor: EditorPosition, editor: Editor, file: TFile): EditorSuggestTriggerInfo? 636 | open fun getSuggestions(context: EditorSuggestContext): dynamic /* Array | Promise> */ 637 | } 638 | 639 | external interface EditorSuggestContext : EditorSuggestTriggerInfo { 640 | var editor: Editor 641 | var file: TFile 642 | } 643 | 644 | external interface EditorSuggestTriggerInfo { 645 | var start: EditorPosition 646 | var end: EditorPosition 647 | var query: String 648 | } 649 | 650 | external interface EditorTransaction { 651 | var replaceSelection: String? 652 | get() = definedExternally 653 | set(value) = definedExternally 654 | var changes: Array? 655 | get() = definedExternally 656 | set(value) = definedExternally 657 | var selections: Array? 658 | get() = definedExternally 659 | set(value) = definedExternally 660 | var selection: EditorRangeOrCaret? 661 | get() = definedExternally 662 | set(value) = definedExternally 663 | } 664 | 665 | external interface HoverParent { 666 | var hoverPopover: HoverPopover? 667 | get() = definedExternally 668 | set(value) = definedExternally 669 | } 670 | 671 | open external class HoverPopover(parent: HoverParent, targetEl: HTMLElement?, waitTime: Number = definedExternally) : Component { 672 | open var state: PopoverState 673 | } 674 | 675 | external enum class PopoverState 676 | 677 | open external class PopoverSuggest(app: App, scope: Scope = definedExternally) : ISuggestOwner, CloseableComponent { 678 | open fun open() 679 | override fun close() 680 | override fun renderSuggestion(value: T, el: HTMLElement) 681 | override fun selectSuggestion(value: T, evt: MouseEvent) 682 | override fun selectSuggestion(value: T, evt: KeyboardEvent) 683 | } 684 | 685 | external interface ISuggestOwner { 686 | fun renderSuggestion(value: T, el: HTMLElement) 687 | fun selectSuggestion(value: T, evt: MouseEvent) 688 | fun selectSuggestion(value: T, evt: KeyboardEvent) 689 | } 690 | 691 | open external class Scope { 692 | // open fun register(modifiers: Array, key: String?, func: KeymapEventListener): KeymapEventHandler 693 | open fun unregister(handler: KeymapEventHandler) 694 | } 695 | 696 | external interface Hotkey { 697 | var modifiers: Array 698 | var key: String 699 | } 700 | 701 | external interface KeymapEventHandler : KeymapInfo { 702 | var scope: Scope 703 | } 704 | 705 | //typealias KeymapEventListener = (evt: KeyboardEvent, ctx: KeymapContext) -> dynamic 706 | 707 | external interface KeymapInfo { 708 | var modifiers: String? 709 | var key: String? 710 | } 711 | 712 | external interface ViewStateResult 713 | 714 | external interface OpenViewState 715 | 716 | external interface ViewState { 717 | var type: String 718 | var state: Any? 719 | get() = definedExternally 720 | set(value) = definedExternally 721 | var active: Boolean? 722 | get() = definedExternally 723 | set(value) = definedExternally 724 | var pinned: Boolean? 725 | get() = definedExternally 726 | set(value) = definedExternally 727 | var group: WorkspaceLeaf? 728 | get() = definedExternally 729 | set(value) = definedExternally 730 | } 731 | 732 | external interface CacheItem { 733 | var position: Pos 734 | } 735 | 736 | external interface ReferenceCache : CacheItem { 737 | var link: String 738 | var original: String 739 | var displayText: String? 740 | get() = definedExternally 741 | set(value) = definedExternally 742 | } 743 | 744 | open external class AbstractTextComponent(inputEl: T) : ValueComponent { 745 | open var inputEl: T 746 | override fun setDisabled(disabled: Boolean): AbstractTextComponent /* this */ 747 | override fun getValue(): String 748 | override fun setValue(value: String): AbstractTextComponent /* this */ 749 | open fun setPlaceholder(placeholder: String): AbstractTextComponent /* this */ 750 | open fun onChanged() 751 | open fun onChange(callback: (value: String) -> Any): AbstractTextComponent /* this */ 752 | } 753 | 754 | open external class BaseComponent { 755 | open var disabled: Boolean 756 | open fun then(cb: (component: BaseComponent /* this */) -> Any): BaseComponent /* this */ 757 | open fun setDisabled(disabled: Boolean): BaseComponent /* this */ 758 | } 759 | 760 | open external class ButtonComponent(containerEl: HTMLElement) : BaseComponent { 761 | open var buttonEl: HTMLButtonElement 762 | override fun setDisabled(disabled: Boolean): ButtonComponent /* this */ 763 | open fun setCta(): ButtonComponent /* this */ 764 | open fun removeCta(): ButtonComponent /* this */ 765 | open fun setWarning(): ButtonComponent /* this */ 766 | open fun setTooltip(tooltip: String): ButtonComponent /* this */ 767 | open fun setButtonText(name: String): ButtonComponent /* this */ 768 | open fun setIcon(icon: String): ButtonComponent /* this */ 769 | open fun setClass(cls: String): ButtonComponent /* this */ 770 | open fun onClick(callback: (evt: MouseEvent) -> Any): ButtonComponent /* this */ 771 | } 772 | 773 | open external class DropdownComponent(containerEl: HTMLElement) : ValueComponent { 774 | open var selectEl: HTMLSelectElement 775 | override fun setDisabled(disabled: Boolean): DropdownComponent /* this */ 776 | open fun addOption(value: String, display: String): DropdownComponent /* this */ 777 | open fun addOptions(options: Record): DropdownComponent /* this */ 778 | override fun getValue(): String 779 | override fun setValue(value: String): DropdownComponent /* this */ 780 | open fun onChange(callback: (value: String) -> Any): DropdownComponent /* this */ 781 | } 782 | 783 | open external class ExtraButtonComponent(containerEl: HTMLElement) : BaseComponent { 784 | open var extraSettingsEl: HTMLElement 785 | override fun setDisabled(disabled: Boolean): ExtraButtonComponent /* this */ 786 | open fun setTooltip(tooltip: String): ExtraButtonComponent /* this */ 787 | open fun setIcon(icon: String): ExtraButtonComponent /* this */ 788 | open fun onClick(callback: () -> Any): ExtraButtonComponent /* this */ 789 | } 790 | 791 | open external class MomentFormatComponent(containerEl: HTMLElement) : TextComponent { 792 | open var sampleEl: HTMLElement 793 | open fun setDefaultFormat(defaultFormat: String): MomentFormatComponent /* this */ 794 | open fun setSampleEl(sampleEl: HTMLElement): MomentFormatComponent /* this */ 795 | override fun setValue(value: String): MomentFormatComponent /* this */ 796 | override fun onChanged() 797 | open fun updateSample() 798 | } 799 | 800 | open external class SearchComponent(containerEl: HTMLElement) : AbstractTextComponent { 801 | open var clearButtonEl: HTMLElement 802 | override fun onChanged() 803 | } 804 | 805 | open external class SliderComponent(containerEl: HTMLElement) : ValueComponent { 806 | open var sliderEl: HTMLInputElement 807 | override fun setDisabled(disabled: Boolean): SliderComponent /* this */ 808 | open fun setLimits(min: Number, max: Number, step: Number): SliderComponent /* this */ 809 | open fun setLimits(min: Number, max: Number, step: String /* "any" */): SliderComponent /* this */ 810 | override fun getValue(): Number 811 | override fun setValue(value: Number): SliderComponent /* this */ 812 | open fun getValuePretty(): String 813 | open fun setDynamicTooltip(): SliderComponent /* this */ 814 | open fun showTooltip() 815 | open fun onChange(callback: (value: Number) -> Any): SliderComponent /* this */ 816 | } 817 | 818 | open external class TextAreaComponent(containerEl: HTMLElement) : AbstractTextComponent 819 | 820 | open external class TextComponent(containerEl: HTMLElement) : AbstractTextComponent 821 | 822 | open external class ToggleComponent(containerEl: HTMLElement) : ValueComponent { 823 | open var toggleEl: HTMLElement 824 | override fun setDisabled(disabled: Boolean): ToggleComponent /* this */ 825 | override fun getValue(): Boolean 826 | override fun setValue(value: Boolean): ToggleComponent /* this */ 827 | open fun setTooltip(tooltip: String): ToggleComponent /* this */ 828 | open fun onClick() 829 | open fun onChange(callback: (value: Boolean) -> Any): ToggleComponent /* this */ 830 | } 831 | 832 | open external class ValueComponent : BaseComponent { 833 | open fun registerOptionListener(listeners: Record T>, key: String): ValueComponent /* this */ 834 | open fun getValue(): T 835 | open fun setValue(value: T): ValueComponent /* this */ 836 | } 837 | 838 | external interface CloseableComponent { 839 | fun close(): Any 840 | } 841 | 842 | external interface Pos { 843 | var start: Loc 844 | var end: Loc 845 | } 846 | 847 | external interface Loc { 848 | var line: Number 849 | var col: Number 850 | var offset: Number 851 | } 852 | 853 | external interface Point { 854 | var x: Number 855 | var y: Number 856 | } 857 | 858 | external interface PluginManifest { 859 | var dir: String? 860 | get() = definedExternally 861 | set(value) = definedExternally 862 | var id: String 863 | var name: String 864 | var author: String 865 | var version: String 866 | var minAppVersion: String 867 | var description: String 868 | var authorUrl: String? 869 | get() = definedExternally 870 | set(value) = definedExternally 871 | var isDesktopOnly: Boolean? 872 | get() = definedExternally 873 | set(value) = definedExternally 874 | } 875 | 876 | open external class Events { 877 | open fun on(name: String, callback: (data: Any) -> Any, ctx: Any = definedExternally): EventRef 878 | open fun off(name: String, callback: (data: Any) -> Any) 879 | open fun offref(ref: EventRef) 880 | open fun trigger(name: String, vararg data: Any) 881 | open fun tryTrigger(evt: EventRef, args: Array) 882 | } 883 | 884 | //typealias ObsidianProtocolHandler = (params: ObsidianProtocolData) -> Any 885 | 886 | external interface EventRef 887 | 888 | open external class Tasks { 889 | open fun add(callback: () -> Promise) 890 | open fun addPromise(promise: Promise) 891 | open fun isEmpty(): Boolean 892 | open fun promise(): Promise 893 | } 894 | 895 | external interface Constructor 896 | -------------------------------------------------------------------------------- /src/main/kotlin/obsidian/builders/CommandBuilder.kt: -------------------------------------------------------------------------------- 1 | package obsidian.builders 2 | 3 | import Command 4 | import Editor 5 | import Hotkey 6 | import MarkdownView 7 | 8 | @Suppress("unused") 9 | @OptIn(ExperimentalJsExport::class) 10 | @JsExport 11 | class CommandBuilder( 12 | var id: String, 13 | var name: String 14 | ) { 15 | private val command = InternalCommand(id, name) 16 | 17 | fun icon(icon: String) = apply { command.icon = icon } 18 | fun mobileOnly(flag: Boolean) = apply { command.mobileOnly = flag } 19 | fun callback(function: () -> Any) = apply { command.callback = function } 20 | fun checkCallback(function: ((checking: Boolean) -> dynamic)) = apply { command.checkCallback = function } 21 | fun editorCallback(function: ((editor: Editor, view: MarkdownView) -> Any)) = apply { command.editorCallback = function } 22 | fun editorCheckCallback(function: ((checking: Boolean, editor: Editor, view: MarkdownView) -> dynamic)) = apply { command.editorCheckCallback = function } 23 | fun hotkeys(hotkeys: Array) = apply { command.hotkeys = hotkeys } 24 | fun build() : Command { 25 | return command 26 | } 27 | 28 | class InternalCommand(override var id: String, override var name: String) : Command 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-kotlin-plugin", 3 | "name": "Kotlin Sample Plugin", 4 | "version": "0.2.0-SNAPSHOT", 5 | "minAppVersion": "0.12.17", 6 | "description": "Base plugin for Obsidian using Kotlin", 7 | "author": "darthmachina", 8 | "authorUrl": "https://obsidian.md", 9 | "isDesktopOnly": false 10 | } -------------------------------------------------------------------------------- /src/main/resources/styles.css: -------------------------------------------------------------------------------- 1 | /* Sets all the text color to red! */ 2 | body { 3 | color: red; 4 | } 5 | -------------------------------------------------------------------------------- /src/test/kotlin/KotlinPluginTest.kt: -------------------------------------------------------------------------------- 1 | import kotlin.test.Test 2 | 3 | class TestClient { 4 | @Test 5 | fun testLoad() { 6 | val manifest = object : PluginManifest { 7 | override var id: String 8 | get() = "" 9 | set(value) {} 10 | override var name: String 11 | get() = "" 12 | set(value) {} 13 | override var author: String 14 | get() = "" 15 | set(value) {} 16 | override var version: String 17 | get() = "" 18 | set(value) {} 19 | override var minAppVersion: String 20 | get() = "" 21 | set(value) {} 22 | override var description: String 23 | get() = "" 24 | set(value) {} 25 | } 26 | val plugin = KotlinPlugin(App(), manifest) 27 | plugin.onload() 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.1.0": "0.12.17" 3 | } 4 | -------------------------------------------------------------------------------- /webpack.config.d/externals.js: -------------------------------------------------------------------------------- 1 | config.externals = { 2 | obsidian: 'obsidian', 3 | }; --------------------------------------------------------------------------------