anpigon commited on
Commit
c63ff03
1 Parent(s): 62a1d16

Add favicon and image assets for Obsidian help and developer documentation

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +162 -0
  2. README.md +1 -1
  3. app.py +177 -0
  4. docs/obsidian-developer/Assets/command.png +0 -0
  5. docs/obsidian-developer/Assets/context-menu-positions.png +0 -0
  6. docs/obsidian-developer/Assets/decorations.svg +173 -0
  7. docs/obsidian-developer/Assets/default-violet.webp +0 -0
  8. docs/obsidian-developer/Assets/editor-todays-date.gif +0 -0
  9. docs/obsidian-developer/Assets/editor-uppercase.gif +0 -0
  10. docs/obsidian-developer/Assets/example-insert-link.gif +0 -0
  11. docs/obsidian-developer/Assets/fuzzy-suggestion-modal.png +0 -0
  12. docs/obsidian-developer/Assets/logo.svg +15 -0
  13. docs/obsidian-developer/Assets/modal-input.png +0 -0
  14. docs/obsidian-developer/Assets/obsidian-lockup-docs.svg +1 -0
  15. docs/obsidian-developer/Assets/settings-headings.png +0 -0
  16. docs/obsidian-developer/Assets/settings.png +0 -0
  17. docs/obsidian-developer/Assets/status-bar.png +0 -0
  18. docs/obsidian-developer/Assets/styles.png +0 -0
  19. docs/obsidian-developer/Assets/suggest-modal.gif +0 -0
  20. docs/obsidian-developer/Assets/user-interface.png +0 -0
  21. docs/obsidian-developer/Assets/viewport.svg +85 -0
  22. docs/obsidian-developer/Developer policies.md +55 -0
  23. docs/obsidian-developer/Home.md +35 -0
  24. docs/obsidian-developer/Plugins/Editor/Communicating with editor extensions.md +53 -0
  25. docs/obsidian-developer/Plugins/Editor/Decorations.md +226 -0
  26. docs/obsidian-developer/Plugins/Editor/Editor extensions.md +32 -0
  27. docs/obsidian-developer/Plugins/Editor/Editor.md +71 -0
  28. docs/obsidian-developer/Plugins/Editor/Markdown post processing.md +76 -0
  29. docs/obsidian-developer/Plugins/Editor/State fields.md +95 -0
  30. docs/obsidian-developer/Plugins/Editor/State management.md +64 -0
  31. docs/obsidian-developer/Plugins/Editor/View plugins.md +54 -0
  32. docs/obsidian-developer/Plugins/Editor/Viewport.md +12 -0
  33. docs/obsidian-developer/Plugins/Events.md +50 -0
  34. docs/obsidian-developer/Plugins/Getting started/Anatomy of a plugin.md +40 -0
  35. docs/obsidian-developer/Plugins/Getting started/Build a plugin.md +131 -0
  36. docs/obsidian-developer/Plugins/Getting started/Development workflow.md +19 -0
  37. docs/obsidian-developer/Plugins/Getting started/Mobile development.md +73 -0
  38. docs/obsidian-developer/Plugins/Getting started/Use React in your plugin.md +138 -0
  39. docs/obsidian-developer/Plugins/Getting started/Use Svelte in your plugin.md +187 -0
  40. docs/obsidian-developer/Plugins/Releasing/Beta-testing plugins.md +3 -0
  41. docs/obsidian-developer/Plugins/Releasing/Plugin guidelines.md +301 -0
  42. docs/obsidian-developer/Plugins/Releasing/Release your plugin with GitHub Actions.md +71 -0
  43. docs/obsidian-developer/Plugins/Releasing/Submission requirements for plugins.md +43 -0
  44. docs/obsidian-developer/Plugins/Releasing/Submit your plugin.md +96 -0
  45. docs/obsidian-developer/Plugins/User interface/About user interface.md +11 -0
  46. docs/obsidian-developer/Plugins/User interface/Commands.md +121 -0
  47. docs/obsidian-developer/Plugins/User interface/Context menus.md +80 -0
  48. docs/obsidian-developer/Plugins/User interface/HTML elements.md +83 -0
  49. docs/obsidian-developer/Plugins/User interface/Icons.md +72 -0
  50. docs/obsidian-developer/Plugins/User interface/Modals.md +169 -0
.gitignore ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
159
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
+ #.idea/
161
+ .cache
162
+ .vscode
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Obsidian Qa Bot
3
  emoji: ⚡
4
  colorFrom: gray
5
  colorTo: gray
 
1
  ---
2
+ title: Obsidian QA Bot
3
  emoji: ⚡
4
  colorFrom: gray
5
  colorTo: gray
app.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+
4
+ from langchain_community.document_loaders import TextLoader
5
+ from langchain_text_splitters import RecursiveCharacterTextSplitter, Language
6
+
7
+ from langchain.embeddings import CacheBackedEmbeddings
8
+ from langchain.storage import LocalFileStore
9
+ from langchain_community.embeddings import HuggingFaceEmbeddings
10
+ from langchain_community.vectorstores import FAISS
11
+
12
+ from langchain_community.retrievers import BM25Retriever
13
+ from langchain.retrievers import EnsembleRetriever
14
+
15
+ from langchain_cohere import CohereRerank
16
+ from langchain.retrievers.contextual_compression import ContextualCompressionRetriever
17
+
18
+ from langchain_core.prompts import PromptTemplate
19
+
20
+ from langchain_core.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
21
+ from langchain_core.callbacks.manager import CallbackManager
22
+ from langchain_core.runnables import ConfigurableField
23
+ from langchain.callbacks.base import BaseCallbackHandler
24
+ from langchain_core.output_parsers import StrOutputParser
25
+ from langchain_core.runnables import RunnablePassthrough
26
+ from langchain_groq import ChatGroq
27
+ from langchain_community.llms import HuggingFaceHub
28
+ from langchain_google_genai import GoogleGenerativeAI
29
+
30
+
31
+ directories = ["./docs/obsidian-help", "./docs/obsidian-developer"]
32
+
33
+
34
+ # 1. 문서 로더를 사용하여 모든 .md 파일을 로드합니다.
35
+ md_documents = []
36
+ for directory in directories:
37
+ # os.walk를 사용하여 root_dir부터 시작하는 모든 디렉토리를 순회합니다.
38
+ for dirpath, dirnames, filenames in os.walk(directory):
39
+ # 각 디렉토리에서 파일 목록을 확인합니다.
40
+ for file in filenames:
41
+ # 파일 확장자가 .md인지 확인하고, 경로 내 '*venv/' 문자열이 포함되지 않는지도 체크합니다.
42
+ if (file.endswith(".md")) and "*venv/" not in dirpath:
43
+ try:
44
+ # TextLoader를 사용하여 파일의 전체 경로를 지정하고 문서를 로드합니다.
45
+ loader = TextLoader(os.path.join(dirpath, file), encoding="utf-8")
46
+ # 로드한 문서를 분할하여 documents 리스트에 추가합니다.
47
+ md_documents.extend(loader.load())
48
+ except Exception:
49
+ # 파일 로드 중 오류가 발생하면 이를 무시하고 계속 진행합니다.
50
+ pass
51
+
52
+
53
+ # 2. 청크 분할기를 생성합니다.
54
+ # 청크 크기는 2000, 청크간 겹치는 부분은 200 문자로 설정합니다.
55
+ md_splitter = RecursiveCharacterTextSplitter.from_language(
56
+ language=Language.MARKDOWN,
57
+ chunk_size=2000,
58
+ chunk_overlap=200,
59
+ )
60
+ md_docs = md_splitter.split_documents(md_documents)
61
+
62
+
63
+ # 3. 임베딩 모델을 사용하여 문서의 임베딩을 계산합니다.
64
+ # 허깅페이스 임베딩 모델 인스턴스를 생성합니다. 모델명으로 "BAAI/bge-m3 "을 사용합니다.
65
+ model_name = "BAAI/bge-m3"
66
+ model_kwargs = {"device": "mps"}
67
+ encode_kwargs = {"normalize_embeddings": False}
68
+ embeddings = HuggingFaceEmbeddings(
69
+ model_name=model_name,
70
+ model_kwargs=model_kwargs,
71
+ encode_kwargs=encode_kwargs,
72
+ )
73
+
74
+ # CacheBackedEmbeddings를 사용하여 임베딩 계산 결과를 캐시합니다.
75
+ store = LocalFileStore("./.cache/")
76
+ cached_embeddings = CacheBackedEmbeddings.from_bytes_store(
77
+ embeddings,
78
+ store,
79
+ namespace=embeddings.model_name,
80
+ )
81
+
82
+ # 4. FAISS 벡터 데이터베이스 인덱스를 생성하고 저장합니다.
83
+ FAISS_DB_INDEX = "db_index"
84
+
85
+ if os.path.exists(FAISS_DB_INDEX):
86
+ # 저장된 데이터베이스 인덱스가 이미 존재하는 경우, 해당 인덱스를 로드합니다.
87
+ db = FAISS.load_local(
88
+ FAISS_DB_INDEX, # 로드할 FAISS 인덱스의 디렉토리 이름
89
+ cached_embeddings, # 임베딩 정보를 제공
90
+ allow_dangerous_deserialization=True, # 역직렬화를 허용하는 옵션
91
+ )
92
+ else:
93
+ # combined_documents 문서들과 cached_embeddings 임베딩을 사용하여
94
+ # FAISS 데이터베이스 인스턴스를 생성합니다.
95
+ db = FAISS.from_documents(md_docs, cached_embeddings)
96
+ # 생성된 데이터베이스 인스턴스를 지정한 폴더에 로컬로 저장합니다.
97
+ db.save_local(folder_path=FAISS_DB_INDEX)
98
+
99
+
100
+ # 5. Retrieval를 생성합니다.
101
+ faiss_retriever = db.as_retriever(search_type="mmr", search_kwargs={"k": 10})
102
+
103
+ # 문서 컬렉션을 사용하여 BM25 검색 모델 인스턴스를 생성합니다.
104
+ bm25_retriever = BM25Retriever.from_documents(md_docs) # 초기화에 사용할 문서 컬렉션
105
+ bm25_retriever.k = 10 # 검색 시 최대 10개의 결과를 반환하도록 합니다.
106
+
107
+ # EnsembleRetriever 인스턴스를 생성합니다.
108
+ ensemble_retriever = EnsembleRetriever(
109
+ retrievers=[bm25_retriever, faiss_retriever], # 사용할 검색 모델의 리스트
110
+ weights=[0.6, 0.4], # 각 검색 모델의 결과에 적용할 가중치
111
+ search_type="mmr", # 검색 결과의 다양성을 증진시키는 MMR 방식을 사��
112
+ )
113
+
114
+ # 6. CohereRerank 모델을 사용하여 재정렬을 수행합니다.
115
+ compressor = CohereRerank(model="rerank-multilingual-v3.0")
116
+ compression_retriever = ContextualCompressionRetriever(
117
+ base_compressor=compressor,
118
+ base_retriever=ensemble_retriever,
119
+ )
120
+
121
+ # 7. Prompt를 생성합니다.
122
+ prompt = PromptTemplate.from_template(
123
+ """당신은 20년 경력의 옵시디언 노트앱 및 플러그인 개발 전문가로, 옵시디언 노트앱 사용법, 플러그인 및 테마 개발에 대한 깊은 지식을 가지고 있습니다. 당신의 주된 임무는 제공된 문서를 바탕으로 질문에 최대한 정확하고 상세하게 답변하는 것입니다.
124
+ 문서에는 옵시디언 노트앱의 기본 사용법, 고급 기능, 플러그인 개발 방법, 테마 개발 가이드 등 옵시디언 노트앱을 깊이 있게 사용하고 확장하는 데 필요한 정보가 포함되어 있습니다.
125
+ 귀하의 답변은 다음 지침에 따라야 합니다:
126
+ 1. 모든 답변은 명확하고 이해하기 쉬운 한국어로 제공되어야 합니다.
127
+ 2. 답변은 문서의 내용을 기반으로 해야 하며, 가능한 한 구체적인 정보를 포함해야 합니다.
128
+ 3. 문서 내에서 직접적인 답변을 찾을 수 없는 경우, "문서에는 해당 질문에 대한 구체적인 답변이 없습니다."라고 명시해 주세요.
129
+ 4. 가능한 경우, 답변과 관련된 문서의 구체적인 부분(예: 섹션 이름, 페이지 번호 등)을 출처로서 명시해 주세요.
130
+ 5. 질문에 대한 답변이 문서에 부분적으로만 포함되어 있는 경우, 가능한 한 많은 정보를 종합하여 답변해 주세요. 또한, 추가적인 연구나 참고자료가 필요할 수 있음을 언급해 주세요.
131
+
132
+ #참고문서:
133
+ {context}
134
+
135
+ #질문:
136
+ {question}
137
+
138
+ #답변:
139
+
140
+ 출처:
141
+ - source1
142
+ - source2
143
+ - ...
144
+ """
145
+ )
146
+
147
+
148
+ # 7. chain를 생성합니다.
149
+ llm = ChatGroq(
150
+ model_name="llama3-70b-8192",
151
+ temperature=0,
152
+ ).configurable_alternatives(
153
+ ConfigurableField(id="llm"),
154
+ default_key="llama3",
155
+ gemini=GoogleGenerativeAI(
156
+ model="gemini-pro",
157
+ temperature=0,
158
+ ),
159
+ )
160
+
161
+ rag_chain = (
162
+ {"context": compression_retriever, "question": RunnablePassthrough()}
163
+ | prompt
164
+ | llm
165
+ | StrOutputParser()
166
+ )
167
+
168
+ # # 8. chain를 실행합니다.
169
+ def predict(message, history=None):
170
+ answer = rag_chain.invoke(message)
171
+ return answer
172
+
173
+ gr.ChatInterface(
174
+ predict,
175
+ title="옵시디언 노트앱 및 플러그인 개발에 대해서 물어보세요!",
176
+ description="안녕하세요!\n저는 옵시디언 노트앱과 플러그인 개발에 대한 인공지능 QA봇입니다. 옵시디언 노트앱의 사용법, 고급 기능, 플러그인 및 테마 개발에 대해 깊은 지식을 가지고 있어요. 문서 작업, 정보 정리 또는 개발에 관한 도움이 필요하시면 언제든지 질문해주세요!",
177
+ ).launch()
docs/obsidian-developer/Assets/command.png ADDED
docs/obsidian-developer/Assets/context-menu-positions.png ADDED
docs/obsidian-developer/Assets/decorations.svg ADDED
docs/obsidian-developer/Assets/default-violet.webp ADDED
docs/obsidian-developer/Assets/editor-todays-date.gif ADDED
docs/obsidian-developer/Assets/editor-uppercase.gif ADDED
docs/obsidian-developer/Assets/example-insert-link.gif ADDED
docs/obsidian-developer/Assets/fuzzy-suggestion-modal.png ADDED
docs/obsidian-developer/Assets/logo.svg ADDED
docs/obsidian-developer/Assets/modal-input.png ADDED
docs/obsidian-developer/Assets/obsidian-lockup-docs.svg ADDED
docs/obsidian-developer/Assets/settings-headings.png ADDED
docs/obsidian-developer/Assets/settings.png ADDED
docs/obsidian-developer/Assets/status-bar.png ADDED
docs/obsidian-developer/Assets/styles.png ADDED
docs/obsidian-developer/Assets/suggest-modal.gif ADDED
docs/obsidian-developer/Assets/user-interface.png ADDED
docs/obsidian-developer/Assets/viewport.svg ADDED
docs/obsidian-developer/Developer policies.md ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Our goal for community plugins and themes is to make it easy for users to safely modify and expand the capabilities of Obsidian, while prioritizing private and offline usage of the app.
2
+
3
+ All community plugins and themes added to the Obsidian directory must respect the following policies. Every plugin and theme is individually vetted before being included in the directory. Plugins and themes that don't follow these policies will be removed from the directory.
4
+
5
+ ## Policies
6
+
7
+ ### Not allowed
8
+
9
+ Plugins and themes must not:
10
+
11
+ - Obfuscate code to hide its purpose.
12
+ - Insert dynamic ads that are loaded over the internet.
13
+ - Insert static ads outside a plugin’s own interface.
14
+ - Include client-side telemetry.
15
+ - Themes may not load assets from the network. To bundle an asset, see [[Embed fonts and images in your theme|this guide]].
16
+
17
+ ### Disclosures
18
+
19
+ The following are only allowed if clearly indicated in your README:
20
+
21
+ - Payment is required for full access.
22
+ - An account is required for full access.
23
+ - Network use. Clearly explain which remote services are used and why they're needed.
24
+ - Accessing files outside of Obsidian vaults. Clearly explain why this is needed.
25
+ - Static ads such as banners and pop-up messages within the plugin's own interface.
26
+ - Server-side telemetry. Link to a privacy policy that explains how the data is handled must be included.
27
+ - Close sourced code. This will be handled on a case by case basis.
28
+
29
+ ### Copyright and licensing
30
+
31
+ All community plugins and themes must follow these requirements:
32
+
33
+ - Include a [LICENSE file](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-license-to-a-repository) and clearly indicate the license of your plugin or theme.
34
+ - Comply with the original licenses of any code your plugin or theme makes use of, including attribution in the README if required.
35
+ - Respect Obsidian's trademark policy. Don't use the "Obsidian" trademark in a way that could confuse users into thinking your plugin or theme is a first-party creation.
36
+
37
+ ## Reporting violations
38
+
39
+ If you encounter a plugin or theme that violates the policies above, please let the developer know by opening a GitHub issue in their repository. Kindly check existing issues to see if it’s already reported.
40
+
41
+ If the developer doesn’t response after 7 days, [contact the Obsidian team](https://help.obsidian.md/Help+and+support#Report+a+security+issue). For serious violations, you can contact our team immediately.
42
+
43
+ ## Removing plugins and themes
44
+
45
+ In case of a policy violation, we may attempt to contact the developer and provide a reasonable timeframe for them to resolve the problem.
46
+
47
+ If the problem isn't resolved by then, we'll remove plugins or themes from our directory.
48
+
49
+ We may immediately remove a plugin or theme if:
50
+
51
+ - The plugin or theme appears to be malicious.
52
+ - The developer is uncooperative.
53
+ - This is a repeated violation.
54
+
55
+ In addition, we may also remove plugins or themes that have become unmaintained or severely broken.
docs/obsidian-developer/Home.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ cssClass: hide-title
3
+ ---
4
+ # Obsidian Developer Docs
5
+
6
+ Welcome to the official Obsidian Developer Documentation, where you can learn how to build plugins and themes for [Obsidian](https://obsidian.md/). For tips on how to use Obsidian, visit [the official Help site](https://help.obsidian.md/).
7
+
8
+ ## Plugins
9
+
10
+ Build plugins to extend the existing functionality in Obsidian using TypeScript.
11
+
12
+ - [[Build a plugin|Build your first plugin]]
13
+ - [[Submit your plugin]]
14
+
15
+ ## Themes
16
+
17
+ Design beautiful themes and snippets for Obsidian using CSS.
18
+
19
+ - [[Build a theme|Build your first theme]]
20
+ - [[Submit your theme]]
21
+ - [[CSS variables]]
22
+
23
+ ## Join the developer community
24
+
25
+ If you get stuck, or if you're looking for feedback, [join the community](https://obsidian.md/community).
26
+
27
+ - `#plugin-dev` and `#theme-dev` channels on Discord.
28
+ - [Developers & API](https://forum.obsidian.md/c/developers-api/14) and [Share & showcase](https://forum.obsidian.md/c/share-showcase/9) on the forum.
29
+
30
+ ## Contributing
31
+
32
+ If you see any errors or room for improvement on this site, or want to submit a PR, feel free to open an issue on [our GitHub repository](https://github.com/obsidianmd/obsidian-developer-docs).
33
+ Additional details are available on our [readme](https://github.com/obsidianmd/obsidian-developer-docs#readme).
34
+
35
+ Thank you in advance for contributing!
docs/obsidian-developer/Plugins/Editor/Communicating with editor extensions.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Once you've built your editor extension, you might want to communicate with it from outside the editor. For example, through a [[Commands|command]], or a [[Ribbon actions|ribbon action]].
2
+
3
+ You can access the CodeMirror 6 editor from a [[MarkdownView|MarkdownView]]. However, since the Obsidian API doesn't actually expose the editor, you need to tell TypeScript to trust that it's there, using `@ts-expect-error`.
4
+
5
+ ```ts
6
+ import { EditorView } from "@codemirror/view";
7
+
8
+ // @ts-expect-error, not typed
9
+ const editorView = view.editor.cm as EditorView;
10
+ ```
11
+
12
+ ## View plugin
13
+
14
+ You can access the [[View plugins|view plugin]] instance from the `EditorView.plugin()` method.
15
+
16
+ ```ts
17
+ this.addCommand({
18
+ id: "example-editor-command",
19
+ name: "Example editor command",
20
+ editorCallback: (editor, view) => {
21
+ // @ts-expect-error, not typed
22
+ const editorView = view.editor.cm as EditorView;
23
+
24
+ const plugin = editorView.plugin(examplePlugin);
25
+
26
+ if (plugin) {
27
+ plugin.addPointerToSelection(editorView);
28
+ }
29
+ },
30
+ });
31
+ ```
32
+
33
+ ## State field
34
+
35
+ You can dispatch changes and [[State fields#Dispatching state effects|dispatch state effects]] directly on the editor view.
36
+
37
+ ```ts
38
+ this.addCommand({
39
+ id: "example-editor-command",
40
+ name: "Example editor command",
41
+ editorCallback: (editor, view) => {
42
+ // @ts-expect-error, not typed
43
+ const editorView = view.editor.cm as EditorView;
44
+
45
+ editorView.dispatch({
46
+ effects: [
47
+ // ...
48
+ ],
49
+ });
50
+ },
51
+ });
52
+ ```
53
+
docs/obsidian-developer/Plugins/Editor/Decorations.md ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Decorations let you control how to draw or style content in [[Editor extensions|editor extensions]]. If you intend to change the look and feel by adding, replacing, or styling elements in the editor, you most likely need to use decorations.
2
+
3
+ By the end of this page, you'll be able to:
4
+
5
+ - Understand how to use decorations to change the editor appearance.
6
+ - Understand the difference between providing decoration using state fields and view plugins.
7
+
8
+ > [!note]
9
+ > This page aims to distill the official CodeMirror 6 documentation for Obsidian plugin developers. For more detailed information on state fields, refer to [Decorating the Document](https://codemirror.net/docs/guide/#decorating-the-document).
10
+
11
+ ## Prerequisites
12
+
13
+ - Basic understanding of [[State fields]].
14
+ - Basic understanding of [[View plugins]].
15
+
16
+ ## Overview
17
+
18
+ Without decorations, the document would render as plain text. Not very interesting at all. Using decorations, you can change how to display the document, for example by highlighting text or adding custom HTML elements.
19
+
20
+ You can use the following types of decorations:
21
+
22
+ - [Mark decorations](https://codemirror.net/docs/ref/#view.Decoration%5Emark) style existing elements.
23
+ - [Widget decorations](https://codemirror.net/docs/ref/#view.Decoration%5Ewidget) insert elements in the document.
24
+ - [Replace decorations](https://codemirror.net/docs/ref/#view.Decoration%5Ereplace) hide or replace part of the document with another element.
25
+ - [Line decorations](https://codemirror.net/docs/ref/#view.Decoration%5Eline) add styling to the lines, rather than the document itself.
26
+
27
+ To use decorations, you need to create them inside an editor extension and have the extension _provide_ them to the editor. You can provide decorations to the editor in two ways, either _directly_ using [[State fields|state fields]] or _indirectly_ using [[View plugins|view plugins]].
28
+
29
+ ## Should I use a view plugin or a state field?
30
+
31
+ Both view plugins and state fields can provide decorations to the editor, but they have some differences.
32
+
33
+ - Use a view plugin if you can determine the decoration based on what's inside the [[Viewport]].
34
+ - Use a state field if you need to manage decorations outside of the viewport.
35
+ - Use a state field if you want to make changes that could change the content of the viewport, for example by adding line breaks.
36
+
37
+ If you can implement your extension using either approach, then the view plugin generally results in better performance. For example, imagine that you want to implement an editor extension that checks the spelling of a document.
38
+
39
+ One way would be to pass the entire document to an external spell checker which then returns a list of spelling errors. In this case, you'd need to map each error to a decoration and use a state field to manage decorations regardless of what's in the viewport at the moment.
40
+
41
+ Another way would be to only spellcheck what's visible in the viewport. The extension would need to continuously run a spell check as the user scrolls through the document, but you'd be able to spell check documents with millions of lines of text.
42
+
43
+ ![State field vs. view plugin](decorations.svg)
44
+
45
+ ## Providing decorations
46
+
47
+ Imagine that you want to build an editor extension that replaces the bullet list item with an emoji. You can accomplish this with either a view plugin or a state field, with some differences. In this section, you'll see how to implement it with both types of extensions.
48
+
49
+ Both implementations share the same core logic:
50
+
51
+ 1. Use [syntaxTree](https://codemirror.net/docs/ref/#language.syntaxTree) to find list items.
52
+ 2. For every list item, replace leading hyphens, `-`, with a _widget_.
53
+
54
+ ### Widgets
55
+
56
+ Widgets are custom HTML elements that you can add to the editor. You can either insert a widget at a specific position in the document, or replace a piece of content with a widget.
57
+
58
+ The following example defines a widget that returns an HTML element, `<span>👉</span>`. You'll use this widget later on.
59
+
60
+ ```ts
61
+ import { EditorView, WidgetType } from "@codemirror/view";
62
+
63
+ export class EmojiWidget extends WidgetType {
64
+ toDOM(view: EditorView): HTMLElement {
65
+ const div = document.createElement("span");
66
+
67
+ div.innerText = "👉";
68
+
69
+ return div;
70
+ }
71
+ }
72
+ ```
73
+
74
+ To replace a range of content in your document with the emoji widget, use the [replace decoration](https://codemirror.net/docs/ref/#view.Decoration%5Ereplace).
75
+
76
+ ```ts
77
+ const decoration = Decoration.replace({
78
+ widget: new EmojiWidget()
79
+ });
80
+ ```
81
+
82
+ ### State fields
83
+
84
+ To provide decorations from a state field:
85
+
86
+ 1. [[State fields#Defining a state field|Define a state field]] with a `DecorationSet` type.
87
+ 2. Add the `provide` property to the state field.
88
+
89
+ ```ts
90
+ provide(field: StateField<DecorationSet>): Extension {
91
+ return EditorView.decorations.from(field);
92
+ },
93
+ ```
94
+
95
+ ```ts
96
+ import { syntaxTree } from "@codemirror/language";
97
+ import {
98
+ Extension,
99
+ RangeSetBuilder,
100
+ StateField,
101
+ Transaction,
102
+ } from "@codemirror/state";
103
+ import {
104
+ Decoration,
105
+ DecorationSet,
106
+ EditorView,
107
+ WidgetType,
108
+ } from "@codemirror/view";
109
+ import { EmojiWidget } from "emoji";
110
+
111
+ export const emojiListField = StateField.define<DecorationSet>({
112
+ create(state): DecorationSet {
113
+ return Decoration.none;
114
+ },
115
+ update(oldState: DecorationSet, transaction: Transaction): DecorationSet {
116
+ const builder = new RangeSetBuilder<Decoration>();
117
+
118
+ syntaxTree(transaction.state).iterate({
119
+ enter(node) {
120
+ if (node.type.name.startsWith("list")) {
121
+ // Position of the '-' or the '*'.
122
+ const listCharFrom = node.from - 2;
123
+
124
+ builder.add(
125
+ listCharFrom,
126
+ listCharFrom + 1,
127
+ Decoration.replace({
128
+ widget: new EmojiWidget(),
129
+ })
130
+ );
131
+ }
132
+ },
133
+ });
134
+
135
+ return builder.finish();
136
+ },
137
+ provide(field: StateField<DecorationSet>): Extension {
138
+ return EditorView.decorations.from(field);
139
+ },
140
+ });
141
+ ```
142
+
143
+ ### View plugins
144
+
145
+ To manage your decorations using a view plugin:
146
+
147
+ 1. [[View plugins#Creating a view plugin|Create a view plugin]].
148
+ 2. Add a `DecorationSet` member property to your plugin.
149
+ 3. Initialize the decorations in the `constructor()`.
150
+ 4. Rebuild decorations in `update()`.
151
+
152
+ Not all updates are reasons to rebuild your decorations. The following example only rebuilds decorations whenever the underlying document or the viewport changes.
153
+
154
+ ```ts
155
+ import { syntaxTree } from "@codemirror/language";
156
+ import { RangeSetBuilder } from "@codemirror/state";
157
+ import {
158
+ Decoration,
159
+ DecorationSet,
160
+ EditorView,
161
+ PluginSpec,
162
+ PluginValue,
163
+ ViewPlugin,
164
+ ViewUpdate,
165
+ WidgetType,
166
+ } from "@codemirror/view";
167
+ import { EmojiWidget } from "emoji";
168
+
169
+ class EmojiListPlugin implements PluginValue {
170
+ decorations: DecorationSet;
171
+
172
+ constructor(view: EditorView) {
173
+ this.decorations = this.buildDecorations(view);
174
+ }
175
+
176
+ update(update: ViewUpdate) {
177
+ if (update.docChanged || update.viewportChanged) {
178
+ this.decorations = this.buildDecorations(update.view);
179
+ }
180
+ }
181
+
182
+ destroy() {}
183
+
184
+ buildDecorations(view: EditorView): DecorationSet {
185
+ const builder = new RangeSetBuilder<Decoration>();
186
+
187
+ for (let { from, to } of view.visibleRanges) {
188
+ syntaxTree(view.state).iterate({
189
+ from,
190
+ to,
191
+ enter(node) {
192
+ if (node.type.name.startsWith("list")) {
193
+ // Position of the '-' or the '*'.
194
+ const listCharFrom = node.from - 2;
195
+
196
+ builder.add(
197
+ listCharFrom,
198
+ listCharFrom + 1,
199
+ Decoration.replace({
200
+ widget: new EmojiWidget(),
201
+ })
202
+ );
203
+ }
204
+ },
205
+ });
206
+ }
207
+
208
+ return builder.finish();
209
+ }
210
+ }
211
+
212
+ const pluginSpec: PluginSpec<EmojiListPlugin> = {
213
+ decorations: (value: EmojiListPlugin) => value.decorations,
214
+ };
215
+
216
+ export const emojiListPlugin = ViewPlugin.fromClass(
217
+ EmojiListPlugin,
218
+ pluginSpec
219
+ );
220
+ ```
221
+
222
+ `buildDecorations()` is a helper method that builds a complete set of decorations based on the editor view.
223
+
224
+ Notice the second argument to the `ViewPlugin.fromClass()` function. The `decorations` property in the `PluginSpec` specifies how the view plugin provides the decorations to the editor.
225
+
226
+ Since the view plugin knows what's visible to the user, you can use `view.visibleRanges` to limit what parts of the syntax tree to visit.
docs/obsidian-developer/Plugins/Editor/Editor extensions.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ alias: editor extension
3
+ ---
4
+
5
+ Editor extensions let you customize the experience of editing notes in Obsidian. This page explains what editor extensions are, and when to use them.
6
+
7
+ Obsidian uses CodeMirror 6 (CM6) to power the Markdown editor. Just like Obsidian, CM6 has plugins of its own, called _extensions_. In other words, an Obsidian _editor extension_ is the same thing as a _CodeMirror 6 extension_.
8
+
9
+ The API for building editor extensions is a bit unconventional and requires that you have a basic understanding of its architecture before you get started. This section aims to give you enough context and examples for you to get started. If you want to learn more about building editor extensions, refer to the [CodeMirror 6 documentation](https://codemirror.net/docs/).
10
+
11
+ ## Do I need an editor extension?
12
+
13
+ Building editor extensions can be challenging, so before you start building one, consider whether you really need it.
14
+
15
+ - If you want to change how to convert Markdown to HTML in the Reading view, consider building a [[Markdown post processing|Markdown post processor]].
16
+ - If you want to change how the document looks and feels in Live Preview, you need to build an editor extension.
17
+
18
+ ## Registering editor extensions
19
+
20
+ CodeMirror 6 (CM6) is a powerful engine for editing code using web technologies. At its core, the editor itself has a minimal set of features. Any features you'd expect from a modern editor are available as _extensions_ that you can pick and choose. While Obsidian comes with many of these extensions out-of-the-box, you can also register your own.
21
+
22
+ To register an editor extension, use [[registerEditorExtension|registerEditorExtension()]] in the `onload` method of your Obsidian plugin:
23
+
24
+ ```ts
25
+ onload() {
26
+ this.registerEditorExtension([examplePlugin, exampleField]);
27
+ }
28
+ ```
29
+
30
+ While CM6 supports several types of extensions, two of the most common ones are [[View plugins]] and [[State fields]].
31
+ <DocCardList items={useCurrentSidebarCategory().items}/>
32
+
docs/obsidian-developer/Plugins/Editor/Editor.md ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The [[Reference/TypeScript API/Editor/Editor|Editor]] class exposes operations for reading and manipulating an active Markdown document in edit mode.
2
+
3
+ If you want to access the editor in a command, use the [[Commands#Editor commands|editorCallback]].
4
+
5
+ If you want to use the editor elsewhere, you can access it from the active view:
6
+
7
+ ```ts
8
+ const view = this.app.workspace.getActiveViewOfType(MarkdownView);
9
+
10
+ // Make sure the user is editing a Markdown file.
11
+ if (view) {
12
+ const cursor = view.editor.getCursor();
13
+
14
+ // ...
15
+ }
16
+ ```
17
+
18
+ > [!note]
19
+ > Obsidian uses [CodeMirror](https://codemirror.net/) (CM) as the underlying text editor, and exposes the CodeMirror editor as part of the API. `Editor` serves as an abstraction to bridge features between CM6 and CM5 (legacy editor, only available on desktop). By using `Editor` instead of directly accessing the CodeMirror instance, you ensure that your plugin works on both platforms.
20
+
21
+ ## Insert text at cursor position
22
+
23
+ The [[replaceRange|replaceRange()]] method replaces the text between two cursor positions. If you only give it one position, it inserts the new text between that position and the next.
24
+
25
+ The following command inserts today's date at the cursor position:
26
+
27
+ ```ts
28
+ import { Editor, moment, Plugin } from "obsidian";
29
+
30
+ export default class ExamplePlugin extends Plugin {
31
+ async onload() {
32
+ this.addCommand({
33
+ id: "insert-todays-date",
34
+ name: "Insert today's date",
35
+ editorCallback: (editor: Editor) => {
36
+ editor.replaceRange(
37
+ moment().format("YYYY-MM-DD"),
38
+ editor.getCursor()
39
+ );
40
+ },
41
+ });
42
+ }
43
+ }
44
+ ```
45
+
46
+ ![[editor-todays-date.gif]]
47
+
48
+ ## Replace current selection
49
+
50
+ If you want to modify the selected text, use [[replaceRange|replaceSelection()]] to replace the current selection with a new text.
51
+
52
+ The following command reads the current selection and converts it to uppercase:
53
+
54
+ ```ts
55
+ import { Editor, Plugin } from "obsidian";
56
+
57
+ export default class ExamplePlugin extends Plugin {
58
+ async onload() {
59
+ this.addCommand({
60
+ id: "convert-to-uppercase",
61
+ name: "Convert to uppercase",
62
+ editorCallback: (editor: Editor) => {
63
+ const selection = editor.getSelection();
64
+ editor.replaceSelection(selection.toUpperCase());
65
+ },
66
+ });
67
+ }
68
+ }
69
+ ```
70
+
71
+ ![[editor-uppercase.gif]]
docs/obsidian-developer/Plugins/Editor/Markdown post processing.md ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ If you want to change how a Markdown document is rendered in Reading view, you can add your own _Markdown post processor_. As indicated by the name, the post processor runs _after_ the Markdown has been processed into HTML. It lets you add, remove, or replace [[HTML elements]] to the rendered document.
2
+
3
+ The following example looks for any code block that contains a text between two colons, `:`, and replaces it with an appropriate emoji:
4
+
5
+ ```ts
6
+ import { Plugin } from "obsidian";
7
+
8
+ const ALL_EMOJIS: Record<string, string> = {
9
+ ":+1:": "👍",
10
+ ":sunglasses:": "😎",
11
+ ":smile:": "😄",
12
+ };
13
+
14
+ export default class ExamplePlugin extends Plugin {
15
+ async onload() {
16
+ this.registerMarkdownPostProcessor((element, context) => {
17
+ const codeblocks = element.findAll("code");
18
+
19
+ for (let codeblock of codeblocks) {
20
+ const text = codeblock.innerText.trim();
21
+ if (text[0] === ":" && text[text.length - 1] === ":") {
22
+ const emojiEl = codeblock.createSpan({
23
+ text: ALL_EMOJIS[text] ?? text,
24
+ });
25
+ codeblock.replaceWith(emojiEl);
26
+ }
27
+ }
28
+ });
29
+ }
30
+ }
31
+ ```
32
+
33
+ ## Post-process Markdown code blocks
34
+
35
+ Did you know that you can create [Mermaid](https://mermaid-js.github.io/) diagrams in Obsidian by creating a `mermaid` code block with a text definition like this one?:
36
+
37
+ ````md
38
+ ```mermaid
39
+ flowchart LR
40
+ Start --> Stop
41
+ ```
42
+ ````
43
+
44
+ If you change to Preview mode, the text in the code block becomes the following diagram:
45
+
46
+ ```mermaid
47
+ flowchart LR
48
+ Start --> Stop
49
+ ```
50
+
51
+ If you want to add your own custom code blocks like the Mermaid one, you can use [[registerMarkdownCodeBlockProcessor|registerMarkdownCodeBlockProcessor()]]. The following example renders a code block with CSV data, as a table:
52
+
53
+ ```ts
54
+ import { Plugin } from "obsidian";
55
+
56
+ export default class ExamplePlugin extends Plugin {
57
+ async onload() {
58
+ this.registerMarkdownCodeBlockProcessor("csv", (source, el, ctx) => {
59
+ const rows = source.split("\n").filter((row) => row.length > 0);
60
+
61
+ const table = el.createEl("table");
62
+ const body = table.createEl("tbody");
63
+
64
+ for (let i = 0; i < rows.length; i++) {
65
+ const cols = rows[i].split(",");
66
+
67
+ const row = body.createEl("tr");
68
+
69
+ for (let j = 0; j < cols.length; j++) {
70
+ row.createEl("td", { text: cols[j] });
71
+ }
72
+ }
73
+ });
74
+ }
75
+ }
76
+ ```
docs/obsidian-developer/Plugins/Editor/State fields.md ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ A state field is an [[Editor extensions|editor extension]] that lets you manage custom editor state. This page walks you through building a state field by implementing a calculator extension.
2
+
3
+ The calculator should be able to add and subtract a number from the current state, and to reset the state when you want to start over.
4
+
5
+ By the end of this page, you'll understand the basic concepts of building a state field.
6
+
7
+ > [!note]
8
+ > This page aims to distill the official CodeMirror 6 documentation for Obsidian plugin developers. For more detailed information on state fields, refer to [State Fields](https://codemirror.net/docs/guide/#state-fields).
9
+
10
+ ## Prerequisites
11
+
12
+ - Basic understanding of [[State management]].
13
+
14
+ ## Defining state effects
15
+
16
+ State effects describe the state change you'd like to make. You may think of them as methods on a class.
17
+
18
+ In the calculator example, you'd define a state effect for each of the calculator operations:
19
+
20
+ ```ts
21
+ const addEffect = StateEffect.define<number>();
22
+ const subtractEffect = StateEffect.define<number>();
23
+ const resetEffect = StateEffect.define();
24
+ ```
25
+
26
+ The type between the angle brackets, `<>`, defines the input type for the effect. For example, the number you want to add or subtract. The reset effect doesn't need any input, so you can leave it out.
27
+
28
+ ## Defining a state field
29
+
30
+ Contrary to what one might think, state fields don't actually _store_ state. They _manage_ it. State fields take the current state, applies any state effects, and returns the new state.
31
+
32
+ The state field contains the calculator logic to apply the mathematical operations depending on the effects in a transaction. Since a transaction can contain multiple effects, for example two additions, the state field needs to apply them all one after another.
33
+
34
+ ```ts
35
+ export const calculatorField = StateField.define<number>({
36
+ create(state: EditorState): number {
37
+ return 0;
38
+ },
39
+ update(oldState: number, transaction: Transaction): number {
40
+ let newState = oldState;
41
+
42
+ for (let effect of transaction.effects) {
43
+ if (effect.is(addEffect)) {
44
+ newState += effect.value;
45
+ } else if (effect.is(subtractEffect)) {
46
+ newState -= effect.value;
47
+ } else if (effect.is(resetEffect)) {
48
+ newState = 0;
49
+ }
50
+ }
51
+
52
+ return newState;
53
+ },
54
+ });
55
+ ```
56
+
57
+ - `create` returns the value the calculator starts with.
58
+ - `update` contains the logic for applying the effects.
59
+ - `effect.is()` lets you check the type of the effect before you apply it.
60
+
61
+ ## Dispatching state effects
62
+
63
+ To apply a state effect to a state field, you need to dispatch it to the editor view as part of a transaction.
64
+
65
+ ```ts
66
+ view.dispatch({
67
+ effects: [addEffect.of(num)],
68
+ });
69
+ ```
70
+
71
+ You can even define a set of helper functions that provide a more familiar API:
72
+
73
+ ```ts
74
+ export function add(view: EditorView, num: number) {
75
+ view.dispatch({
76
+ effects: [addEffect.of(num)],
77
+ });
78
+ }
79
+
80
+ export function subtract(view: EditorView, num: number) {
81
+ view.dispatch({
82
+ effects: [subtractEffect.of(num)],
83
+ });
84
+ }
85
+
86
+ export function reset(view: EditorView) {
87
+ view.dispatch({
88
+ effects: [resetEffect.of(null)],
89
+ });
90
+ }
91
+ ```
92
+
93
+ ## Next steps
94
+
95
+ Provide [[Decorations]] from your state fields to change how to display the document.
docs/obsidian-developer/Plugins/Editor/State management.md ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This page aims to give an introduction to state management for [[Editor extensions|editor extensions]].
2
+
3
+ > [!note]
4
+ > This page aims to distill the official CodeMirror 6 documentation for Obsidian plugin developers. For more detailed information on state management, refer to [State and Updates](https://codemirror.net/docs/guide/#state-and-updates).
5
+
6
+ ## State changes
7
+
8
+ In most applications, you would update state by assigning a new value to a property or variable. As a consequence, the old value is lost forever.
9
+
10
+ ```ts
11
+ let note = "";
12
+ note = "Heading"
13
+ note = "# Heading"
14
+ note = "## Heading" // How to undo this?
15
+ ```
16
+
17
+ To support features like undoing and redoing changes to a user's workspace, applications like Obsidian instead keep a history of all changes that have been made. To undo a change, you can then go back to a point in time before the change was made.
18
+
19
+ | | State |
20
+ |---|------------|
21
+ | 0 | |
22
+ | 1 | Heading |
23
+ | 2 | # Heading |
24
+ | 3 | ## Heading |
25
+
26
+ In TypeScript, you'd then end up with something like this:
27
+
28
+ ```ts
29
+ const changes: ChangeSpec[] = [];
30
+
31
+ changes.push({ from: 0, insert: "Heading" });
32
+ changes.push({ from: 0, insert: "# " });
33
+ changes.push({ from: 0, insert: "#" });
34
+ ```
35
+
36
+ ## Transactions
37
+
38
+ Imagine a feature where you select some text and press the double quote, `"` to surround the selection with quotes on both sides. One way to implement the feature would be to:
39
+
40
+ 1. Insert `"` at the start of the selection.
41
+ 2. Insert `"` at the end of the selection.
42
+
43
+ Notice that the implementation consists of _two_ state changes. If you added these to the undo history, the user would need to undo _twice_, once for each double quote. To avoid this, what if you could group these changes so that they appear as one?
44
+
45
+ For editor extensions, a group of state changes that happen together is called a _transaction_.
46
+
47
+ If you combine what you've learned so far—and if you allow transactions that contain only a single state change—then you can consider state as a _history of transactions_.
48
+
49
+ Bringing it all together to implement the surround feature from before in an editor extension, here's how you'd add, or _dispatch_, a transaction to the editor view:
50
+
51
+ ```ts
52
+ view.dispatch({
53
+ changes: [
54
+ { from: selectionStart, insert: `"` },
55
+ { from: selectionEnd, insert: `"` }
56
+ ]
57
+ });
58
+ ```
59
+
60
+ ## Next steps
61
+
62
+ On this page, you've learned about modeling state as a series of state changes, and how to group them into transactions.
63
+
64
+ To learn how to manage custom state in your editor, refer to [[State fields]].
docs/obsidian-developer/Plugins/Editor/View plugins.md ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ A view plugin is an [[Editor extensions|editor extension]] that gives you access to the editor [[Viewport]].
2
+
3
+ > [!note]
4
+ > This page aims to distill the official CodeMirror 6 documentation for Obsidian plugin developers. For more information on state management, refer to [Affecting the View](https://codemirror.net/docs/guide/#affecting-the-view).
5
+
6
+ ## Prerequisites
7
+
8
+ - Basic understanding of the [[Viewport]].
9
+
10
+ ## Creating a view plugin
11
+
12
+ View plugins are editor extensions that run _after_ the viewport has been recomputed. While this means that they can access the viewport, it also means that a view plugin can't make any changes that would impact the viewport. For example, by inserting blocks or line breaks into the document.
13
+
14
+ > [!tip]
15
+ > If you want to make changes that impact the vertical layout of the editor, by for example inserting blocks and line breaks, you need to use a [[State fields|state field]].
16
+
17
+ To create a view plugin, create a class that implements [PluginValue](https://codemirror.net/docs/ref/#view.PluginValue) and pass it to the [ViewPlugin.fromClass()](https://codemirror.net/docs/ref/#view.ViewPlugin^fromClass) function.
18
+
19
+ ```ts
20
+ import {
21
+ ViewUpdate,
22
+ PluginValue,
23
+ EditorView,
24
+ ViewPlugin,
25
+ } from "@codemirror/view";
26
+
27
+ class ExamplePlugin implements PluginValue {
28
+ constructor(view: EditorView) {
29
+ // ...
30
+ }
31
+
32
+ update(update: ViewUpdate) {
33
+ // ...
34
+ }
35
+
36
+ destroy() {
37
+ // ...
38
+ }
39
+ }
40
+
41
+ export const examplePlugin = ViewPlugin.fromClass(ExamplePlugin);
42
+ ```
43
+
44
+ The three methods of the view plugin control its lifecycle:
45
+
46
+ - `constructor()` initializes the plugin.
47
+ - `update()` updates your plugin when something has changed, for example when the user entered or selected some text.
48
+ - `destroy()` cleans up after the plugin.
49
+
50
+ While the view plugin in the example works, it doesn't do much. If you want to better understand what causes the plugin to update, you can add a `console.log(update);` line to the `update()` method to print all updates to the console.
51
+
52
+ ## Next steps
53
+
54
+ Provide [[Decorations]] from your view plugin to change how to display the document.
docs/obsidian-developer/Plugins/Editor/Viewport.md ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The Obsidian editor supports [huge documents](https://codemirror.net/examples/million/) with millions of lines. One of the reasons why this is possible, is because the editor only renders what's visible (and a little bit more).
2
+
3
+ Imagine that you want to edit a document that is too big to fit on your monitor. The Obsidian editor creates a "window" that moves across the document, only rendering the content within the window (and ignoring what's outside). This window is known as the editor's _viewport_.
4
+
5
+ ![Viewport](viewport.svg)
6
+
7
+ Whenever the user scrolls through the document, or when the document itself changes, the viewport becomes out-of-date and needs to be recomputed.
8
+
9
+ If you want to build an editor extension that depends on the viewport, refer to [[View plugins]].
10
+
11
+ > [!note]
12
+ > This page aims to distill the official CodeMirror 6 documentation for Obsidian plugin developers. For more information on state management, refer to [Viewport](https://codemirror.net/docs/guide/#viewport).
docs/obsidian-developer/Plugins/Events.md ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Many of the interfaces in the Obsidian lets you subscribe to events throughout the application, for example when the user makes changes to a file.
2
+
3
+ Any registered event handlers need to be detached whenever the plugin unloads. The safest way to make sure this happens is to use the [[registerEvent|registerEvent()]] method.
4
+
5
+ ```ts
6
+ import { Plugin } from "obsidian";
7
+
8
+ export default class ExamplePlugin extends Plugin {
9
+ async onload() {
10
+ this.registerEvent(this.app.vault.on('create', () => {
11
+ console.log('a new file has entered the arena')
12
+ }));
13
+ }
14
+ }
15
+ ```
16
+
17
+ ## Timing events
18
+
19
+ If you want to repeatedly call a function with a fixed delay, use the [`window.setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) function with the [[registerInterval|registerInterval()]] method.
20
+
21
+ The following example displays the current time in the status bar, updated every second:
22
+
23
+ ```ts
24
+ import { moment, Plugin } from "obsidian";
25
+
26
+ export default class ExamplePlugin extends Plugin {
27
+ statusBar: HTMLElement;
28
+
29
+ async onload() {
30
+ this.statusBar = this.addStatusBarItem();
31
+
32
+ this.updateStatusBar();
33
+
34
+ this.registerInterval(
35
+ window.setInterval(() => this.updateStatusBar(), 1000)
36
+ );
37
+ }
38
+
39
+ updateStatusBar() {
40
+ this.statusBar.setText(moment().format("H:mm:ss"));
41
+ }
42
+ }
43
+ ```
44
+
45
+ > [!tip] Date and time
46
+ > [Moment](https://momentjs.com/) is a popular JavaScript library for working with dates and time. Obsidian uses Moment internally, so you don't need to install it yourself. You can import it from the Obsidian API instead:
47
+ >
48
+ > ```ts
49
+ > import { moment } from "obsidian";
50
+ > ```
docs/obsidian-developer/Plugins/Getting started/Anatomy of a plugin.md ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The [[Plugin|Plugin]] class defines the lifecycle of a plugin and exposes the operations available to all plugins:
2
+
3
+ ```ts
4
+ import { Plugin } from "obsidian";
5
+
6
+ export default class ExamplePlugin extends Plugin {
7
+ async onload() {
8
+ // Configure resources needed by the plugin.
9
+ }
10
+ async onunload() {
11
+ // Release any resources configured by the plugin.
12
+ }
13
+ }
14
+ ```
15
+
16
+ ## Plugin lifecycle
17
+
18
+ [[onload|onload()]] runs whenever the user starts using the plugin in Obsidian. This is where you'll configure most of the plugin's capabilities.
19
+
20
+ [[onunload|onunload()]] runs when the plugin is disabled. Any resources that your plugin is using must be released here to avoid affecting the performance of Obsidian after your plugin has been disabled.
21
+
22
+ To better understand when these methods are called, you can print a message to the console whenever the plugin loads and unloads. The console is a valuable tool that lets developers monitor the status of their code.
23
+
24
+ To view the console:
25
+
26
+ 1. Toggle the Developer Tools by pressing Ctrl+Shift+I in Windows and Linux, or Cmd-Option-I on macOS.
27
+ 2. Click on the Console tab in the Developer Tools window.
28
+
29
+ ```ts
30
+ import { Plugin } from "obsidian";
31
+
32
+ export default class ExamplePlugin extends Plugin {
33
+ async onload() {
34
+ console.log('loading plugin')
35
+ }
36
+ async onunload() {
37
+ console.log('unloading plugin')
38
+ }
39
+ }
40
+ ```
docs/obsidian-developer/Plugins/Getting started/Build a plugin.md ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Plugins let you extend Obsidian with your own features to create a custom note-taking experience.
2
+
3
+ In this tutorial, you'll compile a sample plugin from source code and load it into Obsidian.
4
+
5
+ ## What you'll learn
6
+
7
+ After you've completed this tutorial, you'll be able to:
8
+
9
+ - Configure an environment for developing Obsidian plugins.
10
+ - Compile a plugin from source code.
11
+ - Reload a plugin after making changes to it.
12
+
13
+ ## Prerequisites
14
+
15
+ To complete this tutorial, you'll need:
16
+
17
+ - [Git](https://git-scm.com/) installed on your local machine.
18
+ - A local development environment for [Node.js](https://Node.js.org/en/about/).
19
+ - A code editor, such as [Visual Studio Code](https://code.visualstudio.com/).
20
+
21
+ ## Before you start
22
+
23
+ When developing plugins, one mistake can lead to unintended changes to your vault. To prevent data loss, you should never develop plugins in your main vault. Always use a separate vault dedicated to plugin development.
24
+
25
+ [Create an empty vault](https://help.obsidian.md/Getting+started/Create+a+vault#Create+empty+vault).
26
+
27
+ ## Step 1: Download the sample plugin
28
+
29
+ In this step, you'll download a sample plugin to the `plugins` directory in your vault's [`.obsidian` directory](https://help.obsidian.md/Advanced+topics/How+Obsidian+stores+data#Per+vault+data) so that Obsidian can find it.
30
+
31
+ The sample plugin you'll use in this tutorial is available in a [GitHub repository](https://github.com/obsidianmd/obsidian-sample-plugin).
32
+
33
+ 1. Open a terminal window and change the project directory to the `plugins` directory.
34
+
35
+ ```bash
36
+ cd path/to/vault
37
+ mkdir .obsidian/plugins
38
+ cd .obsidian/plugins
39
+ ```
40
+
41
+ 2. Clone the sample plugin using Git.
42
+
43
+ ```bash
44
+ git clone https://github.com/obsidianmd/obsidian-sample-plugin.git
45
+ ```
46
+
47
+ > [!tip] GitHub template repository
48
+ > The repository for the sample plugin is a GitHub template repository, which means you can create your own repository from the sample plugin. To learn how, refer to [Creating a repository from a template](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template#creating-a-repository-from-a-template).
49
+ >
50
+ > Remember to use the URL of your own repository when cloning the sample plugin.
51
+
52
+ ## Step 2: Build the plugin
53
+
54
+ In this step, you'll compile the sample plugin so that Obsidian can load it.
55
+
56
+ 1. Navigate to the plugin directory.
57
+
58
+ ```bash
59
+ cd obsidian-sample-plugin
60
+ ```
61
+
62
+ 2. Install dependencies.
63
+
64
+ ```bash
65
+ npm install
66
+ ```
67
+
68
+ 3. Compile the source code. The following command keeps running in the terminal and rebuilds the plugin when you modify the source code.
69
+
70
+ ```bash
71
+ npm run dev
72
+ ```
73
+
74
+ Notice that the plugin directory now has a `main.js` file that contains a compiled version of the plugin.
75
+
76
+ ## Step 3: Enable the plugin
77
+
78
+ To load a plugin in Obsidian, you first need to enable it.
79
+
80
+ 1. In Obsidian, open **Settings**.
81
+ 2. In the side menu, select **Community plugins**.
82
+ 3. Select **Turn on community plugins**.
83
+ 4. Under **Installed plugins**, enable the **Sample Plugin** by selecting the toggle button next to it.
84
+
85
+ You're now ready to use the plugin in Obsidian. Next, we'll make some changes to the plugin.
86
+
87
+ ## Step 4: Update the plugin manifest
88
+
89
+ In this step, you'll rename the plugin by updating the plugin manifest, `manifest.json`. The manifest contains information about your plugin, such as its name and description.
90
+
91
+ 1. Open `manifest.json` in your code editor.
92
+ 2. Change `id` to a unique identifier, such as `"hello-world"`.
93
+ 3. Change `name` to a human-friendly name, such as `"Hello world"`.
94
+ 4. Restart Obsidian to load the new changes to the plugin manifest.
95
+
96
+ Go back to **Installed plugins** and notice that the name of the plugin has been updated to reflect the changes you made.
97
+
98
+ Remember to restart Obsidian whenever you make changes to `manifest.json`.
99
+
100
+ ## Step 5: Update the source code
101
+
102
+ To let the user interact with your plugin, add a _ribbon icon_ that greets the user when they select it.
103
+
104
+ 1. Open `main.ts` in your code editor.
105
+ 2. Rename the plugin class from `MyPlugin` to `HelloWorldPlugin`.
106
+ 3. Import `Notice` from the `obsidian` package.
107
+
108
+ ```ts
109
+ import { Notice, Plugin } from "obsidian";
110
+ ```
111
+
112
+ 4. In the `onload()` method, add the following code:
113
+
114
+ ```ts
115
+ this.addRibbonIcon('dice', 'Greet', () => {
116
+ new Notice('Hello, world!');
117
+ });
118
+ ```
119
+
120
+ 5. In the **Command palette**, select **Reload app without saving** to reload the plugin.
121
+
122
+ You can now see a dice icon in the ribbon on the left side of the Obsidian window. Select it to display a message in the upper-right corner.
123
+
124
+ Remember, you need to **reload your plugin after changing the source code**, either by disabling it then enabling it again in the community plugins panel, or using the command palette as detailed in part 5 of this step.
125
+
126
+ > [!tip] Hot reloading
127
+ > Install the [Hot-Reload](https://github.com/pjeby/hot-reload) plugin to automatically reload your plugin while developing.
128
+
129
+ ## Conclusion
130
+
131
+ In this tutorial, you've built your first Obsidian plugin using the TypeScript API. You've modified the plugin and reloaded it to reflect the changes inside Obsidian.
docs/obsidian-developer/Plugins/Getting started/Development workflow.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Whenever you make a change to the plugin source code, the plugin needs to be reloaded. You can reload the plugin by quitting Obsidian and starting it again, but that gets tiring quickly.
2
+
3
+ ## Reload plugin inside Obsidian
4
+
5
+ You can reload the plugin by re-enabling it in the list of installed plugins:
6
+
7
+ 1. Open **Preferences**.
8
+ 2. Click **Community plugins**.
9
+ 3. Find your plugin under **Installed plugins**.
10
+ 4. Toggle the switch off to disable the plugin.
11
+ 5. Toggle the switch on to enable the plugin.
12
+
13
+ You're now running the updated version of your plugin.
14
+
15
+ ## Reload plugin on file changes
16
+
17
+ The [Hot-Reload](https://github.com/pjeby/hot-reload) plugin reloads your plugin whenever the source code changes.
18
+
19
+ For more information, check out the [forum announcement](https://forum.obsidian.md/t/plugin-release-for-developers-hot-reload-the-plugin-s-youre-developing/12185).
docs/obsidian-developer/Plugins/Getting started/Mobile development.md ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Learn how you can develop your plugin for mobile devices.
2
+
3
+ ## Emulate mobile device on desktop
4
+
5
+ You can emulate Obsidian running a mobile device directly from the Developer Tools.
6
+
7
+ 1. Open the **Developer Tools**.
8
+ 2. Select the **Console** tab.
9
+ 3. Enter the following and then press `Enter`.
10
+
11
+ ```ts
12
+ this.app.emulateMobile(true);
13
+ ```
14
+
15
+ To disable mobile emulation, enter the following and press `Enter`:
16
+
17
+ ```ts
18
+ this.app.emulateMobile(false);
19
+ ```
20
+
21
+
22
+ > [!tip]
23
+ > To instead toggle mobile emulation back and forth, you can use the `this.app.isMobile` flag:
24
+ >
25
+ > ```ts
26
+ > this.app.emulateMobile(!this.app.isMobile);
27
+ > ```
28
+
29
+ ## Inspecting the webview on the actual mobile device
30
+
31
+ ### Android
32
+
33
+ You can inspect Obsidian running on an Android device if you enable USB Debugging in Developer settings of Android. Then go to a chromium based browser on your desktop/laptop and navigate to chrome://inspect/. If you did everything right, if you have your phone/tablet connected to your PC via USB and the browser open at that link you should see your device pop up and it will let you run the usual devtools from there on it.
34
+
35
+ ### iOS
36
+
37
+ You can inspect Obsidian on an iOS device running 16.4 or later and a macOS based computer. Instructions on how to set it up can be found here: https://webkit.org/web-inspector/enabling-web-inspector/
38
+
39
+ ## Platform-specific features
40
+
41
+ To detect the platform your plugin is running on, you can use `Platform`:
42
+
43
+ ```ts
44
+ import { Platform } from "obsidian";
45
+
46
+ if (Platform.isIosApp) {
47
+ // ...
48
+ }
49
+
50
+ if (Platform.isAndroidApp) {
51
+ // ...
52
+ }
53
+ ```
54
+
55
+ ## Disable your plugin on mobile devices
56
+
57
+ If your plugin requires the Node.js or Electron API, you can prevent users from installing the plugin on mobile devices.
58
+
59
+ To only support the desktop app, set `isDesktopOnly` to `true` in the [[Manifest]].
60
+
61
+ ## Troubleshooting
62
+
63
+ This section lists common issues when developing for mobile devices.
64
+
65
+ ### Node and Electron APIs
66
+
67
+ The Node.js API and the Electron API aren't available on mobile devices. Any calls to these libraries result cause your plugin to crash.
68
+
69
+ ### Lookbehind in regular expressions
70
+
71
+ Lookbehind in regular expressions is only supported on iOS 16.4 and above, and some iPhone and iPad users may still use earlier versions. To implement a fallback for iOS users, either refer to [[#Platform-specific features]], or use a JavaScript library to detect specific browser versions.
72
+
73
+ Refer to [Can I Use](https://caniuse.com/js-regexp-lookbehind) for more information and exact version statistics. Look for "Safari on iOS".
docs/obsidian-developer/Plugins/Getting started/Use React in your plugin.md ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ In this guide, you'll configure your plugin to use [React](https://react.dev/). It assumes that you already have a plugin with a [[Views|custom view]] that you want to convert to use React.
2
+
3
+ While you don't need to use a separate framework to build a plugin, there are a few reasons why you'd want to use React:
4
+
5
+ - You have existing experience of React and want to use a familiar technology.
6
+ - You have existing React components that you want to reuse in your plugin.
7
+ - Your plugin requires complex state management or other features that can be cumbersome to implement with regular [[HTML elements]].
8
+
9
+ ## Configure your plugin
10
+
11
+ 1. Add React to your plugin dependencies:
12
+
13
+ ```bash
14
+ npm install react react-dom
15
+ ```
16
+
17
+ 2. Add type definitions for React:
18
+
19
+ ```bash
20
+ npm install --save-dev @types/react @types/react-dom
21
+ ```
22
+
23
+ 3. In `tsconfig.json`, enable JSX support on the `compilerOptions` object:
24
+
25
+ ```ts
26
+ {
27
+ "compilerOptions": {
28
+ "jsx": "preserve"
29
+ }
30
+ }
31
+ ```
32
+
33
+ ## Create a React component
34
+
35
+ Create a new file called `ReactView.tsx` in the plugin root directory, with the following content:
36
+
37
+ ```tsx title="ReactView.tsx"
38
+ export const ReactView = () => {
39
+ return <h4>Hello, React!</h4>;
40
+ };
41
+ ```
42
+
43
+ ## Mount the React component
44
+
45
+ To use the React component, it needs to be mounted on a [[HTML elements]]. The following example mounts the `ReactView` component on the `this.containerEl.children[1]` element:
46
+
47
+ ```tsx
48
+ import { StrictMode } from "react";
49
+ import { ItemView, WorkspaceLeaf } from "obsidian";
50
+ import { Root, createRoot } from "react-dom/client";
51
+ import { ReactView } from "./ReactView";
52
+
53
+ const VIEW_TYPE_EXAMPLE = "example-view";
54
+
55
+ class ExampleView extends ItemView {
56
+ root: Root | null = null;
57
+
58
+ constructor(leaf: WorkspaceLeaf) {
59
+ super(leaf);
60
+ }
61
+
62
+ getViewType() {
63
+ return VIEW_TYPE_EXAMPLE;
64
+ }
65
+
66
+ getDisplayText() {
67
+ return "Example view";
68
+ }
69
+
70
+ async onOpen() {
71
+ this.root = createRoot(this.containerEl.children[1]);
72
+ this.root.render(
73
+ <StrictMode>
74
+ <ReactView />,
75
+ </StrictMode>,
76
+ );
77
+ }
78
+
79
+ async onClose() {
80
+ this.root?.unmount();
81
+ }
82
+ }
83
+ ```
84
+
85
+ For more information on `createRoot` and `unmount()`, refer to the documentation on [ReactDOM](https://react.dev/reference/react-dom/client/createRoot#root-render).
86
+
87
+ You can mount your React component on any `HTMLElement`, for example [[Plugins/User interface/Status bar|status bar items]]. Just make sure to clean up properly by calling `this.root.unmount()` when you're done.
88
+
89
+ ## Create an App context
90
+
91
+ If you want to access the [[Reference/TypeScript API/App/App|App]] object from one of your React components, you need to pass it as a dependency. As your plugin grows, even though you're only using the `App` object in a few places, you start passing it through the whole component tree.
92
+
93
+ Another alternative is to create a React context for the app to make it globally available to all components inside your React view.
94
+
95
+ 1. Use `createContext()` to create a new app context.
96
+
97
+ ```tsx title="context.ts"
98
+ import { createContext } from "react";
99
+ import { App } from "obsidian";
100
+
101
+ export const AppContext = createContext<App | undefined>(undefined);
102
+ ```
103
+
104
+ 2. Wrap the `ReactView` with a context provider and pass the app as the value.
105
+
106
+ ```tsx title="view.tsx"
107
+ this.root = createRoot(this.containerEl.children[1]);
108
+ this.root.render(
109
+ <AppContext.Provider value={this.app}>
110
+ <ReactView />
111
+ </AppContext.Provider>
112
+ );
113
+ ```
114
+
115
+ 3. Create a custom hook to make it easier to use the context in your components.
116
+
117
+ ```tsx title="hooks.ts"
118
+ import { useContext } from "react";
119
+ import { AppContext } from "./context";
120
+
121
+ export const useApp = (): App | undefined => {
122
+ return useContext(AppContext);
123
+ };
124
+ ```
125
+
126
+ 4. Use the hook in any React component within `ReactView` to access the app.
127
+
128
+ ```tsx title="ReactView.tsx"
129
+ import { useApp } from "./hooks";
130
+
131
+ export const ReactView = () => {
132
+ const { vault } = useApp();
133
+
134
+ return <h4>{vault.getName()}</h4>;
135
+ };
136
+ ```
137
+
138
+ For more information, refer to the React documentation for [Passing Data Deeply with Context](https://react.dev/learn/passing-data-deeply-with-context) and [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks).
docs/obsidian-developer/Plugins/Getting started/Use Svelte in your plugin.md ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This guide explains how to configure your plugin to use [Svelte](https://svelte.dev/), a light-weight alternative to traditional frameworks like React and Vue.
2
+
3
+ Svelte is built around a compiler that preprocesses your code and outputs vanilla JavaScript, which means it doesn't need to load any libraries at run time. This also means that it doesn't need a virtual DOM to track state changes, which allows your plugin to run with minimal additional overhead.
4
+
5
+ If you want to learn more about Svelte, and how to use it, refer to the [tutorial](https://svelte.dev/tutorial/basics) and the [documentation](https://svelte.dev/docs).
6
+
7
+ This guide assumes that you've finished [[Build a plugin]].
8
+
9
+ > [!tip] Visual Studio Code
10
+ > Svelte has an [official Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) that enables syntax highlighting and rich IntelliSense in Svelte components.
11
+
12
+ ## Configure your plugin
13
+
14
+ To build a Svelte application, you need to install the dependencies and configure your plugin to compile code written using Svelte.
15
+
16
+ 1. Add Svelte to your plugin dependencies:
17
+
18
+ ```bash
19
+ npm install --save-dev svelte svelte-preprocess @tsconfig/svelte esbuild-svelte
20
+ ```
21
+
22
+ 2. Extend the `tsconfig.json` to enable additional type checking for common Svelte issues. The `types` property is important for TypeScript to recognize `.svelte` files.
23
+
24
+ ```json
25
+ {
26
+ "extends": "@tsconfig/svelte/tsconfig.json",
27
+ "compilerOptions": {
28
+ "types": ["svelte", "node"],
29
+
30
+ // ...
31
+ }
32
+ }
33
+ ```
34
+
35
+ 3. Remove the following line from your `tsconfig.json` as it conflicts with the Svelte configuration.
36
+
37
+ ```json
38
+ "inlineSourceMap": true,
39
+ ```
40
+
41
+ 4. In `esbuild.config.mjs`, add the following imports to the top of the file:
42
+
43
+ ```js
44
+ import esbuildSvelte from "esbuild-svelte";
45
+ import sveltePreprocess from "svelte-preprocess";
46
+ ```
47
+
48
+ 5. Add Svelte to the list of plugins.
49
+
50
+ ```js
51
+ esbuild
52
+ .build({
53
+ plugins: [
54
+ esbuildSvelte({
55
+ compilerOptions: { css: true },
56
+ preprocess: sveltePreprocess(),
57
+ }),
58
+ ],
59
+ // ...
60
+ })
61
+ .catch(() => process.exit(1));
62
+ ```
63
+
64
+ ## Create a Svelte component
65
+
66
+ In the root directory of the plugin, create a new file called `Component.svelte`:
67
+
68
+ ```tsx
69
+ <script lang="ts">
70
+ export let variable: number;
71
+ </script>
72
+
73
+ <div class="number">
74
+ <span>My number is {variable}!</span>
75
+ </div>
76
+
77
+ <style>
78
+ .number {
79
+ color: red;
80
+ }
81
+ </style>
82
+ ```
83
+
84
+ ## Mount the Svelte component
85
+
86
+ To use the Svelte component, it needs to be mounted on an existing [[HTML elements|HTML element]]. For example, if you are mounting on a custom [[ItemView|ItemView]] in Obsidian:
87
+
88
+ ```ts
89
+ import { ItemView, WorkspaceLeaf } from "obsidian";
90
+
91
+ import Component from "./Component.svelte";
92
+
93
+ export const VIEW_TYPE_EXAMPLE = "example-view";
94
+
95
+ export class ExampleView extends ItemView {
96
+ component: Component;
97
+
98
+ constructor(leaf: WorkspaceLeaf) {
99
+ super(leaf);
100
+ }
101
+
102
+ getViewType() {
103
+ return VIEW_TYPE_EXAMPLE;
104
+ }
105
+
106
+ getDisplayText() {
107
+ return "Example view";
108
+ }
109
+
110
+ async onOpen() {
111
+ this.component = new Component({
112
+ target: this.contentEl,
113
+ props: {
114
+ variable: 1
115
+ }
116
+ });
117
+ }
118
+
119
+ async onClose() {
120
+ this.component.$destroy();
121
+ }
122
+ }
123
+ ```
124
+
125
+ > [!info]
126
+ > Svelte requires at least TypeScript 4.5. If you see the following error when you build the plugin, you need to upgrade TypeScript to a more recent version.
127
+ >
128
+ > ```plain
129
+ > error TS5023: Unknown compiler option 'preserveValueImports'.
130
+ > ```
131
+ >
132
+ > To fix the error, run the following in your terminal:
133
+ >
134
+ > ```bash
135
+ > npm update typescript@~4.5.0
136
+ > ```
137
+
138
+ ## Create a Svelte store
139
+
140
+ To create a store for your plugin and access it from within a generic Svelte component instead of passing the plugin as a prop, follow these steps:
141
+
142
+ 1. Create a file called `store.ts`:
143
+
144
+ ```jsx
145
+ import { writable } from "svelte/store";
146
+ import type ExamplePlugin from "./main";
147
+
148
+ const plugin = writable<ExamplePlugin>();
149
+ export default { plugin };
150
+ ```
151
+
152
+ 2. Configure the store:
153
+
154
+ ```ts
155
+ import { ItemView, WorkspaceLeaf } from "obsidian";
156
+ import type ExamplePlugin from "./main";
157
+ import store from "./store";
158
+ import Component from "./Component.svelte";
159
+
160
+ const VIEW_TYPE_EXAMPLE = "example-view";
161
+
162
+ class ExampleView extends ItemView {
163
+ // ...
164
+
165
+ async onOpen() {
166
+ store.plugin.set(this.plugin);
167
+
168
+ this.component = new Component({
169
+ target: this.contentEl,
170
+ props: {
171
+ variable: 1
172
+ }
173
+ });
174
+ }
175
+ }
176
+ ```
177
+
178
+ 3. To use the store in your component:
179
+
180
+ ```jsx
181
+ <script lang="ts">
182
+ import type MyPlugin from "./main";
183
+
184
+ let plugin: MyPlugin;
185
+ store.plugin.subscribe((p) => (plugin = p));
186
+ </script>
187
+ ```
docs/obsidian-developer/Plugins/Releasing/Beta-testing plugins.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ Before you [[Submit your plugin|submit your plugin]], you may want to let users try it out first. While Obsidian doesn't officially support beta releases, we recommend that you use the [BRAT](https://github.com/TfTHacker/obsidian42-brat) plugin to distribute your plugin to beta testers before it's been published.
2
+
3
+ For more information, refer to the [BRAT](https://github.com/TfTHacker/obsidian42-brat/blob/main/README.md) documentation.
docs/obsidian-developer/Plugins/Releasing/Plugin guidelines.md ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This page lists common review comments plugin authors get when submitting their plugin.
2
+
3
+ While the guidelines on this page are recommendations, depending on their severity, we may still require you to address any violations.
4
+
5
+ > [!important] Policies for plugin developers
6
+ > Make sure that you've read our [[Developer policies]] as well as the [[Submission requirements for plugins]].
7
+
8
+ ## General
9
+
10
+ ### Avoid using global app instance
11
+
12
+ Avoid using the global app object, `app` (or `window.app`). Instead, use the reference provided by your plugin instance, `this.app`.
13
+
14
+ The global app object is intended for debugging purposes and might be removed in the future.
15
+
16
+ ## UI text
17
+
18
+ This section lists guidelines for formatting text in the user interface, such as settings, commands, and buttons.
19
+
20
+ The example below from **Settings → Appearance** demonstrates the guidelines for text in the user interface.
21
+
22
+ ![[settings-headings.png]]
23
+
24
+ 1. [[#Only use headings under settings if you have more than one section.|General settings are at the top and don't have a heading]].
25
+ 2. [[#Avoid "settings" in settings headings|Section headings don't have "settings" in the heading text]].
26
+ 3. [[#Use Sentence case in UI]].
27
+
28
+ For more information on writing and formatting text for Obsidian, refer to our [Style guide](https://help.obsidian.md/Contributing+to+Obsidian/Style+guide).
29
+
30
+ ### Only use headings under settings if you have more than one section.
31
+
32
+ Avoid adding a top-level heading in the settings tab, such as "General", "Settings", or the name of your plugin.
33
+
34
+ If you have more than one section under settings, and one contains general settings, keep them at the top without adding a heading.
35
+
36
+ For example, look at the settings under **Settings → Appearance**:
37
+
38
+ ### Avoid "settings" in settings headings
39
+
40
+ In the settings tab, you can add headings to organize settings. Avoid including the word "settings" to these headings. Since everything in under the settings tab is settings, repeating it for every heading becomes redundant.
41
+
42
+ - Prefer "Advanced" over "Advanced settings".
43
+ - Prefer "Templates" over "Settings for templates".
44
+
45
+ ### Use sentence case in UI
46
+
47
+ Any text in UI elements should be using [Sentence case](https://en.wiktionary.org/wiki/sentence_case) instead of [Title Case](https://en.wikipedia.org/wiki/Title_case), where only the first word in a sentence, and proper nouns, should be capitalized.
48
+
49
+ - Prefer "Template folder location" over "Template Folder Location".
50
+ - Prefer "Create new note" over "Create New Note".
51
+
52
+ ## Security
53
+
54
+ ### Avoid `innerHTML`, `outerHTML` and `insertAdjacentHTML`
55
+
56
+ Building DOM elements from user-defined input, using `innerHTML`, `outerHTML` and `insertAdjacentHTML` can pose a security risk.
57
+
58
+ The following example builds a DOM element using a string that contains user input, `${name}`. `name` can contain other DOM elements, such as `<script>alert()</script>`, and can allow a potential attacker to execute arbitrary code on the user's computer.
59
+
60
+ ```ts
61
+ function showName(name: string) {
62
+ let containerElement = document.querySelector('.my-container');
63
+ // DON'T DO THIS
64
+ containerElement.innerHTML = `<div class="my-class"><b>Your name is: </b>${name}</div>`;
65
+ }
66
+ ```
67
+
68
+ Instead, use the DOM API or the Obsidian helper functions, such as `createEl()`, `createDiv()` and `createSpan()` to build the DOM element programmatically. For more information, refer to [[HTML elements]].
69
+
70
+ ## Resource management
71
+
72
+ ### Clean up resources when plugin unloads
73
+
74
+ Any resources created by the plugin, such as event listeners, must be destroyed or released when the plugin unloads.
75
+
76
+ When possible, use methods like [[registerEvent|registerEvent()]] or [[addCommand|addCommand()]] to automatically clean up resources when the plugin unloads.
77
+
78
+ ```ts
79
+ export default class MyPlugin extends Plugin {
80
+ onload() {
81
+ this.registerEvent(this.app.vault.on("create", this.onCreate));
82
+ }
83
+
84
+ onCreate: (file: TAbstractFile) => {
85
+ // ...
86
+ }
87
+ }
88
+ ```
89
+
90
+ > [!note]
91
+ > You don't need to clean up resources that are guaranteed to be removed when your plugin unloads. For example, if you register a `mouseenter` listener on a DOM element, the event listener will be garbage-collected when the element goes out of scope.
92
+
93
+ ### Don't detach leaves in `onunload`
94
+
95
+ When the user updates your plugin, any open leaves will be reinitialized at their original position, regardless of where the user had moved them.
96
+
97
+ ## Commands
98
+
99
+ ### Avoid setting a default hotkey for commands
100
+
101
+ Setting a default hotkey may lead to conflicts between plugins and may override hotkeys that the user has already configured.
102
+
103
+ It's also difficult to choose a default hotkey that is available on all operating systems.
104
+
105
+ ### Use the appropriate callback type for commands
106
+
107
+ When you add a command in your plugin, use the appropriate callback type.
108
+
109
+ - Use `callback` if the command runs unconditionally.
110
+ - Use `checkCallback` if the command only runs under certain conditions.
111
+
112
+ If the command requires an open and active Markdown editor, use `editorCallback`, or the corresponding `editorCheckCallback`.
113
+
114
+ ## Workspace
115
+
116
+ ### Avoid accessing `workspace.activeLeaf` directly
117
+
118
+ If you want to access the active view, use [[getActiveViewOfType|getActiveViewOfType()]] instead:
119
+
120
+ ```ts
121
+ const view = this.app.workspace.getActiveViewOfType(MarkdownView);
122
+
123
+ // getActiveViewOfType will return null if the active view is null, or if it's not a MarkdownView.
124
+ if (view) {
125
+ // ...
126
+ }
127
+ ```
128
+
129
+ If you want to access the editor in the active note, use `activeEditor` instead:
130
+
131
+ ```ts
132
+ const editor = this.app.workspace.activeEditor;
133
+ ```
134
+
135
+ ### Avoid managing references to custom views
136
+
137
+ Managing references to custom view can cause memory leaks or unintended consequences.
138
+
139
+ **Don't** do this:
140
+
141
+ ```ts
142
+ this.registerViewType(MY_VIEW_TYPE, () => this.view = new MyCustomView());
143
+ ```
144
+
145
+ Do this instead:
146
+
147
+ ```ts
148
+ this.registerViewType(MY_VIEW_TYPE, () => new MyCustomView());
149
+ ```
150
+
151
+ To access the view from your plugin, use `Workspace.getActiveLeavesOfType()`:
152
+
153
+ ```ts
154
+ for (let leaf of app.workspace.getActiveLeavesOfType(MY_VIEW_TYPE)) {
155
+ let view = leaf.view;
156
+ if (view instanceof MyCustomView) {
157
+ // ...
158
+ }
159
+ }
160
+ ```
161
+
162
+ ## Vault
163
+
164
+ ### Prefer the Editor API instead of `Vault.modify`
165
+
166
+ If you want to edit an active note, use the [[Editor]] interface instead of [[Vault/modify|Vault.modify()]].
167
+
168
+ Editor maintains information about the active note, such as cursor position, selection, and folded content. When you use [[Vault/modify|Vault.modify()]] to edit the note, all that information is lost, which leads to a poor experience for the user.
169
+
170
+ Editor is also more efficient when making small changes to parts of the note.
171
+
172
+ Only use [[Vault/modify|Vault.modify()]] if you're editing a file in the background.
173
+
174
+ ### Prefer the Vault API over the Adapter API
175
+
176
+ Obsidian exposes two APIs for file operations: the Vault API (`app.vault`) and the Adapter API (`app.vault.adapter`).
177
+
178
+ While the file operations in the Adapter API are often more familiar to many developers, the Vault API has two main advantages over the adapter.
179
+
180
+ - **Performance:** The Vault API has a caching layer that can speed up file reads when the file is already known to Obsidian.
181
+ - **Safety:** The Vault API performs file operations serially to avoid any race conditions, for example when reading a file that is being written to at the same time.
182
+
183
+ ### Avoid iterating all files to find a file by its path
184
+
185
+ This is inefficient, especially for large vaults. Use [[Vault/getAbstractFileByPath|getAbstractFileByPath()]] instead.
186
+
187
+ **Don't** do this:
188
+
189
+ ```ts
190
+ vault.getAllFiles().find(file => file.path === filePath)
191
+ ```
192
+
193
+ Do this instead:
194
+
195
+ ```ts
196
+ const filePath = 'folder/file.md';
197
+
198
+ const file = app.vault.getAbstractFileByPath(filePath);
199
+
200
+ // Check if it exists and is of the correct type
201
+ if (file instanceof TFile) {
202
+ // file is automatically casted to TFile within this scope.
203
+ }
204
+ ```
205
+
206
+ ### Use `normalizePath()` to clean up user-defined paths
207
+
208
+ Use [[normalizePath|normalizePath()]] whenever you accept user-defined paths to files or folders in the vault, or when you construct your own paths in the plugin code.
209
+
210
+ `normalizePath()` takes a path and scrubs it to be safe for the file system and for cross-platform use. This function:
211
+
212
+ - Cleans up the use of forward and backward slashes, such as replacing 1 or more of `\` or `/` with a single `/`.
213
+ - Removes leading and trailing forward and backward slashes.
214
+ - Replaces any non-breaking spaces, `\u00A0`, with a regular space.
215
+ - Runs the path through [String.prototype.normalize](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize).
216
+
217
+ ```ts
218
+ import { normalizePath } from "obsidian";
219
+ const pathToPlugin = normalizePath(app.vault.configDir + "//plugins/my-plugin");
220
+ // pathToPlugin contains ".obsidian/plugins/my-plugin" not .obsidian//plugins/my-plugin
221
+ ```
222
+
223
+ ## Editor
224
+
225
+ ### Change or reconfigure editor extensions
226
+
227
+ If you want to change or reconfigure an [[Editor extensions|editor extension]] after you've registered using [[registerEditorExtension|registerEditorExtension()]], use [[updateOptions|updateOptions()]] to update all editors.
228
+
229
+ ```ts
230
+ class MyPlugin extends Plugin {
231
+ private editorExtension: Extension[] = [];
232
+
233
+ onload() {
234
+ //...
235
+
236
+ this.registerEditorExtension(this.editorExtension);
237
+ }
238
+
239
+ updateEditorExtension() {
240
+ // Empty the array while keeping the same reference
241
+ // (Don't create a new array here)
242
+ this.editorExtension.length = 0;
243
+
244
+ // Create new editor extension
245
+ let myNewExtension = this.createEditorExtension();
246
+ // Add it to the array
247
+ this.editorExtension.push(myNewExtension);
248
+
249
+ // Flush the changes to all editors
250
+ this.app.workspace.updateOptions();
251
+ }
252
+ }
253
+
254
+ ```
255
+
256
+ ## TypeScript
257
+
258
+ ### Prefer `const` and `let` over `var`
259
+
260
+ For more information, refer to [4 Reasons Why var is Considered Obsolete in Modern JavaScript](https://javascript.plainenglish.io/4-reasons-why-var-is-considered-obsolete-in-modern-javascript-a30296b5f08f).
261
+
262
+ ### Prefer async/await over Promise
263
+
264
+ Recent versions of JavaScript and TypeScript support the `async` and `await` keywords to run code asynchronously, which allow for more readable code than using Promises.
265
+
266
+ **Don't** do this:
267
+
268
+ ```ts
269
+ function test(): Promise<string | null> {
270
+ return requestUrl('https://example.com')
271
+ .then(res => res.text
272
+ .catch(e => {
273
+ console.log(e);
274
+ return null;
275
+ });
276
+ }
277
+ ```
278
+
279
+ Do this instead:
280
+
281
+ ```ts
282
+ async function AsyncTest(): Promise<string | null> {
283
+ try {
284
+ let res = await requestUrl('https://example.com');
285
+ let text = await r.text;
286
+ return text;
287
+ }
288
+ catch (e) {
289
+ console.log(e);
290
+ return null;
291
+ }
292
+ }
293
+ ```
294
+
295
+ ### Consider organizing your code base using folders
296
+
297
+ If your plugin uses more than one `.ts` file, consider organizing them into folders to make it easier to review and maintain.
298
+
299
+ ### Rename placeholder class names
300
+
301
+ The sample plugin contains placeholder names for common classes, such as `MyPlugin`, `MyPluginSettings`, and `SampleSettingTab`. Rename these to reflect the name of your plugin.
docs/obsidian-developer/Plugins/Releasing/Release your plugin with GitHub Actions.md ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Manually releasing your plugin can be time-consuming and error-prone. In this guide, you'll configure your plugin to use [GitHub Actions](https://github.com/features/actions) to automatically create a release when you create a new tag.
2
+
3
+ 1. In the root directory of your plugin, create a file called `release.yml` under `.github/workflows` with the following content:
4
+
5
+ ```yml
6
+ name: Release Obsidian plugin
7
+
8
+ on:
9
+ push:
10
+ tags:
11
+ - "*"
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - uses: actions/checkout@v3
19
+
20
+ - name: Use Node.js
21
+ uses: actions/setup-node@v3
22
+ with:
23
+ node-version: "18.x"
24
+
25
+ - name: Build plugin
26
+ run: |
27
+ npm install
28
+ npm run build
29
+
30
+ - name: Create release
31
+ env:
32
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33
+ run: |
34
+ tag="${GITHUB_REF#refs/tags/}"
35
+
36
+ gh release create "$tag" \
37
+ --title="$tag" \
38
+ --draft \
39
+ main.js manifest.json styles.css
40
+ ```
41
+
42
+ 2. In your terminal, commit the workflow.
43
+
44
+ ```bash
45
+ git add .github/workflows/release.yml
46
+ git commit -m "Add release workflow"
47
+ git push origin main
48
+ ```
49
+
50
+ 3. Create a tag that matches the version in the `manifest.json` file.
51
+
52
+ ```bash
53
+ git tag -a 1.0.1 -m "1.0.1"
54
+ git push origin 1.0.1
55
+ ```
56
+
57
+ - `-a` creates an [annotated tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging#_creating_tags).
58
+ - `-m` specifies the name of your release. For Obsidian plugins, this must be the same as the version.
59
+
60
+ 4. Browse to your repository on GitHub and select the **Actions** tab. Your workflow might still be running, or it might have finished already.
61
+
62
+ 5. When the workflow finishes, go back to the main page for your repository and select **Releases** in the sidebar on the right side. The workflow has created a draft GitHub release and uploaded the required assets as binary attachments.
63
+
64
+ 6. Select **Edit** (pencil icon) on the right side of the release name.
65
+
66
+ 7. Add release notes to let users know what happened in this release, and then select **Publish release**.
67
+
68
+ You've successfully set up your plugin to automatically create a GitHub release whenever you create a new tag.
69
+
70
+ - If this is the first release for this plugin, you're now ready to [[Submit your plugin]].
71
+ - If this is an update to an already published plugin, your users can now update to the latest version.
docs/obsidian-developer/Plugins/Releasing/Submission requirements for plugins.md ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This page lists extends the [[Developer policies]] with plugin-specific requirements that all plugins must follow to be published.
2
+
3
+ ## Only use `fundingUrl` to link to services for financial support
4
+
5
+ Use [[Manifest#fundingUrl|fundingUrl]] if you accept financial support for your plugin, using services like Buy Me A Coffee or GitHub Sponsors.
6
+
7
+ If you don't accept donations, remove `fundingUrl` from your manifest.
8
+
9
+ ## Keep plugin descriptions short and simple
10
+
11
+ Good plugin descriptions help users understand your plugin quickly and succinctly. Good descriptions often start with an action statement such as:
12
+
13
+ - "Translate selected text into..."
14
+ - "Generate notes automatically from..."
15
+ - "Import notes from..."
16
+ - "Sync highlights and annotations from..."
17
+ - "Open links in..."
18
+
19
+ Avoid starting your description with "This is a plugin", because it'll be obvious to users in the context of the Community Plugins directory.
20
+
21
+ Your description should:
22
+
23
+ - Follow the [Obsidian style guide](https://help.obsidian.md/Contributing+to+Obsidian/Style+guide).
24
+ - Have 250 characters maximum.
25
+ - End with a period `.`.
26
+ - Avoid using emoji or special characters.
27
+ - Use correct capitalization for acronyms, proper nouns and trademarks such as "Obsidian", "Markdown", "PDF". If you are not sure how to capitalize a term, refer to its website or Wikipedia description.
28
+
29
+ ## Node.js and Electron APIs are only allowed on desktop
30
+
31
+ The Node.js and Electron APIs are only available in the desktop version of Obsidian. For example, Node.js packages like `fs`, `crypto`, and `os`, are only available on desktop.
32
+
33
+ If your plugin uses any of these APIs, you **must** set `isDesktopOnly` to `true` in the `manifest.json`.
34
+
35
+ > [!tip]
36
+ > Many Node.js features have Web API alternatives:
37
+ >
38
+ > - [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) instead of [`crypto`](https://nodejs.org/api/crypto.html).
39
+ > - `navigator.clipboard.readText()` and `navigator.clipboard.writeText()` to access clipboard contents.
40
+
41
+ ## Don't include the plugin ID in the command ID
42
+
43
+ Obsidian automatically prefixes command IDs with your plugin ID. You don't need to include the plugin ID yourself.
docs/obsidian-developer/Plugins/Releasing/Submit your plugin.md ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ If you want to share your plugin with the Obsidian community, the best way is to submit it to the [official list of plugins](https://github.com/obsidianmd/obsidian-releases/blob/master/community-plugins.json). Once we've reviewed and published your plugin, users can install it directly from within Obsidian. It'll also be featured in the [plugin directory](https://obsidian.md/plugins) on the Obsidian website.
2
+
3
+ You only need to submit the initial version of your plugin. After your plugin has been published, users can download new releases from GitHub directly from within Obsidian.
4
+
5
+ ## Prerequisites
6
+
7
+ To complete this guide, you'll need:
8
+
9
+ - A [GitHub](https://github.com/signup) account.
10
+
11
+ ## Before you begin
12
+
13
+ Before you submit your plugin, make sure you have the following files in the root folder of your repository:
14
+
15
+ - A `README.md` that describes the purpose of the plugin, and how to use it.
16
+ - A `LICENSE` that determines how others are allowed to use the plugin and its source code. If you need help to [add a license](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-license-to-a-repository) for your plugin, refer to [Choose a License](https://choosealicense.com/).
17
+ - A `manifest.json` that describes your plugin. For more information, refer to [[Manifest]].
18
+
19
+ ## Step 1: Publish your plugin to GitHub
20
+
21
+ > [!note] Template repositories
22
+ > If you created your plugin from one of our template repositories, you may skip this step.
23
+
24
+ To review your plugin, we need to access to the source code on GitHub. If you're unfamiliar with GitHub, refer to the GitHub docs for how to [Create a new repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository).
25
+
26
+ ## Step 2: Create a release
27
+
28
+ In this step, you'll prepare a release for your plugin that's ready to be submitted.
29
+
30
+ 1. In `manifest.json`, update `version` to a new version that follows the [Semantic Versioning](https://semver.org/) specification, for example `1.0.0` for your initial release. You can only use numbers and periods (`.`).
31
+ 2. [Create a GitHub release](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release). The "Tag version" of the release must match the version in your `manifest.json`.
32
+ 3. Enter a name for the release, and describe it in the description field. Obsidian doesn't use the release name for anything, so feel free to name it however you like.
33
+ 4. Upload the following plugin assets to the release as binary attachments:
34
+
35
+ - `main.js`
36
+ - `manifest.json`
37
+ - `styles.css` (optional)
38
+
39
+ ## Step 3: Submit your plugin for review
40
+
41
+ In this step, you'll submit your plugin to the Obsidian team for review.
42
+
43
+ 1. In [community-plugins.json](https://github.com/obsidianmd/obsidian-releases/edit/master/community-plugins.json), add a new entry at the end of the JSON array.
44
+
45
+ ```json
46
+ {
47
+ "id": "doggo-dictation",
48
+ "name": "Doggo Dictation",
49
+ "author": "John Dolittle",
50
+ "description": "Transcribes dog speech into notes.",
51
+ "repo": "drdolittle/doggo-dictation"
52
+ }
53
+ ```
54
+
55
+ - `id`, `name`, `author`, and `description` determines how your plugin appears to the user, and should match the corresponding properties in your [[Manifest]].
56
+ - `id` is unique to your plugin. Search `community-plugins.json` to confirm that there's no existing plugin with the same id. The `id` can't contain `obsidian`.
57
+ - `repo` is the path to your GitHub repository. For example, if your GitHub repo is located at https://github.com/your-username/your-repo-name, the path is `your-username/your-repo-name`.
58
+
59
+ Remember to add a comma after the closing brace, `}`, of the previous entry.
60
+
61
+ 2. Select **Commit changes...** in the upper-right corner.
62
+ 3. Select **Propose changes**.
63
+ 4. Select **Create pull request**.
64
+ 5. Select **Preview**, and then select **Community Plugin**.
65
+ 6. Click **Create pull request**.
66
+ 7. In the name of the pull request, enter "Add [...] plugin", where [...] is the name of your plugin.
67
+ 8. Fill in the details in the description for the pull request. For the checkboxes, insert an `x` between the brackets, `[x]`, to mark them as done.
68
+ 9. Click **Create pull request** (for the last time 🤞).
69
+
70
+ You've now submitted your plugin to the Obsidian plugin directory. Sit back and wait for an initial validation by our friendly bot. It may take a few minutes before the results are ready.
71
+
72
+ - If you see a **Ready to review** label on your PR, your submission has passed the automatic validation.
73
+ - If you see a **Validation failed** label on your PR, you need to address all listed issues until the bot assigns a **Ready to review** label.
74
+
75
+ Once your submission is ready to review, you can sit back and wait for the Obsidian team to review it.
76
+
77
+ > [!question] How long does it take to review my plugin?
78
+ > The time it takes to review your submission depends on the current workload of the Obsidian team. The team is still small, so please be patient while you wait for your plugin to be reviewed. We're currently unable to give any estimates on when we'll be able to review your submission.
79
+
80
+ ## Step 4: Address review comments
81
+
82
+ Once a reviewer has reviewed your plugin, they'll add a comment to your pull request with the result of the review. The reviewer may require that you update your plugin, or they can offer suggestions on how you can improve it.
83
+
84
+ Address any required changes and update the GitHub release with the new changes. Leave a comment on the PR to let us know you've addressed the feedback. Don't open a new PR.
85
+
86
+ We'll publish the plugin as soon we've verified that all required changes have been addressed.
87
+
88
+ > [!note]
89
+ > While only Obsidian team members can publish your plugin, other community members may also offer to review your submission in the meantime.
90
+
91
+ ## Next steps
92
+
93
+ Once we've reviewed and published your plugin, it's time to announce it to the community:
94
+
95
+ - Announce in [Share & showcase](https://forum.obsidian.md/c/share-showcase/9) in the forums.
96
+ - Announce in the `#updates` channel on [Discord](https://discord.gg/veuWUTm). You need the [`developer` role](https://discord.com/channels/686053708261228577/702717892533157999/830492034807758859) to post in `#updates`.
docs/obsidian-developer/Plugins/User interface/About user interface.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This page gives you an overview of how to add or change the Obsidian user interface.
2
+
3
+ You can see some of the user interface components when you first open Obsidian.
4
+
5
+ - [[Ribbon actions]]
6
+ - [[Views]]
7
+ - [[Plugins/User interface/Status bar|Status bar]]
8
+
9
+ To modify the editor, refer to [[Editor]] and [[Editor extensions]].
10
+
11
+ ![User interface](user-interface.png)
docs/obsidian-developer/Plugins/User interface/Commands.md ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Commands are actions that the user can perform from the [Command Palette](https://help.obsidian.md/Plugins/Command+palette) or by using a hot key.
2
+
3
+ ![[command.png]]
4
+
5
+ To register a new command for your plugin, call the [[addCommand|addCommand()]] method inside the `onload()` method:
6
+
7
+ ```ts
8
+ import { Plugin } from "obsidian";
9
+
10
+ export default class ExamplePlugin extends Plugin {
11
+ async onload() {
12
+ this.addCommand({
13
+ id: "print-greeting-to-console",
14
+ name: "Print greeting to console",
15
+ callback: () => {
16
+ console.log("Hey, you!");
17
+ },
18
+ });
19
+ }
20
+ }
21
+ ```
22
+
23
+ ## Conditional commands
24
+
25
+ If your command is only able to run under certain conditions, then consider using [[checkCallback|checkCallback()]] instead.
26
+
27
+ The `checkCallback` runs twice. First, to perform a preliminary check to determine whether the command can run. Second, to perform the action.
28
+
29
+ Since time may pass between the two runs, you need to perform the check during both calls.
30
+
31
+ To determine whether the callback should perform a preliminary check or an action, a `checking` argument is passed to the callback.
32
+
33
+ - If `checking` is set to `true`, perform a preliminary check.
34
+ - If `checking` is set to `false`, perform an action.
35
+
36
+ The command in the following example depends on a required value. In both runs, the callback checks that the value is present but only performs the action if `checking` is `false`.
37
+
38
+ ```ts
39
+ this.addCommand({
40
+ id: 'example-command',
41
+ name: 'Example command',
42
+ // highlight-next-line
43
+ checkCallback: (checking: boolean) => {
44
+ const value = getRequiredValue();
45
+
46
+ if (value) {
47
+ if (!checking) {
48
+ doCommand(value);
49
+ }
50
+
51
+ return true
52
+ }
53
+
54
+ return false;
55
+ },
56
+ });
57
+ ```
58
+
59
+ ## Editor commands
60
+
61
+ If your command needs access to the editor, you can also use the [[editorCallback|editorCallback()]], which provides the active editor and its view as arguments.
62
+
63
+ ```ts
64
+ this.addCommand({
65
+ id: 'example-command',
66
+ name: 'Example command',
67
+ editorCallback: (editor: Editor, view: MarkdownView) => {
68
+ const sel = editor.getSelection()
69
+
70
+ console.log(`You have selected: ${sel}`);
71
+ },
72
+ }
73
+ ```
74
+
75
+ > [!note]
76
+ > Editor commands only appear in the Command Palette when there's an active editor available.
77
+
78
+ If the editor callback can only run given under certain conditions, consider using the [[editorCheckCallback|editorCheckCallback()]] instead. For more information, refer to [[#Conditional commands]].
79
+
80
+ ```ts
81
+ this.addCommand({
82
+ id: 'example-command',
83
+ name: 'Example command',
84
+ editorCheckCallback: (checking: boolean, editor: Editor, view: MarkdownView) => {
85
+ const value = getRequiredValue();
86
+
87
+ if (value) {
88
+ if (!checking) {
89
+ doCommand(value);
90
+ }
91
+
92
+ return true
93
+ }
94
+
95
+ return false;
96
+ },
97
+ });
98
+ ```
99
+
100
+ ## Hot keys
101
+
102
+ The user can run commands using a keyboard shortcut, or _hot key_. While they can configure this themselves, you can also provide a default hot key.
103
+
104
+ > [!warning]
105
+ > Avoid setting default hot keys for plugins that you intend for others to use. Hot keys are highly likely to conflict with those defined by other plugins or by the user themselves.
106
+
107
+ In this example, the user can run the command by pressing and holding Ctrl (or Cmd on Mac) and Shift together, and then pressing the letter `a` on their keyboard.
108
+
109
+ ```ts
110
+ this.addCommand({
111
+ id: 'example-command',
112
+ name: 'Example command',
113
+ hotkeys: [{ modifiers: ["Mod", "Shift"], key: "a" }],
114
+ callback: () => {
115
+ console.log('Hey, you!');
116
+ },
117
+ });
118
+ ```
119
+
120
+ > [!note]
121
+ > The Mod key is a special modifier key that becomes Ctrl on Windows and Linux, and Cmd on macOS.
docs/obsidian-developer/Plugins/User interface/Context menus.md ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ If you want to open up a context menu, use [[Menu|Menu]]:
2
+
3
+ ```ts
4
+ import { Menu, Notice, Plugin } from "obsidian";
5
+
6
+ export default class ExamplePlugin extends Plugin {
7
+ async onload() {
8
+ this.addRibbonIcon("dice", "Open menu", (event) => {
9
+ const menu = new Menu();
10
+
11
+ menu.addItem((item) =>
12
+ item
13
+ .setTitle("Copy")
14
+ .setIcon("documents")
15
+ .onClick(() => {
16
+ new Notice("Copied");
17
+ })
18
+ );
19
+
20
+ menu.addItem((item) =>
21
+ item
22
+ .setTitle("Paste")
23
+ .setIcon("paste")
24
+ .onClick(() => {
25
+ new Notice("Pasted");
26
+ })
27
+ );
28
+
29
+ menu.showAtMouseEvent(event);
30
+ });
31
+ }
32
+ }
33
+ ```
34
+
35
+ [[showAtMouseEvent|showAtMouseEvent()]] opens the menu where you clicked with the mouse.
36
+
37
+ > [!tip]
38
+ > If you need more control of where the menu appears, you can use `menu.showAtPosition({ x: 20, y: 20 })` to open the menu at a position relative to the top-left corner of the Obsidian window.
39
+
40
+ For more information on what icons you can use, refer to [[Plugins/User interface/Icons|Icons]].
41
+
42
+ You can also add an item to the file menu, or the editor menu, by subscribing to the `file-menu` and `editor-menu` workspace events:
43
+
44
+ ![[context-menu-positions.png]]
45
+
46
+ ```ts
47
+ import { Notice, Plugin } from "obsidian";
48
+
49
+ export default class ExamplePlugin extends Plugin {
50
+ async onload() {
51
+ this.registerEvent(
52
+ this.app.workspace.on("file-menu", (menu, file) => {
53
+ menu.addItem((item) => {
54
+ item
55
+ .setTitle("Print file path 👈")
56
+ .setIcon("document")
57
+ .onClick(async () => {
58
+ new Notice(file.path);
59
+ });
60
+ });
61
+ })
62
+ );
63
+
64
+ this.registerEvent(
65
+ this.app.workspace.on("editor-menu", (menu, editor, view) => {
66
+ menu.addItem((item) => {
67
+ item
68
+ .setTitle("Print file path 👈")
69
+ .setIcon("document")
70
+ .onClick(async () => {
71
+ new Notice(view.file.path);
72
+ });
73
+ });
74
+ })
75
+ );
76
+ }
77
+ }
78
+ ```
79
+
80
+ For more information on handling events, refer to [[Events]].
docs/obsidian-developer/Plugins/User interface/HTML elements.md ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Several components in the Obsidian API, such as the [[Settings]], expose _container elements_:
2
+
3
+ ```ts
4
+ import { App, PluginSettingTab } from "obsidian";
5
+
6
+ class ExampleSettingTab extends PluginSettingTab {
7
+ plugin: ExamplePlugin;
8
+
9
+ constructor(app: App, plugin: ExamplePlugin) {
10
+ super(app, plugin);
11
+ this.plugin = plugin;
12
+ }
13
+
14
+ display(): void {
15
+ // highlight-next-line
16
+ let { containerEl } = this;
17
+
18
+ // ...
19
+ }
20
+ }
21
+ ```
22
+
23
+ Container elements are `HTMLElement` objects that make it possible to create custom interfaces within Obsidian.
24
+
25
+ ## Create HTML elements using `createEl()`
26
+
27
+ Every `HTMLElement`, including the container element, exposes a `createEl()` method that creates an `HTMLElement` under the original element.
28
+
29
+ For example, here's how you can add an `<h1>` heading element inside the container element:
30
+
31
+ ```ts
32
+ containerEl.createEl("h1", { text: "Heading 1" });
33
+ ```
34
+
35
+ `createEl()` returns a reference to the new element:
36
+
37
+ ```ts
38
+ const book = containerEl.createEl("div");
39
+ book.createEl("div", { text: "How to Take Smart Notes" });
40
+ book.createEl("small", { text: "Sönke Ahrens" });
41
+ ```
42
+
43
+ ## Style your elements
44
+
45
+ You can add custom CSS styles to your plugin by adding a `styles.css` file in the plugin root directory. To add some styles for the previous book example:
46
+
47
+ ```css title="styles.css"
48
+ .book {
49
+ border: 1px solid var(--background-modifier-border);
50
+ padding: 10px;
51
+ }
52
+
53
+ .book__title {
54
+ font-weight: 600;
55
+ }
56
+
57
+ .book__author {
58
+ color: var(--text-muted);
59
+ }
60
+ ```
61
+
62
+ > [!tip]
63
+ > `--background-modifier-border` and `--text-muted` are [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) that are defined and used by Obsidian itself. If you use these variables for your styles, your plugin will look great even if the user has a different theme! 🌈
64
+
65
+ To make the HTML elements use the styles, set the `cls` property for the HTML element:
66
+
67
+ ```ts
68
+ const book = containerEl.createEl("div", { cls: "book" });
69
+ book.createEl("div", { text: "How to Take Smart Notes", cls: "book__title" });
70
+ book.createEl("small", { text: "Sönke Ahrens", cls: "book__author" });
71
+ ```
72
+
73
+ Now it looks much better! 🎉
74
+
75
+ ![[styles.png]]
76
+
77
+ ### Conditional styles
78
+
79
+ Use the `toggleClass` method if you want to change the style of an element based on the user's settings or other values:
80
+
81
+ ```ts
82
+ element.toggleClass("danger", status === "error");
83
+ ```
docs/obsidian-developer/Plugins/User interface/Icons.md ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Several of the UI components in the Obsidian API lets you configure an accompanying icon. You can choose from one of the built-in icons, or you can add your own.
2
+
3
+ ## Browse available icons
4
+
5
+ Browse to [lucide.dev](https://lucide.dev/) to see all available icons and their corresponding names.
6
+
7
+ **Please note:** Only icons up to v0.171.0 are supported at this time.
8
+
9
+ ## Use icons
10
+
11
+ If you'd like to use icons in your custom interfaces, use the [[setIcon|setIcon()]] utility function to add an icon to an [[HTML elements|HTML element]]. The following example adds icon to the status bar:
12
+
13
+ ```ts
14
+ import { Plugin, setIcon } from "obsidian";
15
+
16
+ export default class ExamplePlugin extends Plugin {
17
+ async onload() {
18
+ const item = this.addStatusBarItem();
19
+ setIcon(item, "info");
20
+ }
21
+ }
22
+ ```
23
+
24
+ To change the size of the icon, set the `--icon-size` [[Reference/CSS variables/Foundations/Icons|CSS variable]] on the element containing the icon using preset sizes:
25
+
26
+ ```css
27
+ div {
28
+ --icon-size: var(--icon-size-m);
29
+ }
30
+ ```
31
+
32
+ ## Add your own icon
33
+
34
+ To add a custom icon for your plugin, use the [[addIcon|addIcon()]] utility:
35
+
36
+ ```ts
37
+ import { addIcon, Plugin } from "obsidian";
38
+
39
+ export default class ExamplePlugin extends Plugin {
40
+ async onload() {
41
+ addIcon("circle", `<circle cx="50" cy="50" r="50" fill="currentColor" />`);
42
+
43
+ this.addRibbonIcon("circle", "Click me", () => {
44
+ console.log("Hello, you!");
45
+ });
46
+ }
47
+ }
48
+ ```
49
+
50
+ `addIcon` takes two arguments:
51
+
52
+ 1. A name to uniquely identify your icon.
53
+ 2. The SVG content for the icon, without the surrounding `<svg>` tag.
54
+
55
+ Note that your icon needs to fit within a `0 0 100 100` view box to be drawn properly.
56
+
57
+ After the call to `addIcon`, you can use the icon just like any of the built-in icons.
58
+
59
+ ### Icon design guidelines
60
+
61
+ For compatibility and cohesiveness with the Obsidian interface, your icons should [follow Lucide’s guidelines](https://lucide.dev/guide/design/icon-design-guide):
62
+
63
+ - Icons must be designed on a 24 by 24 pixels canvas
64
+ - Icons must have at least 1 pixel padding within the canvas
65
+ - Icons must have a stroke width of 2 pixels
66
+ - Icons must use round joins
67
+ - Icons must use round caps
68
+ - Icons must use centered strokes
69
+ - Shapes (such as rectangles) in icons must have border radius of 2 pixels
70
+ - Distinct elements must have 2 pixels of spacing between each other
71
+
72
+ Lucide also [provides templates and guides](https://github.com/lucide-icons/lucide/blob/main/CONTRIBUTING.md) for vector editors such as Illustrator, Figma, and Inkscape.
docs/obsidian-developer/Plugins/User interface/Modals.md ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Modals display information and accept input from the user. To create a modal, create a class that extends [[Reference/TypeScript API/Modal/Modal|Modal]]:
2
+
3
+ ```ts
4
+ import { App, Modal } from "obsidian";
5
+
6
+ export class ExampleModal extends Modal {
7
+ constructor(app: App) {
8
+ super(app);
9
+ }
10
+
11
+ onOpen() {
12
+ let { contentEl } = this;
13
+ contentEl.setText("Look at me, I'm a modal! 👀");
14
+ }
15
+
16
+ onClose() {
17
+ let { contentEl } = this;
18
+ contentEl.empty();
19
+ }
20
+ }
21
+ ```
22
+
23
+ - [[Reference/TypeScript API/View/onOpen|onOpen()]] is called when the modal is opened and is responsible for building the content of your modal. For more information, refer to [HTML elements](HTML%20elements.md).
24
+ - [[Reference/TypeScript API/Modal/onClose|onClose()]] is called when the modal is closed and is responsible for cleaning up any resources used by the modal.
25
+
26
+ To open a modal, create a new instance of `ExampleModal` and call [[Reference/TypeScript API/Modal/open|open()]] on it:
27
+
28
+ ```ts
29
+ import { Plugin } from "obsidian";
30
+ import { ExampleModal } from "./modal";
31
+
32
+ export default class ExamplePlugin extends Plugin {
33
+ async onload() {
34
+ this.addCommand({
35
+ id: "display-modal",
36
+ name: "Display modal",
37
+ callback: () => {
38
+ new ExampleModal(this.app).open();
39
+ },
40
+ });
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## Accept user input
46
+
47
+ The modal in the previous example only displayed some text. Let's look at a little more complex example that handles input from the user.
48
+
49
+ ![[modal-input.png]]
50
+
51
+ ```ts
52
+ import { App, Modal, Setting } from "obsidian";
53
+
54
+ export class ExampleModal extends Modal {
55
+ result: string;
56
+ onSubmit: (result: string) => void;
57
+
58
+ constructor(app: App, onSubmit: (result: string) => void) {
59
+ super(app);
60
+ this.onSubmit = onSubmit;
61
+ }
62
+
63
+ onOpen() {
64
+ const { contentEl } = this;
65
+
66
+ contentEl.createEl("h1", { text: "What's your name?" });
67
+
68
+ new Setting(contentEl)
69
+ .setName("Name")
70
+ .addText((text) =>
71
+ text.onChange((value) => {
72
+ this.result = value
73
+ }));
74
+
75
+ new Setting(contentEl)
76
+ .addButton((btn) =>
77
+ btn
78
+ .setButtonText("Submit")
79
+ .setCta()
80
+ .onClick(() => {
81
+ this.close();
82
+ this.onSubmit(this.result);
83
+ }));
84
+ }
85
+
86
+ onClose() {
87
+ let { contentEl } = this;
88
+ contentEl.empty();
89
+ }
90
+ }
91
+ ```
92
+
93
+ The result is stored in `this.result` and returned in the `onSubmit` callback when the user clicks **Submit**:
94
+
95
+ ```ts
96
+ new ExampleModal(this.app, (result) => {
97
+ new Notice(`Hello, ${result}!`);
98
+ }).open();
99
+ ```
100
+
101
+ ## Select from list of suggestions
102
+
103
+ [[SuggestModal|SuggestModal]] is a special modal that lets you display a list of suggestions to the user.
104
+
105
+ ![[suggest-modal.gif]]
106
+
107
+ ```ts
108
+ import { App, Notice, SuggestModal } from "obsidian";
109
+
110
+ interface Book {
111
+ title: string;
112
+ author: string;
113
+ }
114
+
115
+ const ALL_BOOKS = [
116
+ {
117
+ title: "How to Take Smart Notes",
118
+ author: "Sönke Ahrens",
119
+ },
120
+ {
121
+ title: "Thinking, Fast and Slow",
122
+ author: "Daniel Kahneman",
123
+ },
124
+ {
125
+ title: "Deep Work",
126
+ author: "Cal Newport",
127
+ },
128
+ ];
129
+
130
+ export class ExampleModal extends SuggestModal<Book> {
131
+ // Returns all available suggestions.
132
+ getSuggestions(query: string): Book[] {
133
+ return ALL_BOOKS.filter((book) =>
134
+ book.title.toLowerCase().includes(query.toLowerCase())
135
+ );
136
+ }
137
+
138
+ // Renders each suggestion item.
139
+ renderSuggestion(book: Book, el: HTMLElement) {
140
+ el.createEl("div", { text: book.title });
141
+ el.createEl("small", { text: book.author });
142
+ }
143
+
144
+ // Perform action on the selected suggestion.
145
+ onChooseSuggestion(book: Book, evt: MouseEvent | KeyboardEvent) {
146
+ new Notice(`Selected ${book.title}`);
147
+ }
148
+ }
149
+ ```
150
+
151
+ In addition to `SuggestModal`, the Obsidian API provides an even more specialized type of modal for suggestions: the [[FuzzySuggestModal|FuzzySuggestModal]]. While it doesn't give you the same control of how each item is rendered, you get [fuzzy string search](https://en.wikipedia.org/wiki/Approximate_string_matching) out-of-the-box.
152
+
153
+ ![[fuzzy-suggestion-modal.png]]
154
+
155
+ ```ts
156
+ export class ExampleModal extends FuzzySuggestModal<Book> {
157
+ getItems(): Book[] {
158
+ return ALL_BOOKS;
159
+ }
160
+
161
+ getItemText(book: Book): string {
162
+ return book.title;
163
+ }
164
+
165
+ onChooseItem(book: Book, evt: MouseEvent | KeyboardEvent) {
166
+ new Notice(`Selected ${book.title}`);
167
+ }
168
+ }
169
+ ```