├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── build-and-test.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── docs ├── assets │ ├── css │ │ └── main.css │ ├── images │ │ ├── icons.png │ │ ├── icons@2x.png │ │ ├── widgets.png │ │ └── widgets@2x.png │ └── js │ │ └── search.js ├── classes │ ├── entry.html │ ├── entrybiblatexadapter.html │ ├── entrycsladapter.html │ └── library.html ├── index.html ├── interfaces │ ├── author.html │ ├── entrydatabiblatex.html │ ├── entrydatacsl.html │ └── iindexable.html └── modules.html ├── manifest.json ├── package-lock.json ├── package.json ├── rollup.config.js ├── screenshot.png ├── src ├── __tests__ │ ├── library.bib │ ├── library.json │ ├── regression_7f9aefe.bib │ ├── regression_fe15ef6.bib │ └── types.spec.ts ├── events.ts ├── main.ts ├── modals.ts ├── obsidian-extensions.d.ts ├── obsidian-extensions.ts ├── original-fs.d.ts ├── settings.ts ├── types.ts ├── util.ts ├── worker.ts └── workers.d.ts ├── styles.css ├── tsconfig.json └── versions.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.md] 10 | insert_final_newline = false 11 | trim_trailing_whitespace = false 12 | 13 | [*.{js,jsx,json,ts,tsx,yml}] 14 | indent_size = 2 15 | indent_style = space 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /**/*.js 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "env": { 4 | "browser": false, 5 | "es6": true, 6 | "node": true 7 | }, 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "project": "tsconfig.json", 11 | "sourceType": "module" 12 | }, 13 | "extends": [ 14 | "prettier", 15 | "prettier/@typescript-eslint", 16 | "plugin:prettier/recommended", 17 | "eslint:recommended", 18 | "plugin:@typescript-eslint/recommended" 19 | ], 20 | "plugins": ["prettier", "@typescript-eslint"] 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Console output** 21 | Please open the Obsidian developer console (Ctrl+Shift+I). Click anywhere in the console and force reload Obsidian by typing Ctrl+R. Then reproduce the issue you're describing. Please include any relevant error messages that appear in the console in your report here. 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Platform** 30 | - OS: [e.g. OS X 11.5.2] 31 | - Obsidian version [e.g. 0.12.13] 32 | - Plugin version [e.g. 0.4.3] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master, develop ] 9 | pull_request: 10 | branches: [ master, develop ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-node@v1 20 | with: 21 | node-version: 12.x 22 | - name: Reconfigure git to use HTTP authentication 23 | run: > 24 | git config --global url."https://github.com/".insteadOf ssh://git@github.com/ 25 | - run: npm ci --no-optional 26 | - run: npm run build --if-present 27 | - run: npm test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | *.iml 3 | .idea 4 | 5 | # npm 6 | node_modules 7 | 8 | # build 9 | main.js 10 | *.js.map 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "overrides": [ 5 | { 6 | "files": "*.ts", 7 | "options": { 8 | "parser": "typescript" 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Jon Gauthier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # obsidian-citation-plugin 2 | 3 | This plugin for [Obsidian](https://obsidian.md) integrates your academic reference manager with the Obsidian editing experience. 4 | 5 | ![](screenshot.png) 6 | 7 | The plugin supports reading bibliographies in [BibTeX / BibLaTeX `.bib` format][4] and [CSL-JSON format][1]. 8 | 9 | ## Setup 10 | 11 | You can install this plugin via the Obsidian "Third-party plugin interface." It requires Obsidian 0.9.20 or higher. 12 | 13 | Once the plugin is installed, you must provide it with a bibliography file: 14 | 15 | - If you use **Zotero** with [Better BibTeX][2]: 16 | - Select a collection in Zotero's left sidebar that you want to export. 17 | - Click `File` -> `Export library ...`. Select `Better BibLaTeX` or `Better CSL JSON` as the format. (We recommend using the BibLaTeX export unless you experience performance issues. The BibLaTeX format includes more information that you can reference from Obsidian, such as associated PDF attachments, but loads more slowly than the JSON export.) 18 | - You can optionally choose "Keep updated" to automatically re-export the collection -- this is recommended! 19 | - If you use other reference managers, check their documentation for BibLaTeX or CSL-JSON export support. We plan to officially support other managers in the future. 20 | 21 | Now open the Obsidian preferences and view the "Citations" tab. Paste the path to the exported file (`.bib` or `.json`, depending on the format you chose) in the text field labeled "Citation export path." After closing the settings dialog, you should now be able to search your references from within Obsidian! 22 | 23 | ## Usage 24 | 25 | The plugin offers four simple features at the moment: 26 | 27 | 1. **Open literature note** (Ctrl+Shift+O): automatically create or open a literature note for a particular reference. The title, folder, and initial content of the note can be configured in the plugin settings. 28 | 2. **Insert literature note reference** (Ctrl+Shift+E): insert a link to the literature note corresponding to a particular reference. 29 | 3. **Insert literature note content in the current pane** (no hotkey by default): insert content describing a particular reference into the current pane. (This can be useful for updating literature notes you already have but which are missing reference information.) 30 | 4. **Insert Markdown citation** (no hotkey by default): insert a [Pandoc-style citation][3] for a particular reference. (The exact format of the citation can be configured in the plugin settings.) 31 | 32 | ### Templates 33 | You can set up your own template for both the title and content of literature notes. The following variables can be used: 34 | 35 | ``` 36 | * {{citekey}} 37 | * {{abstract}} 38 | * {{authorString}} 39 | * {{containerTitle}} 40 | * {{DOI}} 41 | * {{eprint}} 42 | * {{eprinttype}} 43 | * {{eventPlace}} 44 | * {{page}} 45 | * {{publisher}} 46 | * {{publisherPlace}} 47 | * {{title}} 48 | * {{titleShort}} 49 | * {{URL}} 50 | * {{year}} 51 | * {{zoteroSelectURI}} 52 | ``` 53 | For example, your literature note title template can simply be `@{{citekey}}` and the content template can look like: 54 | ``` 55 | --- 56 | title: {{title}} 57 | authors: {{authorString}} 58 | year: {{year}} 59 | --- 60 | {{abstract}} 61 | ``` 62 | 63 | ## License 64 | 65 | MIT License. 66 | 67 | ## Contributors 68 | 69 | - Jon Gauthier ([hans](https://github.com/hans)) 70 | - [raineszm](https://github.com/raineszm) 71 | - [Luke Murray](https://lukesmurray.com/) 72 | - [valmaev](https://github.com/valmaev) 73 | 74 | [1]: https://github.com/citation-style-language/schema#csl-json-schema 75 | [2]: https://retorque.re/zotero-better-bibtex/ 76 | [3]: https://pandoc.org/MANUAL.html#extension-citations 77 | [4]: http://www.bibtex.org/ 78 | -------------------------------------------------------------------------------- /docs/assets/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hans/obsidian-citation-plugin/2edeeceaf5f38de90f77f111ab46d24eef3e0bfa/docs/assets/images/icons.png -------------------------------------------------------------------------------- /docs/assets/images/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hans/obsidian-citation-plugin/2edeeceaf5f38de90f77f111ab46d24eef3e0bfa/docs/assets/images/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hans/obsidian-citation-plugin/2edeeceaf5f38de90f77f111ab46d24eef3e0bfa/docs/assets/images/widgets.png -------------------------------------------------------------------------------- /docs/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hans/obsidian-citation-plugin/2edeeceaf5f38de90f77f111ab46d24eef3e0bfa/docs/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /docs/assets/js/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = {"kinds":{"32":"Variable","64":"Function","128":"Class","256":"Interface","512":"Constructor","1024":"Property","2048":"Method","65536":"Type literal","262144":"Accessor","4194304":"Type alias"},"rows":[{"id":0,"kind":64,"name":"loadEntries","url":"modules.html#loadentries","classes":"tsd-kind-function"},{"id":1,"kind":256,"name":"IIndexable","url":"interfaces/iindexable.html","classes":"tsd-kind-interface"},{"id":2,"kind":4194304,"name":"DatabaseType","url":"modules.html#databasetype","classes":"tsd-kind-type-alias"},{"id":3,"kind":32,"name":"TEMPLATE_VARIABLES","url":"modules.html#template_variables","classes":"tsd-kind-variable"},{"id":4,"kind":65536,"name":"__type","url":"modules.html#template_variables.__type","classes":"tsd-kind-type-literal tsd-parent-kind-variable","parent":"TEMPLATE_VARIABLES"},{"id":5,"kind":1024,"name":"citekey","url":"modules.html#template_variables.__type.citekey","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"TEMPLATE_VARIABLES.__type"},{"id":6,"kind":1024,"name":"abstract","url":"modules.html#template_variables.__type.abstract","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"TEMPLATE_VARIABLES.__type"},{"id":7,"kind":1024,"name":"authorString","url":"modules.html#template_variables.__type.authorstring","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"TEMPLATE_VARIABLES.__type"},{"id":8,"kind":1024,"name":"containerTitle","url":"modules.html#template_variables.__type.containertitle","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"TEMPLATE_VARIABLES.__type"},{"id":9,"kind":1024,"name":"DOI","url":"modules.html#template_variables.__type.doi","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"TEMPLATE_VARIABLES.__type"},{"id":10,"kind":1024,"name":"page","url":"modules.html#template_variables.__type.page","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"TEMPLATE_VARIABLES.__type"},{"id":11,"kind":1024,"name":"title","url":"modules.html#template_variables.__type.title","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"TEMPLATE_VARIABLES.__type"},{"id":12,"kind":1024,"name":"URL","url":"modules.html#template_variables.__type.url","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"TEMPLATE_VARIABLES.__type"},{"id":13,"kind":1024,"name":"year","url":"modules.html#template_variables.__type.year","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"TEMPLATE_VARIABLES.__type"},{"id":14,"kind":1024,"name":"zoteroSelectURI","url":"modules.html#template_variables.__type.zoteroselecturi","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"TEMPLATE_VARIABLES.__type"},{"id":15,"kind":128,"name":"Library","url":"classes/library.html","classes":"tsd-kind-class"},{"id":16,"kind":512,"name":"constructor","url":"classes/library.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"Library"},{"id":17,"kind":1024,"name":"entries","url":"classes/library.html#entries","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Library"},{"id":18,"kind":65536,"name":"__type","url":"classes/library.html#__type","classes":"tsd-kind-type-literal tsd-parent-kind-class","parent":"Library"},{"id":19,"kind":262144,"name":"size","url":"classes/library.html#size","classes":"tsd-kind-get-signature tsd-parent-kind-class","parent":"Library"},{"id":20,"kind":2048,"name":"getTemplateVariablesForCitekey","url":"classes/library.html#gettemplatevariablesforcitekey","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Library"},{"id":21,"kind":256,"name":"Author","url":"interfaces/author.html","classes":"tsd-kind-interface"},{"id":22,"kind":1024,"name":"given","url":"interfaces/author.html#given","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Author"},{"id":23,"kind":1024,"name":"family","url":"interfaces/author.html#family","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Author"},{"id":24,"kind":128,"name":"Entry","url":"classes/entry.html","classes":"tsd-kind-class"},{"id":25,"kind":512,"name":"constructor","url":"classes/entry.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"Entry"},{"id":26,"kind":1024,"name":"id","url":"classes/entry.html#id","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":27,"kind":1024,"name":"type","url":"classes/entry.html#type","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":28,"kind":1024,"name":"abstract","url":"classes/entry.html#abstract","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":29,"kind":1024,"name":"author","url":"classes/entry.html#author","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":30,"kind":1024,"name":"authorString","url":"classes/entry.html#authorstring","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":31,"kind":1024,"name":"containerTitle","url":"classes/entry.html#containertitle","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":32,"kind":1024,"name":"DOI","url":"classes/entry.html#doi","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":33,"kind":1024,"name":"files","url":"classes/entry.html#files","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":34,"kind":1024,"name":"issuedDate","url":"classes/entry.html#issueddate","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":35,"kind":1024,"name":"page","url":"classes/entry.html#page","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":36,"kind":1024,"name":"title","url":"classes/entry.html#title","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":37,"kind":1024,"name":"URL","url":"classes/entry.html#url","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Entry"},{"id":38,"kind":262144,"name":"year","url":"classes/entry.html#year","classes":"tsd-kind-get-signature tsd-parent-kind-class","parent":"Entry"},{"id":39,"kind":262144,"name":"zoteroSelectURI","url":"classes/entry.html#zoteroselecturi","classes":"tsd-kind-get-signature tsd-parent-kind-class","parent":"Entry"},{"id":40,"kind":4194304,"name":"EntryData","url":"modules.html#entrydata","classes":"tsd-kind-type-alias"},{"id":41,"kind":256,"name":"EntryDataCSL","url":"interfaces/entrydatacsl.html","classes":"tsd-kind-interface"},{"id":42,"kind":1024,"name":"id","url":"interfaces/entrydatacsl.html#id","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"EntryDataCSL"},{"id":43,"kind":1024,"name":"type","url":"interfaces/entrydatacsl.html#type","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"EntryDataCSL"},{"id":44,"kind":1024,"name":"abstract","url":"interfaces/entrydatacsl.html#abstract","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"EntryDataCSL"},{"id":45,"kind":1024,"name":"author","url":"interfaces/entrydatacsl.html#author","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"EntryDataCSL"},{"id":46,"kind":1024,"name":"container-title","url":"interfaces/entrydatacsl.html#container_title","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"EntryDataCSL"},{"id":47,"kind":1024,"name":"DOI","url":"interfaces/entrydatacsl.html#doi","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"EntryDataCSL"},{"id":48,"kind":1024,"name":"issued","url":"interfaces/entrydatacsl.html#issued","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"EntryDataCSL"},{"id":49,"kind":65536,"name":"__type","url":"interfaces/entrydatacsl.html#__type","classes":"tsd-kind-type-literal tsd-parent-kind-interface","parent":"EntryDataCSL"},{"id":50,"kind":1024,"name":"date-parts","url":"interfaces/entrydatacsl.html#__type.date_parts","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"EntryDataCSL.__type"},{"id":51,"kind":1024,"name":"page","url":"interfaces/entrydatacsl.html#page","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"EntryDataCSL"},{"id":52,"kind":1024,"name":"title","url":"interfaces/entrydatacsl.html#title","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"EntryDataCSL"},{"id":53,"kind":1024,"name":"URL","url":"interfaces/entrydatacsl.html#url","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"EntryDataCSL"},{"id":54,"kind":128,"name":"EntryCSLAdapter","url":"classes/entrycsladapter.html","classes":"tsd-kind-class"},{"id":55,"kind":512,"name":"constructor","url":"classes/entrycsladapter.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":56,"kind":1024,"name":"files","url":"classes/entrycsladapter.html#files","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-overwrite","parent":"EntryCSLAdapter"},{"id":57,"kind":262144,"name":"id","url":"classes/entrycsladapter.html#id","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":58,"kind":262144,"name":"type","url":"classes/entrycsladapter.html#type","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":59,"kind":262144,"name":"abstract","url":"classes/entrycsladapter.html#abstract","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":60,"kind":262144,"name":"author","url":"classes/entrycsladapter.html#author","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":61,"kind":262144,"name":"authorString","url":"classes/entrycsladapter.html#authorstring","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":62,"kind":262144,"name":"containerTitle","url":"classes/entrycsladapter.html#containertitle","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":63,"kind":262144,"name":"DOI","url":"classes/entrycsladapter.html#doi","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":64,"kind":262144,"name":"issuedDate","url":"classes/entrycsladapter.html#issueddate","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":65,"kind":262144,"name":"page","url":"classes/entrycsladapter.html#page","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":66,"kind":262144,"name":"title","url":"classes/entrycsladapter.html#title","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":67,"kind":262144,"name":"URL","url":"classes/entrycsladapter.html#url","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":68,"kind":262144,"name":"year","url":"classes/entrycsladapter.html#year","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":69,"kind":262144,"name":"zoteroSelectURI","url":"classes/entrycsladapter.html#zoteroselecturi","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryCSLAdapter"},{"id":70,"kind":128,"name":"EntryBibLaTeXAdapter","url":"classes/entrybiblatexadapter.html","classes":"tsd-kind-class"},{"id":71,"kind":512,"name":"constructor","url":"classes/entrybiblatexadapter.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class tsd-is-inherited","parent":"EntryBibLaTeXAdapter"},{"id":72,"kind":1024,"name":"abstract","url":"classes/entrybiblatexadapter.html#abstract","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-overwrite","parent":"EntryBibLaTeXAdapter"},{"id":73,"kind":1024,"name":"_containerTitle","url":"classes/entrybiblatexadapter.html#_containertitle","classes":"tsd-kind-property tsd-parent-kind-class","parent":"EntryBibLaTeXAdapter"},{"id":74,"kind":1024,"name":"containerTitleShort","url":"classes/entrybiblatexadapter.html#containertitleshort","classes":"tsd-kind-property tsd-parent-kind-class","parent":"EntryBibLaTeXAdapter"},{"id":75,"kind":1024,"name":"DOI","url":"classes/entrybiblatexadapter.html#doi","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-overwrite","parent":"EntryBibLaTeXAdapter"},{"id":76,"kind":1024,"name":"event","url":"classes/entrybiblatexadapter.html#event","classes":"tsd-kind-property tsd-parent-kind-class","parent":"EntryBibLaTeXAdapter"},{"id":77,"kind":1024,"name":"issued","url":"classes/entrybiblatexadapter.html#issued","classes":"tsd-kind-property tsd-parent-kind-class","parent":"EntryBibLaTeXAdapter"},{"id":78,"kind":1024,"name":"page","url":"classes/entrybiblatexadapter.html#page","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-overwrite","parent":"EntryBibLaTeXAdapter"},{"id":79,"kind":1024,"name":"title","url":"classes/entrybiblatexadapter.html#title","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-overwrite","parent":"EntryBibLaTeXAdapter"},{"id":80,"kind":1024,"name":"titleShort","url":"classes/entrybiblatexadapter.html#titleshort","classes":"tsd-kind-property tsd-parent-kind-class","parent":"EntryBibLaTeXAdapter"},{"id":81,"kind":1024,"name":"URL","url":"classes/entrybiblatexadapter.html#url","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-overwrite","parent":"EntryBibLaTeXAdapter"},{"id":82,"kind":262144,"name":"id","url":"classes/entrybiblatexadapter.html#id","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryBibLaTeXAdapter"},{"id":83,"kind":262144,"name":"type","url":"classes/entrybiblatexadapter.html#type","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryBibLaTeXAdapter"},{"id":84,"kind":262144,"name":"files","url":"classes/entrybiblatexadapter.html#files","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryBibLaTeXAdapter"},{"id":85,"kind":262144,"name":"authorString","url":"classes/entrybiblatexadapter.html#authorstring","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryBibLaTeXAdapter"},{"id":86,"kind":262144,"name":"containerTitle","url":"classes/entrybiblatexadapter.html#containertitle","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryBibLaTeXAdapter"},{"id":87,"kind":262144,"name":"issuedDate","url":"classes/entrybiblatexadapter.html#issueddate","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryBibLaTeXAdapter"},{"id":88,"kind":262144,"name":"author","url":"classes/entrybiblatexadapter.html#author","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryBibLaTeXAdapter"},{"id":89,"kind":262144,"name":"year","url":"classes/entrybiblatexadapter.html#year","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryBibLaTeXAdapter"},{"id":90,"kind":262144,"name":"zoteroSelectURI","url":"classes/entrybiblatexadapter.html#zoteroselecturi","classes":"tsd-kind-get-signature tsd-parent-kind-class tsd-is-inherited","parent":"EntryBibLaTeXAdapter"}],"index":{"version":"2.3.9","fields":["name","parent"],"fieldVectors":[["name/0",[0,41.529]],["parent/0",[]],["name/1",[1,41.529]],["parent/1",[]],["name/2",[2,41.529]],["parent/2",[]],["name/3",[3,36.375]],["parent/3",[]],["name/4",[4,32.98]],["parent/4",[3,3.413]],["name/5",[5,41.529]],["parent/5",[6,2.055]],["name/6",[7,28.42]],["parent/6",[6,2.055]],["name/7",[8,30.445]],["parent/7",[6,2.055]],["name/8",[9,30.445]],["parent/8",[6,2.055]],["name/9",[10,28.42]],["parent/9",[6,2.055]],["name/10",[11,28.42]],["parent/10",[6,2.055]],["name/11",[12,26.735]],["parent/11",[6,2.055]],["name/12",[13,28.42]],["parent/12",[6,2.055]],["name/13",[14,30.445]],["parent/13",[6,2.055]],["name/14",[15,30.445]],["parent/14",[6,2.055]],["name/15",[16,26.735]],["parent/15",[]],["name/16",[17,30.445]],["parent/16",[16,2.509]],["name/17",[18,41.529]],["parent/17",[16,2.509]],["name/18",[4,32.98]],["parent/18",[16,2.509]],["name/19",[19,41.529]],["parent/19",[16,2.509]],["name/20",[20,41.529]],["parent/20",[16,2.509]],["name/21",[21,25.291]],["parent/21",[]],["name/22",[22,41.529]],["parent/22",[21,2.373]],["name/23",[23,41.529]],["parent/23",[21,2.373]],["name/24",[24,17.337]],["parent/24",[]],["name/25",[17,30.445]],["parent/25",[24,1.627]],["name/26",[25,30.445]],["parent/26",[24,1.627]],["name/27",[26,30.445]],["parent/27",[24,1.627]],["name/28",[7,28.42]],["parent/28",[24,1.627]],["name/29",[21,25.291]],["parent/29",[24,1.627]],["name/30",[8,30.445]],["parent/30",[24,1.627]],["name/31",[9,30.445]],["parent/31",[24,1.627]],["name/32",[10,28.42]],["parent/32",[24,1.627]],["name/33",[27,32.98]],["parent/33",[24,1.627]],["name/34",[28,32.98]],["parent/34",[24,1.627]],["name/35",[11,28.42]],["parent/35",[24,1.627]],["name/36",[12,26.735]],["parent/36",[24,1.627]],["name/37",[13,28.42]],["parent/37",[24,1.627]],["name/38",[14,30.445]],["parent/38",[24,1.627]],["name/39",[15,30.445]],["parent/39",[24,1.627]],["name/40",[29,41.529]],["parent/40",[]],["name/41",[30,20.138]],["parent/41",[]],["name/42",[25,30.445]],["parent/42",[30,1.89]],["name/43",[26,30.445]],["parent/43",[30,1.89]],["name/44",[7,28.42]],["parent/44",[30,1.89]],["name/45",[21,25.291]],["parent/45",[30,1.89]],["name/46",[12,19.044,31,29.582]],["parent/46",[30,1.89]],["name/47",[10,28.42]],["parent/47",[30,1.89]],["name/48",[32,36.375]],["parent/48",[30,1.89]],["name/49",[4,32.98]],["parent/49",[30,1.89]],["name/50",[33,29.582,34,29.582]],["parent/50",[35,3.897]],["name/51",[11,28.42]],["parent/51",[30,1.89]],["name/52",[12,26.735]],["parent/52",[30,1.89]],["name/53",[13,28.42]],["parent/53",[30,1.89]],["name/54",[36,17.337]],["parent/54",[]],["name/55",[17,30.445]],["parent/55",[36,1.627]],["name/56",[27,32.98]],["parent/56",[36,1.627]],["name/57",[25,30.445]],["parent/57",[36,1.627]],["name/58",[26,30.445]],["parent/58",[36,1.627]],["name/59",[7,28.42]],["parent/59",[36,1.627]],["name/60",[21,25.291]],["parent/60",[36,1.627]],["name/61",[8,30.445]],["parent/61",[36,1.627]],["name/62",[9,30.445]],["parent/62",[36,1.627]],["name/63",[10,28.42]],["parent/63",[36,1.627]],["name/64",[28,32.98]],["parent/64",[36,1.627]],["name/65",[11,28.42]],["parent/65",[36,1.627]],["name/66",[12,26.735]],["parent/66",[36,1.627]],["name/67",[13,28.42]],["parent/67",[36,1.627]],["name/68",[14,30.445]],["parent/68",[36,1.627]],["name/69",[15,30.445]],["parent/69",[36,1.627]],["name/70",[37,14.666]],["parent/70",[]],["name/71",[17,30.445]],["parent/71",[37,1.376]],["name/72",[7,28.42]],["parent/72",[37,1.376]],["name/73",[38,41.529]],["parent/73",[37,1.376]],["name/74",[39,41.529]],["parent/74",[37,1.376]],["name/75",[10,28.42]],["parent/75",[37,1.376]],["name/76",[40,41.529]],["parent/76",[37,1.376]],["name/77",[32,36.375]],["parent/77",[37,1.376]],["name/78",[11,28.42]],["parent/78",[37,1.376]],["name/79",[12,26.735]],["parent/79",[37,1.376]],["name/80",[41,41.529]],["parent/80",[37,1.376]],["name/81",[13,28.42]],["parent/81",[37,1.376]],["name/82",[25,30.445]],["parent/82",[37,1.376]],["name/83",[26,30.445]],["parent/83",[37,1.376]],["name/84",[27,32.98]],["parent/84",[37,1.376]],["name/85",[8,30.445]],["parent/85",[37,1.376]],["name/86",[9,30.445]],["parent/86",[37,1.376]],["name/87",[28,32.98]],["parent/87",[37,1.376]],["name/88",[21,25.291]],["parent/88",[37,1.376]],["name/89",[14,30.445]],["parent/89",[37,1.376]],["name/90",[15,30.445]],["parent/90",[37,1.376]]],"invertedIndex":[["__type",{"_index":4,"name":{"4":{},"18":{},"49":{}},"parent":{}}],["_containertitle",{"_index":38,"name":{"73":{}},"parent":{}}],["abstract",{"_index":7,"name":{"6":{},"28":{},"44":{},"59":{},"72":{}},"parent":{}}],["author",{"_index":21,"name":{"21":{},"29":{},"45":{},"60":{},"88":{}},"parent":{"22":{},"23":{}}}],["authorstring",{"_index":8,"name":{"7":{},"30":{},"61":{},"85":{}},"parent":{}}],["citekey",{"_index":5,"name":{"5":{}},"parent":{}}],["constructor",{"_index":17,"name":{"16":{},"25":{},"55":{},"71":{}},"parent":{}}],["container",{"_index":31,"name":{"46":{}},"parent":{}}],["containertitle",{"_index":9,"name":{"8":{},"31":{},"62":{},"86":{}},"parent":{}}],["containertitleshort",{"_index":39,"name":{"74":{}},"parent":{}}],["databasetype",{"_index":2,"name":{"2":{}},"parent":{}}],["date",{"_index":33,"name":{"50":{}},"parent":{}}],["doi",{"_index":10,"name":{"9":{},"32":{},"47":{},"63":{},"75":{}},"parent":{}}],["entries",{"_index":18,"name":{"17":{}},"parent":{}}],["entry",{"_index":24,"name":{"24":{}},"parent":{"25":{},"26":{},"27":{},"28":{},"29":{},"30":{},"31":{},"32":{},"33":{},"34":{},"35":{},"36":{},"37":{},"38":{},"39":{}}}],["entrybiblatexadapter",{"_index":37,"name":{"70":{}},"parent":{"71":{},"72":{},"73":{},"74":{},"75":{},"76":{},"77":{},"78":{},"79":{},"80":{},"81":{},"82":{},"83":{},"84":{},"85":{},"86":{},"87":{},"88":{},"89":{},"90":{}}}],["entrycsladapter",{"_index":36,"name":{"54":{}},"parent":{"55":{},"56":{},"57":{},"58":{},"59":{},"60":{},"61":{},"62":{},"63":{},"64":{},"65":{},"66":{},"67":{},"68":{},"69":{}}}],["entrydata",{"_index":29,"name":{"40":{}},"parent":{}}],["entrydatacsl",{"_index":30,"name":{"41":{}},"parent":{"42":{},"43":{},"44":{},"45":{},"46":{},"47":{},"48":{},"49":{},"51":{},"52":{},"53":{}}}],["entrydatacsl.__type",{"_index":35,"name":{},"parent":{"50":{}}}],["event",{"_index":40,"name":{"76":{}},"parent":{}}],["family",{"_index":23,"name":{"23":{}},"parent":{}}],["files",{"_index":27,"name":{"33":{},"56":{},"84":{}},"parent":{}}],["gettemplatevariablesforcitekey",{"_index":20,"name":{"20":{}},"parent":{}}],["given",{"_index":22,"name":{"22":{}},"parent":{}}],["id",{"_index":25,"name":{"26":{},"42":{},"57":{},"82":{}},"parent":{}}],["iindexable",{"_index":1,"name":{"1":{}},"parent":{}}],["issued",{"_index":32,"name":{"48":{},"77":{}},"parent":{}}],["issueddate",{"_index":28,"name":{"34":{},"64":{},"87":{}},"parent":{}}],["library",{"_index":16,"name":{"15":{}},"parent":{"16":{},"17":{},"18":{},"19":{},"20":{}}}],["loadentries",{"_index":0,"name":{"0":{}},"parent":{}}],["page",{"_index":11,"name":{"10":{},"35":{},"51":{},"65":{},"78":{}},"parent":{}}],["parts",{"_index":34,"name":{"50":{}},"parent":{}}],["size",{"_index":19,"name":{"19":{}},"parent":{}}],["template_variables",{"_index":3,"name":{"3":{}},"parent":{"4":{}}}],["template_variables.__type",{"_index":6,"name":{},"parent":{"5":{},"6":{},"7":{},"8":{},"9":{},"10":{},"11":{},"12":{},"13":{},"14":{}}}],["title",{"_index":12,"name":{"11":{},"36":{},"46":{},"52":{},"66":{},"79":{}},"parent":{}}],["titleshort",{"_index":41,"name":{"80":{}},"parent":{}}],["type",{"_index":26,"name":{"27":{},"43":{},"58":{},"83":{}},"parent":{}}],["url",{"_index":13,"name":{"12":{},"37":{},"53":{},"67":{},"81":{}},"parent":{}}],["year",{"_index":14,"name":{"13":{},"38":{},"68":{},"89":{}},"parent":{}}],["zoteroselecturi",{"_index":15,"name":{"14":{},"39":{},"69":{},"90":{}},"parent":{}}]],"pipeline":[]}} -------------------------------------------------------------------------------- /docs/classes/library.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Library | obsidian-citation-plugin 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Class Library

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Hierarchy

70 |
    71 |
  • 72 | Library 73 |
  • 74 |
75 |
76 |
77 |

Index

78 |
79 |
80 |
81 |

Constructors

82 | 85 |
86 |
87 |

Properties

88 | 91 |
92 |
93 |

Accessors

94 | 97 |
98 |
99 |

Methods

100 | 103 |
104 |
105 |
106 |
107 |
108 |

Constructors

109 |
110 | 111 |

constructor

112 |
    113 |
  • new Library(entries: {}): Library
  • 114 |
115 |
    116 |
  • 117 | 122 |

    Parameters

    123 |
      124 |
    • 125 |
      entries: {}
      126 |
        127 |
      • 128 |
        [citekey: string]: Entry
        129 |
      • 130 |
      131 |
    • 132 |
    133 |

    Returns Library

    134 |
  • 135 |
136 |
137 |
138 |
139 |

Properties

140 |
141 | 142 |

entries

143 |
entries: {}
144 | 146 |
147 |

Type declaration

148 |
    149 |
  • 150 |
    [citekey: string]: Entry
    151 |
  • 152 |
153 |
154 |
155 |
156 |
157 |

Accessors

158 |
159 | 160 |

size

161 |
    162 |
  • get size(): number
  • 163 |
164 |
    165 |
  • 166 | 171 |

    Returns number

    172 |
  • 173 |
174 |
175 |
176 |
177 |

Methods

178 |
179 | 180 |

getTemplateVariablesForCitekey

181 |
    182 |
  • getTemplateVariablesForCitekey(citekey: string): Record<string, string>
  • 183 |
184 |
    185 |
  • 186 | 191 |
    192 |
    193 |

    For the given citekey, find the corresponding Entry and return a 194 | collection of template variable assignments.

    195 |
    196 |
    197 |

    Parameters

    198 |
      199 |
    • 200 |
      citekey: string
      201 |
    • 202 |
    203 |

    Returns Record<string, string>

    204 |
  • 205 |
206 |
207 |
208 |
209 | 276 |
277 |
278 | 296 |
297 |

Generated using TypeDoc

298 |
299 |
300 | 301 | 302 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | obsidian-citation-plugin 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 |

Project obsidian-citation-plugin

54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | 62 |

obsidian-citation-plugin

63 |
64 |

This plugin for Obsidian integrates your academic reference manager with the Obsidian editing experience.

65 |

66 |

The plugin supports reading bibliographies in BibTeX / BibLaTeX .bib format and CSL-JSON format.

67 | 68 |

Setup

69 |
70 |

You can install this plugin via the Obsidian "Third-party plugin interface." It requires Obsidian 0.9.20 or higher.

71 |

Once the plugin is installed, you must provide it with a bibliography file:

72 |
    73 |
  • If you use Zotero with Better BibTeX:
      74 |
    • Select a collection in Zotero's left sidebar that you want to export.
    • 75 |
    • Click File -> Export library .... Select Better BibLaTeX or Better CSL JSON as the format. (We recommend using the BibLaTeX export unless you experience performance issues. The BibLaTeX format includes more information that you can reference from Obsidian, such as associated PDF attachments, but loads more slowly than the JSON export.)
    • 76 |
    • You can optionally choose "Keep updated" to automatically re-export the collection -- this is recommended!
    • 77 |
    78 |
  • 79 |
  • If you use other reference managers, check their documentation for BibLaTeX or CSL-JSON export support. We plan to officially support other managers in the future.
  • 80 |
81 |

Now open the Obsidian preferences and view the "Citations" tab. Paste the path to the exported file (.bib or .json, depending on the format you chose) in the text field labeled "Citation export path." After closing the settings dialog, you should now be able to search your references from within Obsidian!

82 | 83 |

Usage

84 |
85 |

The plugin offers three simple features at the moment:

86 |
    87 |
  1. Open literature note (Ctrl+Shift+O): automatically create or open a literature note for a particular reference. The title, folder, and initial content of the note can be configured in the plugin settings.
  2. 88 |
  3. Insert literature note reference (Ctrl+Shift+E): insert a link to the literature note corresponding to a particular reference.
  4. 89 |
  5. Insert Markdown citation (no hotkey by default): insert a Pandoc-style citation for a particular reference. (The exact format of the citation can be configured in the plugin settings.)
  6. 90 |
91 | 92 |

License

93 |
94 |

MIT License.

95 | 96 |

Contributors

97 |
98 | 102 |
103 |
104 | 153 |
154 |
155 | 173 |
174 |

Generated using TypeDoc

175 |
176 |
177 | 178 | 179 | -------------------------------------------------------------------------------- /docs/interfaces/author.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Author | obsidian-citation-plugin 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Interface Author

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Hierarchy

70 |
    71 |
  • 72 | Author 73 |
  • 74 |
75 |
76 |
77 |

Index

78 |
79 |
80 |
81 |

Properties

82 | 86 |
87 |
88 |
89 |
90 |
91 |

Properties

92 |
93 | 94 |

Optional family

95 |
family: string
96 | 101 |
102 |
103 | 104 |

Optional given

105 |
given: string
106 | 111 |
112 |
113 |
114 | 175 |
176 |
177 | 195 |
196 |

Generated using TypeDoc

197 |
198 |
199 | 200 | 201 | -------------------------------------------------------------------------------- /docs/interfaces/entrydatabiblatex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EntryDataBibLaTeX | obsidian-citation-plugin 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Interface EntryDataBibLaTeX

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Hierarchy

70 |
    71 |
  • 72 | EntryDataBibLaTeX 73 |
  • 74 |
75 |
76 |
77 |

Index

78 |
79 |
80 |
81 |

Properties

82 | 89 |
90 |
91 |
92 |
93 |
94 |

Properties

95 |
96 | 97 |

creators

98 |
creators: {}
99 | 104 |
105 |
106 |

authors, editors, by creator type. Name order within the creator-type is retained.

107 |
108 |
109 |
110 |

Type declaration

111 |
    112 |
  • 113 |
    [type: string]: Name[]
    114 |
  • 115 |
116 |
117 |
118 |
119 | 120 |

fields

121 |
fields: {}
122 | 127 |
128 |
129 |

entry fields. The keys are always in lowercase

130 |
131 |
132 |
133 |

Type declaration

134 |
    135 |
  • 136 |
    [key: string]: string[]
    137 |
  • 138 |
139 |
140 |
141 |
142 | 143 |

key

144 |
key: string
145 | 150 |
151 |
152 |

citation key

153 |
154 |
155 |
156 |
157 | 158 |

Optional sentenceCased

159 |
sentenceCased: boolean
160 | 165 |
166 |
167 |

will be set to true if sentence casing was applied to the entry

168 |
169 |
170 |
171 |
172 | 173 |

type

174 |
type: string
175 | 180 |
181 |
182 |

entry type

183 |
184 |
185 |
186 |
187 |
188 | 258 |
259 |
260 | 278 |
279 |

Generated using TypeDoc

280 |
281 |
282 | 283 | 284 | -------------------------------------------------------------------------------- /docs/interfaces/entrydatacsl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EntryDataCSL | obsidian-citation-plugin 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Interface EntryDataCSL

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Hierarchy

70 |
    71 |
  • 72 | EntryDataCSL 73 |
  • 74 |
75 |
76 |
77 |

Index

78 |
79 |
80 |
81 |

Properties

82 | 94 |
95 |
96 |
97 |
98 |
99 |

Properties

100 |
101 | 102 |

Optional DOI

103 |
DOI: string
104 | 109 |
110 |
111 | 112 |

Optional URL

113 |
URL: string
114 | 119 |
120 |
121 | 122 |

Optional abstract

123 |
abstract: string
124 | 129 |
130 |
131 | 132 |

Optional author

133 |
author: Author[]
134 | 139 |
140 |
141 | 142 |

Optional container-title

143 |
container-title: string
144 | 149 |
150 |
151 | 152 |

id

153 |
id: string
154 | 159 |
160 |
161 | 162 |

Optional issued

163 |
issued: { date-parts: [any[]] }
164 | 169 |
170 |

Type declaration

171 |
    172 |
  • 173 |
    date-parts: [any[]]
    174 |
  • 175 |
176 |
177 |
178 |
179 | 180 |

Optional page

181 |
page: string
182 | 187 |
188 |
189 | 190 |

Optional title

191 |
title: string
192 | 197 |
198 |
199 | 200 |

type

201 |
type: string
202 | 207 |
208 |
209 |
210 | 295 |
296 |
297 | 315 |
316 |

Generated using TypeDoc

317 |
318 |
319 | 320 | 321 | -------------------------------------------------------------------------------- /docs/interfaces/iindexable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | IIndexable | obsidian-citation-plugin 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Interface IIndexable

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Hierarchy

70 |
    71 |
  • 72 | IIndexable 73 |
  • 74 |
75 |
76 |
77 |

Indexable

78 |
[key: string]: any
79 |
80 |
81 | 134 |
135 |
136 | 154 |
155 |

Generated using TypeDoc

156 |
157 |
158 | 159 | 160 | -------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | obsidian-citation-plugin 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 |

Project obsidian-citation-plugin

54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |

Index

62 |
63 |
64 |
65 |

Classes

66 | 72 |
73 |
74 |

Interfaces

75 | 81 |
82 |
83 |

Type aliases

84 | 88 |
89 |
90 |

Variables

91 | 94 |
95 |
96 |

Functions

97 | 100 |
101 |
102 |
103 |
104 |
105 |

Type aliases

106 |
107 | 108 |

DatabaseType

109 |
DatabaseType: typeof databaseTypes[number]
110 | 115 |
116 |
117 | 118 |

EntryData

119 | 120 | 125 |
126 |
127 |
128 |

Variables

129 |
130 | 131 |

Const TEMPLATE_VARIABLES

132 |
TEMPLATE_VARIABLES: { DOI: string; URL: string; abstract: string; authorString: string; citekey: string; containerTitle: string; page: string; title: string; year: string; zoteroSelectURI: string } = ...
133 | 138 |
139 |

Type declaration

140 |
    141 |
  • 142 |
    DOI: string
    143 |
  • 144 |
  • 145 |
    URL: string
    146 |
  • 147 |
  • 148 |
    abstract: string
    149 |
  • 150 |
  • 151 |
    authorString: string
    152 |
  • 153 |
  • 154 |
    citekey: string
    155 |
  • 156 |
  • 157 |
    containerTitle: string
    158 |
  • 159 |
  • 160 |
    page: string
    161 |
  • 162 |
  • 163 |
    title: string
    164 |
  • 165 |
  • 166 |
    year: string
    167 |
  • 168 |
  • 169 |
    zoteroSelectURI: string
    170 |
  • 171 |
172 |
173 |
174 |
175 |
176 |

Functions

177 |
178 | 179 |

loadEntries

180 | 183 |
    184 |
  • 185 | 190 |
    191 |
    192 |

    Load reference entries from the given raw database data.

    193 |
    194 |

    Returns a list of EntryData, which should be wrapped with the relevant 195 | adapter and used to instantiate a Library.

    196 |
    197 |

    Parameters

    198 |
      199 |
    • 200 |
      databaseRaw: string
      201 |
    • 202 |
    • 203 |
      databaseType: DatabaseType
      204 |
    • 205 |
    206 |

    Returns EntryData[]

    207 |
  • 208 |
209 |
210 |
211 |
212 | 261 |
262 |
263 | 281 |
282 |

Generated using TypeDoc

283 |
284 |
285 | 286 | 287 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-citation-plugin", 3 | "name": "Citations", 4 | "version": "0.4.5", 5 | "minAppVersion": "0.9.20", 6 | "description": "Automatically search and insert citations from a Zotero library", 7 | "author": "Jon Gauthier", 8 | "authorUrl": "http://foldl.me", 9 | "isDesktopOnly": true 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-citation-plugin", 3 | "version": "0.4.5", 4 | "description": "Automatically search and insert citations from a Zotero library", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "rollup --config rollup.config.js -w", 8 | "build": "rollup --config rollup.config.js", 9 | "lint": "eslint . --ext .ts,.tsx", 10 | "test": "jest", 11 | "doc": "typedoc src/types.ts", 12 | "release": "standard-version" 13 | }, 14 | "standard-version": { 15 | "bumpFiles": [ 16 | "manifest.json" 17 | ] 18 | }, 19 | "keywords": [], 20 | "author": "Jon Gauthier", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "@rollup/plugin-commonjs": "^15.1.0", 24 | "@rollup/plugin-json": "^4.1.0", 25 | "@rollup/plugin-node-resolve": "^9.0.0", 26 | "@rollup/plugin-replace": "^2.3.4", 27 | "@rollup/plugin-typescript": "^6.0.0", 28 | "@types/jest": "^26.0.20", 29 | "@types/lodash": "^4.14.167", 30 | "@types/node": "^14.14.2", 31 | "@typescript-eslint/eslint-plugin": "^4.10.0", 32 | "@typescript-eslint/parser": "^4.10.0", 33 | "codemirror": "^5.58.3", 34 | "eslint": "^7.15.0", 35 | "eslint-config-prettier": "^7.0.0", 36 | "eslint-plugin-prettier": "^3.3.0", 37 | "husky": "^4.3.6", 38 | "jest": "^26.6.3", 39 | "lodash": "^4.17.20", 40 | "obsidian": "git+https://github.com/obsidianmd/obsidian-api.git#master", 41 | "prettier": "^2.2.1", 42 | "rollup": "^2.32.1", 43 | "rollup-plugin-web-worker-loader": "^1.5.0", 44 | "ts-jest": "^26.4.4", 45 | "tslib": "^2.0.3", 46 | "typedoc": "^0.20.14", 47 | "typescript": "^4.1.3" 48 | }, 49 | "dependencies": { 50 | "@retorquere/bibtex-parser": "^3.2.30", 51 | "chokidar": "^3.5.0", 52 | "handlebars": "^4.7.6", 53 | "open": "^7.3.0", 54 | "promise-worker": "^2.0.1" 55 | }, 56 | "lint-staged": { 57 | "*.ts": "eslint --fix", 58 | "*.json": "prettier --write" 59 | }, 60 | "jest": { 61 | "moduleNameMapper": { 62 | "src/(.*)": "/src/$1" 63 | }, 64 | "moduleFileExtensions": [ 65 | "js", 66 | "ts", 67 | "d.ts" 68 | ], 69 | "transform": { 70 | "^.+\\.ts$": "ts-jest" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import json from '@rollup/plugin-json'; 5 | import replace from '@rollup/plugin-replace'; 6 | import webWorkerLoader from 'rollup-plugin-web-worker-loader'; 7 | 8 | export default { 9 | input: 'src/main.ts', 10 | output: { 11 | dir: '.', 12 | sourcemap: 'inline', 13 | format: 'cjs', 14 | exports: 'default', 15 | }, 16 | external: ['obsidian', 'path', 'fs', 'util', 'events', 'stream', 'os'], 17 | plugins: [ 18 | /** 19 | * Chokidar hacks to get working with platform-general Electron build. 20 | * 21 | * HACK: Manually replace fsevents import. This is only available on OS X, 22 | * and we need to make a platform-general build here. 23 | */ 24 | replace({ 25 | delimiters: ['', ''], 26 | include: "node_modules/chokidar/**/*.js", 27 | 28 | "require('fsevents')": "null", 29 | "require('fs')": "require('original-fs')", 30 | }), 31 | 32 | typescript(), 33 | nodeResolve({ browser: true }), 34 | commonjs({ ignore: ['original-fs'] }), 35 | json(), 36 | webWorkerLoader({ 37 | targetPlatform: 'browser', 38 | extensions: ['.ts'], 39 | preserveSource: true, 40 | sourcemap: true, 41 | }), 42 | ], 43 | }; 44 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hans/obsidian-citation-plugin/2edeeceaf5f38de90f77f111ab46d24eef3e0bfa/screenshot.png -------------------------------------------------------------------------------- /src/__tests__/library.bib: -------------------------------------------------------------------------------- 1 | @article{Weiner2003, 2 | abstract = {Biomineralization links soft organic tissues, which are compositionally akin to the atmosphere and oceans, with the hard materials of the solid Earth. It provides organisms with skeletons and shells while they are alive, and when they die these are deposited as sediment in environments from river plains to the deep ocean floor. It is also these hard, resistant products of life which are mainly responsible for the Earths fossil record. Consequently, biomineralization involves biologists, chemists, and geologists in interdisciplinary studies at one of the interfaces between Earth and life.}, 3 | archivePrefix = {arXiv}, 4 | arxivId = {1105.3402}, 5 | author = {Weiner, S.}, 6 | doi = {10.2113/0540001}, 7 | eprint = {1105.3402}, 8 | file = {:/Documents/literature/Weiner/Reviews in Mineralogy and Geochemistry/Weiner - 2003 - An Overview of Biomineralization Processes and the Problem of the Vital Effect.pdf:pdf}, 9 | isbn = {1529-6466}, 10 | issn = {1529-6466}, 11 | journal = {Rev. Mineral. Geochemistry}, 12 | number = {1}, 13 | pages = {1--29}, 14 | pmid = {11459040}, 15 | title = {{An Overview of Biomineralization Processes and the Problem of the Vital Effect}}, 16 | url = {http://rimg.geoscienceworld.org/cgi/doi/10.2113/0540001}, 17 | volume = {54}, 18 | year = {2003}, 19 | note = {This is a test note with some \textbf{formatting}.} 20 | } 21 | 22 | @online{abnar2019blackbox, 23 | title = {Blackbox Meets Blackbox: {{Representational Similarity}} and {{Stability Analysis}} of {{Neural Language Models}} and {{Brains}}}, 24 | shorttitle = {Blackbox Meets Blackbox}, 25 | author = {Abnar, Samira and Beinborn, Lisa and Choenni, Rochelle and Zuidema, Willem}, 26 | date = {2019-06-04}, 27 | url = {http://arxiv.org/abs/1906.01539}, 28 | urldate = {2019-08-29}, 29 | abstract = {In this paper, we define and apply representational stability analysis (ReStA), an intuitive way of analyzing neural language models. ReStA is a variant of the popular representational similarity analysis (RSA) in cognitive neuroscience. While RSA can be used to compare representations in models, model components, and human brains, ReStA compares instances of the same model, while systematically varying single model parameter. Using ReStA, we study four recent and successful neural language models, and evaluate how sensitive their internal representations are to the amount of prior context. Using RSA, we perform a systematic study of how similar the representational spaces in the first and second (or higher) layers of these models are to each other and to patterns of activation in the human brain. Our results reveal surprisingly strong differences between language models, and give insights into where the deep linguistic processing, that integrates information over multiple sentences, is happening in these models. The combination of ReStA and RSA on models and brains allows us to start addressing the important question of what kind of linguistic processes we can hope to observe in fMRI brain imaging data. In particular, our results suggest that the data on story reading from Wehbe et al. (2014) contains a signal of shallow linguistic processing, but show no evidence on the more interesting deep linguistic processing.}, 30 | archivePrefix = {arXiv}, 31 | eprint = {1906.01539}, 32 | eprinttype = {arxiv}, 33 | file = {/home/jon/.dropbox-mit/Dropbox (MIT)/Papers/2019/Abnar et al_2019_Blackbox meets blackbox.pdf}, 34 | keywords = {Computer Science - Artificial Intelligence,Computer Science - Computation and Language,Quantitative Biology - Neurons and Cognition}, 35 | langid = {english}, 36 | primaryClass = {cs, q-bio} 37 | } 38 | 39 | @article{aitchison2017you, 40 | title = {With or without You: Predictive Coding and {{Bayesian}} Inference in the Brain}, 41 | shorttitle = {With or without You}, 42 | author = {Aitchison, Laurence and Lengyel, Máté}, 43 | date = {2017-10-01}, 44 | journaltitle = {Current Opinion in Neurobiology}, 45 | shortjournal = {Current Opinion in Neurobiology}, 46 | volume = {46}, 47 | pages = {219--227}, 48 | issn = {0959-4388}, 49 | doi = {10.1016/j.conb.2017.08.010}, 50 | url = {http://www.sciencedirect.com/science/article/pii/S0959438817300454}, 51 | urldate = {2019-06-24}, 52 | abstract = {Two theoretical ideas have emerged recently with the ambition to provide a unifying functional explanation of neural population coding and dynamics: predictive coding and Bayesian inference. Here, we describe the two theories and their combination into a single framework: Bayesian predictive coding. We clarify how the two theories can be distinguished, despite sharing core computational concepts and addressing an overlapping set of empirical phenomena. We argue that predictive coding is an algorithmic/representational motif that can serve several different computational goals of which Bayesian inference is but one. Conversely, while Bayesian inference can utilize predictive coding, it can also be realized by a variety of other representations. We critically evaluate the experimental evidence supporting Bayesian predictive coding and discuss how to test it more directly.}, 53 | file = {/home/jon/.dropbox-mit/Dropbox (MIT)/Papers/2017/Aitchison_Lengyel_2017_With or without you.pdf}, 54 | series = {Computational {{Neuroscience}}} 55 | } 56 | 57 | @inproceedings{alexandrescu2006factored, 58 | title = {Factored {{Neural Language Models}}}, 59 | author = {Alexandrescu, Andrei and Kirchhoff, Katrin}, 60 | date = {2006}, 61 | pages = {1--4}, 62 | publisher = {{Association for Computational Linguistics}}, 63 | url = {http://aclasb.dfki.de/nlp/bib/N06-2001}, 64 | urldate = {2015-02-19}, 65 | eventtitle = {Proceedings of the {{Human Language Technology Conference}} of the {{NAACL}}, {{Companion Volume}}: {{Short Papers}}}, 66 | file = {/home/jon/.dropbox-mit/Dropbox (MIT)/Papers/2006/Alexandrescu_Kirchhoff_2006_Factored Neural Language Models.pdf}, 67 | keywords = {neural language models,neural networks,nlp,unknown words} 68 | } 69 | 70 | @book{bar-ashersiegal2020perspectives, 71 | title = {Perspectives on {{Causation}}: {{Selected Papers}} from the {{Jerusalem}} 2017 {{Workshop}}}, 72 | shorttitle = {Perspectives on {{Causation}}}, 73 | editor = {Bar-Asher Siegal, Elitzur A. and Boneh, Nora}, 74 | date = {2020}, 75 | publisher = {{Springer International Publishing}}, 76 | location = {{Cham}}, 77 | doi = {10.1007/978-3-030-34308-8}, 78 | url = {http://link.springer.com/10.1007/978-3-030-34308-8}, 79 | urldate = {2020-08-10}, 80 | file = {/home/jon/.dropbox-mit/Dropbox (MIT)/Papers/2020/Bar-Asher Siegal_Boneh_2020_Perspectives on Causation.pdf}, 81 | isbn = {978-3-030-34307-1 978-3-030-34308-8}, 82 | langid = {english}, 83 | series = {Jerusalem {{Studies}} in {{Philosophy}} and {{History}} of {{Science}}} 84 | } 85 | 86 | @preamble{ "\ifdefined\DeclarePrefChars\DeclarePrefChars{'’-}\else\fi " } 87 | -------------------------------------------------------------------------------- /src/__tests__/library.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"id":"Weiner2003","abstract":"Biomineralization links soft organic tissues, which are compositionally akin to the atmosphere and oceans, with the hard materials of the solid Earth. It provides organisms with skeletons and shells while they are alive, and when they die these are deposited as sediment in environments from river plains to the deep ocean floor. It is also these hard, resistant products of life which are mainly responsible for the Earths fossil record. Consequently, biomineralization involves biologists, chemists, and geologists in interdisciplinary studies at one of the interfaces between Earth and life.","author":[{"family":"Weiner","given":"S."}],"container-title":"Rev. Mineral. Geochemistry","DOI":"10.2113/0540001","ISBN":"1529-6466","ISSN":"1529-6466","issue":"1","issued":{"date-parts":[[2003]]},"page":"1–29","PMID":"11459040","title":"An overview of biomineralization processes and the problem of the vital effect","type":"article-journal","URL":"http://rimg.geoscienceworld.org/cgi/doi/10.2113/0540001","volume":"54"}, 3 | { 4 | "id": "abnar2019blackbox", 5 | "abstract": "In this paper, we define and apply representational stability analysis (ReStA), an intuitive way of analyzing neural language models. ReStA is a variant of the popular representational similarity analysis (RSA) in cognitive neuroscience. While RSA can be used to compare representations in models, model components, and human brains, ReStA compares instances of the same model, while systematically varying single model parameter. Using ReStA, we study four recent and successful neural language models, and evaluate how sensitive their internal representations are to the amount of prior context. Using RSA, we perform a systematic study of how similar the representational spaces in the first and second (or higher) layers of these models are to each other and to patterns of activation in the human brain. Our results reveal surprisingly strong differences between language models, and give insights into where the deep linguistic processing, that integrates information over multiple sentences, is happening in these models. The combination of ReStA and RSA on models and brains allows us to start addressing the important question of what kind of linguistic processes we can hope to observe in fMRI brain imaging data. In particular, our results suggest that the data on story reading from Wehbe et al. (2014) contains a signal of shallow linguistic processing, but show no evidence on the more interesting deep linguistic processing.", 6 | "accessed": { 7 | "date-parts": [ 8 | [ 9 | 2019, 10 | 8, 11 | 29 12 | ] 13 | ] 14 | }, 15 | "author": [ 16 | { 17 | "family": "Abnar", 18 | "given": "Samira" 19 | }, 20 | { 21 | "family": "Beinborn", 22 | "given": "Lisa" 23 | }, 24 | { 25 | "family": "Choenni", 26 | "given": "Rochelle" 27 | }, 28 | { 29 | "family": "Zuidema", 30 | "given": "Willem" 31 | } 32 | ], 33 | "container-title": "arXiv:1906.01539 [cs, q-bio]", 34 | "issued": { 35 | "date-parts": [ 36 | [ 37 | 2019, 38 | 6, 39 | 4 40 | ] 41 | ] 42 | }, 43 | "language": "en", 44 | "source": "arXiv.org", 45 | "title": "Blackbox meets blackbox: Representational Similarity and Stability Analysis of Neural Language Models and Brains", 46 | "title-short": "Blackbox meets blackbox", 47 | "type": "article-journal", 48 | "URL": "http://arxiv.org/abs/1906.01539" 49 | }, 50 | { 51 | "id": "aitchison2017you", 52 | "abstract": "Two theoretical ideas have emerged recently with the ambition to provide a unifying functional explanation of neural population coding and dynamics: predictive coding and Bayesian inference. Here, we describe the two theories and their combination into a single framework: Bayesian predictive coding. We clarify how the two theories can be distinguished, despite sharing core computational concepts and addressing an overlapping set of empirical phenomena. We argue that predictive coding is an algorithmic/representational motif that can serve several different computational goals of which Bayesian inference is but one. Conversely, while Bayesian inference can utilize predictive coding, it can also be realized by a variety of other representations. We critically evaluate the experimental evidence supporting Bayesian predictive coding and discuss how to test it more directly.", 53 | "accessed": { 54 | "date-parts": [ 55 | [ 56 | 2019, 57 | 6, 58 | 24 59 | ] 60 | ] 61 | }, 62 | "author": [ 63 | { 64 | "family": "Aitchison", 65 | "given": "Laurence" 66 | }, 67 | { 68 | "family": "Lengyel", 69 | "given": "M\u00e1t\u00e9" 70 | } 71 | ], 72 | "collection-title": "Computational Neuroscience", 73 | "container-title": "Current Opinion in Neurobiology", 74 | "container-title-short": "Current Opinion in Neurobiology", 75 | "DOI": "10.1016/j.conb.2017.08.010", 76 | "ISSN": "0959-4388", 77 | "issued": { 78 | "date-parts": [ 79 | [ 80 | 2017, 81 | 10, 82 | 1 83 | ] 84 | ] 85 | }, 86 | "page": "219-227", 87 | "source": "ScienceDirect", 88 | "title": "With or without you: predictive coding and Bayesian inference in the brain", 89 | "title-short": "With or without you", 90 | "type": "article-journal", 91 | "URL": "http://www.sciencedirect.com/science/article/pii/S0959438817300454", 92 | "volume": "46" 93 | }, 94 | { 95 | "id": "alexandrescu2006factored", 96 | "accessed": { 97 | "date-parts": [ 98 | [ 99 | 2015, 100 | 2, 101 | 19 102 | ] 103 | ] 104 | }, 105 | "author": [ 106 | { 107 | "family": "Alexandrescu", 108 | "given": "Andrei" 109 | }, 110 | { 111 | "family": "Kirchhoff", 112 | "given": "Katrin" 113 | } 114 | ], 115 | "event": "Proceedings of the Human Language Technology Conference of the NAACL, Companion Volume: Short Papers", 116 | "issued": { 117 | "date-parts": [ 118 | [ 119 | 2006 120 | ] 121 | ] 122 | }, 123 | "page": "1-4", 124 | "publisher": "Association for Computational Linguistics", 125 | "source": "aclasb.dfki.de", 126 | "title": "Factored Neural Language Models", 127 | "type": "paper-conference", 128 | "URL": "http://aclasb.dfki.de/nlp/bib/N06-2001" 129 | }, 130 | { 131 | "id": "bar-ashersiegal2020perspectives", 132 | "accessed": { 133 | "date-parts": [ 134 | [ 135 | 2020, 136 | 8, 137 | 10 138 | ] 139 | ] 140 | }, 141 | "collection-title": "Jerusalem Studies in Philosophy and History of Science", 142 | "DOI": "10.1007/978-3-030-34308-8", 143 | "editor": [ 144 | { 145 | "family": "Bar-Asher Siegal", 146 | "given": "Elitzur A." 147 | }, 148 | { 149 | "family": "Boneh", 150 | "given": "Nora" 151 | } 152 | ], 153 | "event-place": "Cham", 154 | "ISBN": "978-3-030-34307-1 978-3-030-34308-8", 155 | "issued": { 156 | "date-parts": [ 157 | [ 158 | 2020 159 | ] 160 | ] 161 | }, 162 | "language": "en", 163 | "publisher": "Springer International Publishing", 164 | "publisher-place": "Cham", 165 | "source": "DOI.org (Crossref)", 166 | "title": "Perspectives on Causation: Selected Papers from the Jerusalem 2017 Workshop", 167 | "title-short": "Perspectives on Causation", 168 | "type": "book", 169 | "URL": "http://link.springer.com/10.1007/978-3-030-34308-8" 170 | } 171 | ] 172 | -------------------------------------------------------------------------------- /src/__tests__/regression_7f9aefe.bib: -------------------------------------------------------------------------------- 1 | % This failed because \dag was not understood by the bibtex parser 2 | 3 | @article{Robert2016-ROBJOJ, 4 | author = {Aur\'elien Robert}, 5 | date-added = {2016-06-16 12:24:28 +0000}, 6 | date-modified = {2016-06-16 12:24:28 +0000}, 7 | journal = {British Journal for the History of Philosophy}, 8 | number = {3}, 9 | pages = {490--511}, 10 | title = {John of Jandun on Relations and Cambridge Changes\dag}, 11 | volume = {24}, 12 | year = {2016}} 13 | -------------------------------------------------------------------------------- /src/__tests__/regression_fe15ef6.bib: -------------------------------------------------------------------------------- 1 | @article{Mubeen.Frontiers in Genetics.2019, 2 | title = {{The Impact of Pathway Database Choice on Statistical Enrichment Analysis and Predictive Modeling}}, 3 | author = {Mubeen, Sarah and Hoyt, Charles Tapley and Gemünd, André and Hofmann-Apitius, Martin and Fröhlich, Holger and Domingo-Fernández, Daniel}, 4 | journal = {Frontiers in Genetics}, 5 | issn = {1664-8021}, 6 | doi = {10.3389/fgene.2019.01203}, 7 | pmid = {31824580}, 8 | abstract = {{Pathway-centric approaches are widely used to interpret and contextualize -omics data. However, databases contain different representations of the same biological pathway, which may lead to different results of statistical enrichment analysis and predictive models in the context of precision medicine. We have performed an in-depth benchmarking of the impact of pathway database choice on statistical enrichment analysis and predictive modeling. We analyzed five cancer datasets using three major pathway databases and developed an approach to merge several databases into a single integrative one: MPath. Our results show that equivalent pathways from different databases yield disparate results in statistical enrichment analysis. Moreover, we observed a significant dataset-dependent impact on the performance of machine learning models on different prediction tasks. In some cases, MPath significantly improved prediction performance and also reduced the variance of prediction performances. Furthermore, MPath yielded more consistent and biologically plausible results in statistical enrichment analyses. In summary, this benchmarking study demonstrates that pathway database choice can influence the results of statistical enrichment analysis and predictive modeling. Therefore, we recommend the use of multiple pathway databases or integrative ones.}}, 9 | pages = {1203}, 10 | volume = {10}, 11 | keywords = {\textasciicircumgraph,\textasciicircumGSEA,\textasciicircumInformatic,\textasciicircumnetwork,\textasciicircumpathway enrichment,\textasciicircumreactome,`machinelearning,`stats}, 12 | local-url = {file://localhost/Users/kiefer/Documents/Papers%20Library/2019/Frontiers%20in%20Genetics/Mubeen/Mubeen_Frontiers%20in%20Genetics_2019.pdf}, 13 | year = {2019} 14 | } 15 | -------------------------------------------------------------------------------- /src/__tests__/types.spec.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | import * as _ from 'lodash'; 5 | import { compile as compileTemplate } from 'handlebars'; 6 | 7 | import { 8 | Library, 9 | Entry, 10 | EntryData, 11 | EntryDataBibLaTeX, 12 | EntryDataCSL, 13 | EntryBibLaTeXAdapter, 14 | EntryCSLAdapter, 15 | loadEntries, 16 | } from '../types'; 17 | 18 | const expectedRender: Record[] = [ 19 | { 20 | citekey: 'Weiner2003', 21 | abstract: 22 | 'Biomineralization links soft organic tissues, which are compositionally akin to the atmosphere and oceans, with the hard materials of the solid Earth. It provides organisms with skeletons and shells while they are alive, and when they die these are deposited as sediment in environments from river plains to the deep ocean floor. It is also these hard, resistant products of life which are mainly responsible for the Earths fossil record. Consequently, biomineralization involves biologists, chemists, and geologists in interdisciplinary studies at one of the interfaces between Earth and life.', 23 | authorString: 'S. Weiner', 24 | containerTitle: 'Rev. Mineral. Geochemistry', 25 | DOI: '10.2113/0540001', 26 | eprint: '1105.3402', 27 | note: 'This is a test note with some formatting.', 28 | page: '1-29', 29 | title: 30 | 'An Overview of Biomineralization Processes and the Problem of the Vital Effect', 31 | URL: 'http://rimg.geoscienceworld.org/cgi/doi/10.2113/0540001', 32 | year: '2003', 33 | zoteroSelectURI: 'zotero://select/items/@Weiner2003', 34 | }, 35 | { 36 | citekey: 'abnar2019blackbox', 37 | abstract: 38 | 'In this paper, we define and apply representational stability analysis (ReStA), an intuitive way of analyzing neural language models. ReStA is a variant of the popular representational similarity analysis (RSA) in cognitive neuroscience. While RSA can be used to compare representations in models, model components, and human brains, ReStA compares instances of the same model, while systematically varying single model parameter. Using ReStA, we study four recent and successful neural language models, and evaluate how sensitive their internal representations are to the amount of prior context. Using RSA, we perform a systematic study of how similar the representational spaces in the first and second (or higher) layers of these models are to each other and to patterns of activation in the human brain. Our results reveal surprisingly strong differences between language models, and give insights into where the deep linguistic processing, that integrates information over multiple sentences, is happening in these models. The combination of ReStA and RSA on models and brains allows us to start addressing the important question of what kind of linguistic processes we can hope to observe in fMRI brain imaging data. In particular, our results suggest that the data on story reading from Wehbe et al. (2014) contains a signal of shallow linguistic processing, but show no evidence on the more interesting deep linguistic processing.', 39 | authorString: 40 | 'Samira Abnar, Lisa Beinborn, Rochelle Choenni, Willem Zuidema', 41 | containerTitle: 'arxiv:1906.01539 [cs, q-bio]', 42 | DOI: undefined, 43 | eprint: '1906.01539', 44 | eprinttype: 'arxiv', 45 | page: undefined, 46 | title: 47 | 'Blackbox meets blackbox: Representational Similarity and Stability Analysis of Neural Language Models and Brains', 48 | titleShort: "Blackbox meets blackbox", 49 | URL: 'http://arxiv.org/abs/1906.01539', 50 | year: '2019', 51 | zoteroSelectURI: 'zotero://select/items/@abnar2019blackbox', 52 | }, 53 | { 54 | citekey: 'aitchison2017you', 55 | abstract: 56 | 'Two theoretical ideas have emerged recently with the ambition to provide a unifying functional explanation of neural population coding and dynamics: predictive coding and Bayesian inference. Here, we describe the two theories and their combination into a single framework: Bayesian predictive coding. We clarify how the two theories can be distinguished, despite sharing core computational concepts and addressing an overlapping set of empirical phenomena. We argue that predictive coding is an algorithmic/representational motif that can serve several different computational goals of which Bayesian inference is but one. Conversely, while Bayesian inference can utilize predictive coding, it can also be realized by a variety of other representations. We critically evaluate the experimental evidence supporting Bayesian predictive coding and discuss how to test it more directly.', 57 | authorString: 'Laurence Aitchison, Máté Lengyel', 58 | containerTitle: 'Current Opinion in Neurobiology', 59 | DOI: '10.1016/j.conb.2017.08.010', 60 | page: '219–227', 61 | title: 62 | 'With or without you: Predictive coding and Bayesian inference in the brain', 63 | URL: 'http://www.sciencedirect.com/science/article/pii/S0959438817300454', 64 | year: '2017', 65 | zoteroSelectURI: 'zotero://select/items/@aitchison2017you', 66 | }, 67 | { 68 | citekey: 'alexandrescu2006factored', 69 | abstract: undefined, 70 | authorString: 'Andrei Alexandrescu, Katrin Kirchhoff', 71 | containerTitle: undefined, 72 | DOI: undefined, 73 | page: '1–4', 74 | title: 'Factored Neural Language Models', 75 | URL: 'http://aclasb.dfki.de/nlp/bib/N06-2001', 76 | year: '2006', 77 | zoteroSelectURI: 'zotero://select/items/@alexandrescu2006factored', 78 | publisher: 'Association for Computational Linguistics', 79 | }, 80 | { 81 | citekey: 'bar-ashersiegal2020perspectives', 82 | abstract: undefined, 83 | authorString: undefined, 84 | containerTitle: undefined, 85 | DOI: '10.1007/978-3-030-34308-8', 86 | page: undefined, 87 | title: 88 | 'Perspectives on Causation: Selected Papers from the Jerusalem 2017 Workshop', 89 | URL: 'http://link.springer.com/10.1007/978-3-030-34308-8', 90 | year: '2020', 91 | zoteroSelectURI: 'zotero://select/items/@bar-ashersiegal2020perspectives', 92 | publisher: 'Springer International Publishing', 93 | publisherPlace: 'Cham', 94 | }, 95 | ]; 96 | 97 | /** 98 | * Fields available only in the BibLaTeX format, and which shouldn't be checked 99 | * against CSL format 100 | */ 101 | const BIBLATEX_FIELDS_ONLY = ['eprint', 'eprinttype', 'files', 'note']; 102 | 103 | // Test whether loaded and expected libraries are the same, ignoring casing and 104 | // hyphenation and the `entry` field 105 | function matchLibraryRender( 106 | actual: Record[], 107 | expected: Record[], 108 | dropFields?: string[], 109 | ): void { 110 | const transform = (dict: Record): Record => { 111 | delete dict.entry; 112 | 113 | if (dropFields) { 114 | dropFields.forEach((f) => delete dict[f]); 115 | } 116 | 117 | return _.mapValues(dict, (val: unknown) => 118 | val 119 | ?.toString() 120 | .toLowerCase() 121 | .replace(/[\u2012-\u2014]/g, '-'), 122 | ); 123 | }; 124 | 125 | actual = actual.map(transform); 126 | expected = expected.map(transform); 127 | 128 | expect(actual).toMatchObject(expected); 129 | } 130 | 131 | function loadBibLaTeXEntries(filename: string): EntryDataBibLaTeX[] { 132 | const biblatexPath = path.join(__dirname, filename); 133 | const biblatex = fs.readFileSync(biblatexPath, 'utf-8'); 134 | return loadEntries(biblatex, 'biblatex') as EntryDataBibLaTeX[]; 135 | } 136 | 137 | function loadBibLaTeXLibrary(entries: EntryDataBibLaTeX[]): Library { 138 | return new Library( 139 | Object.fromEntries( 140 | entries.map((e: EntryDataBibLaTeX) => [ 141 | e.key, 142 | new EntryBibLaTeXAdapter(e), 143 | ]), 144 | ), 145 | ); 146 | }; 147 | 148 | const renderAdvancedTemplate = ( 149 | loadLibrary: () => Library, 150 | citekey: string, 151 | ) => { 152 | const library = loadLibrary(); 153 | const template = 154 | '{{#each entry.author}}[[{{this.family}}, {{this.given}}]]{{#unless @last}}, {{/unless}}{{/each}}'; 155 | return compileTemplate(template)( 156 | library.getTemplateVariablesForCitekey(citekey), 157 | ); 158 | }; 159 | 160 | describe('biblatex library', () => { 161 | let entries: EntryDataBibLaTeX[]; 162 | beforeEach(() => { 163 | entries = loadBibLaTeXEntries('library.bib'); 164 | }); 165 | const loadLibrary = () => loadBibLaTeXLibrary(entries); 166 | 167 | test('loads', () => { 168 | expect(entries.length).toBe(5); 169 | }); 170 | 171 | test('can support library', () => { 172 | const library = loadLibrary(); 173 | }); 174 | 175 | test('renders correctly', () => { 176 | const library = loadLibrary(); 177 | const templateVariables: Record[] = Object.keys( 178 | library.entries, 179 | ).map((citekey) => { 180 | return library.getTemplateVariablesForCitekey(citekey); 181 | }); 182 | 183 | matchLibraryRender(templateVariables, expectedRender); 184 | }); 185 | 186 | test('advanced template render', () => { 187 | const render = renderAdvancedTemplate(loadLibrary, 'aitchison2017you'); 188 | expect(render).toBe('[[Aitchison, Laurence]], [[Lengyel, Máté]]'); 189 | }); 190 | }); 191 | 192 | describe('biblatex regression tests', () => { 193 | test('regression 7f9aefe (non-fatal parser error handling)', () => { 194 | const load = () => { 195 | const library = loadBibLaTeXLibrary( 196 | loadBibLaTeXEntries('regression_7f9aefe.bib'), 197 | ); 198 | }; 199 | 200 | // Make sure we log warning 201 | const warnCallback = jest.fn(); 202 | jest.spyOn(global.console, 'warn').mockImplementation(warnCallback); 203 | 204 | expect(load).not.toThrowError(); 205 | expect(warnCallback.mock.calls.length).toBe(1); 206 | }); 207 | 208 | test('regression fe15ef6 (fatal parser error handling)', () => { 209 | const load = () => { 210 | const library = loadBibLaTeXLibrary( 211 | loadBibLaTeXEntries('regression_fe15ef6.bib'), 212 | ); 213 | }; 214 | 215 | // Make sure we log warning 216 | const warnCallback = jest.fn(); 217 | jest.spyOn(global.console, 'error').mockImplementation(warnCallback); 218 | 219 | expect(load).not.toThrowError(); 220 | expect(warnCallback.mock.calls.length).toBe(1); 221 | }); 222 | }); 223 | 224 | describe('csl library', () => { 225 | let entries: EntryDataCSL[]; 226 | beforeEach(() => { 227 | const cslPath = path.join(__dirname, 'library.json'); 228 | const csl = fs.readFileSync(cslPath, 'utf-8'); 229 | entries = loadEntries(csl, 'csl-json') as EntryDataCSL[]; 230 | }); 231 | 232 | test('loads', () => { 233 | expect(entries.length).toBe(5); 234 | }); 235 | 236 | function loadLibrary(): Library { 237 | return new Library( 238 | Object.fromEntries( 239 | entries.map((e: EntryDataCSL) => [e.id, new EntryCSLAdapter(e)]), 240 | ), 241 | ); 242 | } 243 | 244 | test('can support library', () => { 245 | const library = loadLibrary(); 246 | }); 247 | 248 | test('renders correctly', () => { 249 | const library = loadLibrary(); 250 | const templateVariables: Record[] = Object.keys( 251 | library.entries, 252 | ).map((citekey) => { 253 | return library.getTemplateVariablesForCitekey(citekey); 254 | }); 255 | 256 | matchLibraryRender(templateVariables, expectedRender, BIBLATEX_FIELDS_ONLY); 257 | }); 258 | 259 | test('advanced template render', () => { 260 | const render = renderAdvancedTemplate(loadLibrary, 'aitchison2017you'); 261 | expect(render).toBe('[[Aitchison, Laurence]], [[Lengyel, Máté]]'); 262 | }); 263 | }); 264 | -------------------------------------------------------------------------------- /src/events.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines an event manager for the citations plugin. 3 | */ 4 | 5 | import { Events, EventRef } from 'obsidian'; 6 | 7 | export default class CitationEvents extends Events { 8 | on(name: 'library-load-start', callback: () => any, ctx?: any): EventRef; 9 | on(name: 'library-load-complete', callback: () => any, ctx?: any): EventRef; 10 | on(name: string, callback: (...data: any[]) => any, ctx?: any): EventRef { 11 | return super.on(name, callback, ctx); 12 | } 13 | 14 | trigger(name: 'library-load-start'): void; 15 | trigger(name: 'library-load-complete'): void; 16 | trigger(name: string, ...data: any[]): void { 17 | super.trigger(name, data); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FileSystemAdapter, 3 | MarkdownSourceView, 4 | MarkdownView, 5 | normalizePath, 6 | Plugin, 7 | TFile, 8 | } from 'obsidian'; 9 | import * as path from 'path'; 10 | import * as chokidar from 'chokidar'; 11 | import * as CodeMirror from 'codemirror'; 12 | 13 | import { 14 | compile as compileTemplate, 15 | TemplateDelegate as Template, 16 | } from 'handlebars'; 17 | 18 | 19 | import CitationEvents from './events'; 20 | import { 21 | InsertCitationModal, 22 | InsertNoteLinkModal, 23 | InsertNoteContentModal, 24 | OpenNoteModal, 25 | } from './modals'; 26 | import { VaultExt } from './obsidian-extensions.d'; 27 | import { CitationSettingTab, CitationsPluginSettings } from './settings'; 28 | import { 29 | Entry, 30 | EntryData, 31 | EntryBibLaTeXAdapter, 32 | EntryCSLAdapter, 33 | IIndexable, 34 | Library, 35 | } from './types'; 36 | import { 37 | DISALLOWED_FILENAME_CHARACTERS_RE, 38 | Notifier, 39 | WorkerManager, 40 | WorkerManagerBlocked, 41 | } from './util'; 42 | import LoadWorker from 'web-worker:./worker'; 43 | 44 | export default class CitationPlugin extends Plugin { 45 | settings: CitationsPluginSettings; 46 | library: Library; 47 | 48 | // Template compilation options 49 | private templateSettings = { 50 | noEscape: true, 51 | }; 52 | 53 | private loadWorker = new WorkerManager(new LoadWorker(), { 54 | blockingChannel: true, 55 | }); 56 | 57 | events = new CitationEvents(); 58 | 59 | loadErrorNotifier = new Notifier( 60 | 'Unable to load citations. Please update Citations plugin settings.', 61 | ); 62 | literatureNoteErrorNotifier = new Notifier( 63 | 'Unable to access literature note. Please check that the literature note folder exists, or update the Citations plugin settings.', 64 | ); 65 | 66 | get editor(): CodeMirror.Editor { 67 | const view = this.app.workspace.activeLeaf.view; 68 | if (!(view instanceof MarkdownView)) return null; 69 | 70 | const sourceView = view.sourceMode; 71 | return (sourceView as MarkdownSourceView).cmEditor; 72 | } 73 | 74 | async loadSettings(): Promise { 75 | this.settings = new CitationsPluginSettings(); 76 | 77 | const loadedSettings = await this.loadData(); 78 | if (!loadedSettings) return; 79 | 80 | const toLoad = [ 81 | 'citationExportPath', 82 | 'citationExportFormat', 83 | 'literatureNoteTitleTemplate', 84 | 'literatureNoteFolder', 85 | 'literatureNoteContentTemplate', 86 | 'markdownCitationTemplate', 87 | 'alternativeMarkdownCitationTemplate', 88 | ]; 89 | toLoad.forEach((setting) => { 90 | if (setting in loadedSettings) { 91 | (this.settings as IIndexable)[setting] = loadedSettings[setting]; 92 | } 93 | }); 94 | } 95 | 96 | async saveSettings(): Promise { 97 | await this.saveData(this.settings); 98 | } 99 | 100 | onload(): void { 101 | this.loadSettings().then(() => this.init()); 102 | } 103 | 104 | async init(): Promise { 105 | if (this.settings.citationExportPath) { 106 | // Load library for the first time 107 | this.loadLibrary(); 108 | 109 | // Set up a watcher to refresh whenever the export is updated 110 | try { 111 | // Wait until files are finished being written before going ahead with 112 | // the refresh -- here, we request that `change` events be accumulated 113 | // until nothing shows up for 500 ms 114 | // TODO magic number 115 | const watchOptions = { 116 | awaitWriteFinish: { 117 | stabilityThreshold: 500, 118 | }, 119 | }; 120 | 121 | chokidar 122 | .watch( 123 | this.resolveLibraryPath(this.settings.citationExportPath), 124 | watchOptions, 125 | ) 126 | .on('change', () => { 127 | this.loadLibrary(); 128 | }); 129 | } catch { 130 | this.loadErrorNotifier.show(); 131 | } 132 | } else { 133 | // TODO show warning? 134 | } 135 | 136 | this.addCommand({ 137 | id: 'open-literature-note', 138 | name: 'Open literature note', 139 | hotkeys: [{ modifiers: ['Ctrl', 'Shift'], key: 'o' }], 140 | callback: () => { 141 | const modal = new OpenNoteModal(this.app, this); 142 | modal.open(); 143 | }, 144 | }); 145 | 146 | this.addCommand({ 147 | id: 'update-bib-data', 148 | name: 'Refresh citation database', 149 | hotkeys: [{ modifiers: ['Ctrl', 'Shift'], key: 'r' }], 150 | callback: () => { 151 | this.loadLibrary(); 152 | }, 153 | }); 154 | 155 | this.addCommand({ 156 | id: 'insert-citation', 157 | name: 'Insert literature note link', 158 | hotkeys: [{ modifiers: ['Ctrl', 'Shift'], key: 'e' }], 159 | callback: () => { 160 | const modal = new InsertNoteLinkModal(this.app, this); 161 | modal.open(); 162 | }, 163 | }); 164 | 165 | this.addCommand({ 166 | id: 'insert-literature-note-content', 167 | name: 'Insert literature note content in the current pane', 168 | callback: () => { 169 | const modal = new InsertNoteContentModal(this.app, this); 170 | modal.open(); 171 | }, 172 | }); 173 | 174 | this.addCommand({ 175 | id: 'insert-markdown-citation', 176 | name: 'Insert Markdown citation', 177 | callback: () => { 178 | const modal = new InsertCitationModal(this.app, this); 179 | modal.open(); 180 | }, 181 | }); 182 | 183 | this.addSettingTab(new CitationSettingTab(this.app, this)); 184 | } 185 | 186 | /** 187 | * Resolve a provided library path, allowing for relative paths rooted at 188 | * the vault directory. 189 | */ 190 | resolveLibraryPath(rawPath: string): string { 191 | const vaultRoot = 192 | this.app.vault.adapter instanceof FileSystemAdapter 193 | ? this.app.vault.adapter.getBasePath() 194 | : '/'; 195 | return path.resolve(vaultRoot, rawPath); 196 | } 197 | 198 | async loadLibrary(): Promise { 199 | console.debug('Citation plugin: Reloading library'); 200 | if (this.settings.citationExportPath) { 201 | const filePath = this.resolveLibraryPath( 202 | this.settings.citationExportPath, 203 | ); 204 | 205 | // Unload current library. 206 | this.events.trigger('library-load-start'); 207 | this.library = null; 208 | 209 | return FileSystemAdapter.readLocalFile(filePath) 210 | .then((buffer) => { 211 | // If there is a remaining error message, hide it 212 | this.loadErrorNotifier.hide(); 213 | 214 | // Decode file as UTF-8. 215 | const dataView = new DataView(buffer); 216 | const decoder = new TextDecoder('utf8'); 217 | const value = decoder.decode(dataView); 218 | 219 | return this.loadWorker.post({ 220 | databaseRaw: value, 221 | databaseType: this.settings.citationExportFormat, 222 | }); 223 | }) 224 | .then((entries: EntryData[]) => { 225 | let adapter: new (data: EntryData) => Entry; 226 | let idKey: string; 227 | 228 | switch (this.settings.citationExportFormat) { 229 | case 'biblatex': 230 | adapter = EntryBibLaTeXAdapter; 231 | idKey = 'key'; 232 | break; 233 | case 'csl-json': 234 | adapter = EntryCSLAdapter; 235 | idKey = 'id'; 236 | break; 237 | } 238 | 239 | this.library = new Library( 240 | Object.fromEntries( 241 | entries.map((e) => [(e as IIndexable)[idKey], new adapter(e)]), 242 | ), 243 | ); 244 | console.debug( 245 | `Citation plugin: successfully loaded library with ${this.library.size} entries.`, 246 | ); 247 | 248 | this.events.trigger('library-load-complete'); 249 | 250 | return this.library; 251 | }) 252 | .catch((e) => { 253 | if (e instanceof WorkerManagerBlocked) { 254 | // Silently catch WorkerManager error, which will be thrown if the 255 | // library is already being loaded 256 | return; 257 | } 258 | 259 | console.error(e); 260 | this.loadErrorNotifier.show(); 261 | 262 | return null; 263 | }); 264 | } else { 265 | console.warn( 266 | 'Citations plugin: citation export path is not set. Please update plugin settings.', 267 | ); 268 | } 269 | } 270 | 271 | /** 272 | * Returns true iff the library is currently being loaded on the worker thread. 273 | */ 274 | get isLibraryLoading(): boolean { 275 | return this.loadWorker.blocked; 276 | } 277 | 278 | get literatureNoteTitleTemplate(): Template { 279 | return compileTemplate( 280 | this.settings.literatureNoteTitleTemplate, 281 | this.templateSettings, 282 | ); 283 | } 284 | 285 | get literatureNoteContentTemplate(): Template { 286 | return compileTemplate( 287 | this.settings.literatureNoteContentTemplate, 288 | this.templateSettings, 289 | ); 290 | } 291 | 292 | get markdownCitationTemplate(): Template { 293 | return compileTemplate( 294 | this.settings.markdownCitationTemplate, 295 | this.templateSettings, 296 | ); 297 | } 298 | 299 | get alternativeMarkdownCitationTemplate(): Template { 300 | return compileTemplate( 301 | this.settings.alternativeMarkdownCitationTemplate, 302 | this.templateSettings, 303 | ); 304 | } 305 | 306 | getTitleForCitekey(citekey: string): string { 307 | const unsafeTitle = this.literatureNoteTitleTemplate( 308 | this.library.getTemplateVariablesForCitekey(citekey), 309 | ); 310 | return unsafeTitle.replace(DISALLOWED_FILENAME_CHARACTERS_RE, '_'); 311 | } 312 | 313 | getPathForCitekey(citekey: string): string { 314 | const title = this.getTitleForCitekey(citekey); 315 | // TODO escape note title 316 | return path.join(this.settings.literatureNoteFolder, `${title}.md`); 317 | } 318 | 319 | getInitialContentForCitekey(citekey: string): string { 320 | return this.literatureNoteContentTemplate( 321 | this.library.getTemplateVariablesForCitekey(citekey), 322 | ); 323 | } 324 | 325 | getMarkdownCitationForCitekey(citekey: string): string { 326 | return this.markdownCitationTemplate( 327 | this.library.getTemplateVariablesForCitekey(citekey), 328 | ); 329 | } 330 | 331 | getAlternativeMarkdownCitationForCitekey(citekey: string): string { 332 | return this.alternativeMarkdownCitationTemplate( 333 | this.library.getTemplateVariablesForCitekey(citekey), 334 | ); 335 | } 336 | 337 | /** 338 | * Run a case-insensitive search for the literature note file corresponding to 339 | * the given citekey. If no corresponding file is found, create one. 340 | */ 341 | async getOrCreateLiteratureNoteFile(citekey: string): Promise { 342 | const path = this.getPathForCitekey(citekey); 343 | const normalizedPath = normalizePath(path); 344 | 345 | let file = this.app.vault.getAbstractFileByPath(normalizedPath); 346 | if (file == null) { 347 | // First try a case-insensitive lookup. 348 | const matches = this.app.vault 349 | .getMarkdownFiles() 350 | .filter((f) => f.path.toLowerCase() == normalizedPath.toLowerCase()); 351 | if (matches.length > 0) { 352 | file = matches[0]; 353 | } else { 354 | try { 355 | file = await this.app.vault.create( 356 | path, 357 | this.getInitialContentForCitekey(citekey), 358 | ); 359 | } catch (exc) { 360 | this.literatureNoteErrorNotifier.show(); 361 | throw exc; 362 | } 363 | } 364 | } 365 | 366 | return file as TFile; 367 | } 368 | 369 | async openLiteratureNote(citekey: string, newPane: boolean): Promise { 370 | this.getOrCreateLiteratureNoteFile(citekey) 371 | .then((file: TFile) => { 372 | this.app.workspace.getLeaf(newPane).openFile(file); 373 | }) 374 | .catch(console.error); 375 | } 376 | 377 | async insertLiteratureNoteLink(citekey: string): Promise { 378 | this.getOrCreateLiteratureNoteFile(citekey) 379 | .then((file: TFile) => { 380 | const useMarkdown: boolean = (this.app.vault).getConfig( 381 | 'useMarkdownLinks', 382 | ); 383 | const title = this.getTitleForCitekey(citekey); 384 | 385 | let linkText: string; 386 | if (useMarkdown) { 387 | const uri = encodeURI( 388 | this.app.metadataCache.fileToLinktext(file, '', false), 389 | ); 390 | linkText = `[${title}](${uri})`; 391 | } else { 392 | linkText = `[[${title}]]`; 393 | } 394 | 395 | this.editor.replaceSelection(linkText); 396 | }) 397 | .catch(console.error); 398 | } 399 | 400 | /** 401 | * Format literature note content for a given reference and insert in the 402 | * currently active pane. 403 | */ 404 | async insertLiteratureNoteContent(citekey: string): Promise { 405 | const content = this.getInitialContentForCitekey(citekey); 406 | this.editor.replaceRange(content, this.editor.getCursor()); 407 | } 408 | 409 | async insertMarkdownCitation( 410 | citekey: string, 411 | alternative = false, 412 | ): Promise { 413 | const func = alternative 414 | ? this.getAlternativeMarkdownCitationForCitekey 415 | : this.getMarkdownCitationForCitekey; 416 | const citation = func.bind(this)(citekey); 417 | 418 | this.editor.replaceRange(citation, this.editor.getCursor()); 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /src/modals.ts: -------------------------------------------------------------------------------- 1 | import { 2 | App, 3 | EventRef, 4 | FuzzyMatch, 5 | FuzzySuggestModal, 6 | Notice, 7 | renderMatches, 8 | SearchMatches, 9 | SearchMatchPart, 10 | } from 'obsidian'; 11 | import CitationPlugin from './main'; 12 | import { Entry } from './types'; 13 | 14 | // Stub some methods we know are there.. 15 | interface FuzzySuggestModalExt extends FuzzySuggestModal { 16 | chooser: ChooserExt; 17 | } 18 | interface ChooserExt { 19 | useSelectedItem(evt: MouseEvent | KeyboardEvent): void; 20 | } 21 | 22 | class SearchModal extends FuzzySuggestModal { 23 | plugin: CitationPlugin; 24 | limit = 50; 25 | 26 | loadingEl: HTMLElement; 27 | 28 | eventRefs: EventRef[]; 29 | 30 | constructor(app: App, plugin: CitationPlugin) { 31 | super(app); 32 | this.plugin = plugin; 33 | 34 | this.resultContainerEl.addClass('zoteroModalResults'); 35 | 36 | this.inputEl.setAttribute('spellcheck', 'false'); 37 | 38 | this.loadingEl = this.resultContainerEl.parentElement.createEl('div', { 39 | cls: 'zoteroModalLoading', 40 | }); 41 | this.loadingEl.createEl('div', { cls: 'zoteroModalLoadingAnimation' }); 42 | this.loadingEl.createEl('p', { 43 | text: 'Loading citation database. Please wait...', 44 | }); 45 | } 46 | 47 | onOpen() { 48 | super.onOpen(); 49 | 50 | this.eventRefs = [ 51 | this.plugin.events.on('library-load-start', () => { 52 | this.setLoading(true); 53 | }), 54 | 55 | this.plugin.events.on('library-load-complete', () => { 56 | this.setLoading(false); 57 | }), 58 | ]; 59 | 60 | this.setLoading(this.plugin.isLibraryLoading); 61 | 62 | // Don't immediately register keyevent listeners. If the modal was triggered 63 | // by an "Enter" keystroke (e.g. via the Obsidian command dialog), this event 64 | // will be received here erroneously. 65 | setTimeout(() => { 66 | this.inputEl.addEventListener('keydown', (ev) => this.onInputKeydown(ev)); 67 | this.inputEl.addEventListener('keyup', (ev) => this.onInputKeyup(ev)); 68 | }, 200); 69 | } 70 | 71 | onClose() { 72 | this.eventRefs?.forEach((e) => this.plugin.events.offref(e)); 73 | } 74 | 75 | getItems(): Entry[] { 76 | if (this.plugin.isLibraryLoading) { 77 | return []; 78 | } 79 | 80 | return Object.values(this.plugin.library.entries); 81 | } 82 | 83 | getItemText(item: Entry): string { 84 | return `${item.title} ${item.authorString} ${item.year}`; 85 | } 86 | 87 | setLoading(loading: boolean): void { 88 | if (loading) { 89 | this.loadingEl.removeClass('d-none'); 90 | this.inputEl.disabled = true; 91 | this.resultContainerEl.empty(); 92 | } else { 93 | this.loadingEl.addClass('d-none'); 94 | this.inputEl.disabled = false; 95 | this.inputEl.focus(); 96 | 97 | // @ts-ignore: not exposed in API. 98 | this.updateSuggestions(); 99 | } 100 | } 101 | 102 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 103 | onChooseItem(item: Entry, evt: MouseEvent | KeyboardEvent): void { 104 | this.plugin.openLiteratureNote(item.id, false).catch(console.error); 105 | } 106 | 107 | renderSuggestion(match: FuzzyMatch, el: HTMLElement): void { 108 | el.empty(); 109 | const entry = match.item; 110 | const entryTitle = entry.title || ''; 111 | 112 | const container = el.createEl('div', { cls: 'zoteroResult' }); 113 | const titleEl = container.createEl('span', { 114 | cls: 'zoteroTitle', 115 | }); 116 | container.createEl('span', { cls: 'zoteroCitekey', text: entry.id }); 117 | 118 | const authorsCls = entry.authorString 119 | ? 'zoteroAuthors' 120 | : 'zoteroAuthors zoteroAuthorsEmpty'; 121 | const authorsEl = container.createEl('span', { 122 | cls: authorsCls, 123 | }); 124 | 125 | // Prepare to highlight string matches for each part of the search item. 126 | // Compute offsets of each rendered element's content within the string 127 | // returned by `getItemText`. 128 | const allMatches = match.match.matches; 129 | const authorStringOffset = 1 + entryTitle.length; 130 | 131 | // Filter a match list to contain only the relevant matches for a given 132 | // substring, and with match indices shifted relative to the start of that 133 | // substring 134 | const shiftMatches = ( 135 | matches: SearchMatches, 136 | start: number, 137 | end: number, 138 | ) => { 139 | return matches 140 | .map((match: SearchMatchPart) => { 141 | const [matchStart, matchEnd] = match; 142 | return [ 143 | matchStart - start, 144 | Math.min(matchEnd - start, end), 145 | ] as SearchMatchPart; 146 | }) 147 | .filter((match: SearchMatchPart) => { 148 | const [matchStart, matchEnd] = match; 149 | return matchStart >= 0; 150 | }); 151 | }; 152 | 153 | // Now highlight matched strings within each element 154 | renderMatches( 155 | titleEl, 156 | entryTitle, 157 | shiftMatches(allMatches, 0, entryTitle.length), 158 | ); 159 | if (entry.authorString) { 160 | renderMatches( 161 | authorsEl, 162 | entry.authorString, 163 | shiftMatches( 164 | allMatches, 165 | authorStringOffset, 166 | authorStringOffset + entry.authorString.length, 167 | ), 168 | ); 169 | } 170 | } 171 | 172 | onInputKeydown(ev: KeyboardEvent) { 173 | if (ev.key == 'Tab') { 174 | ev.preventDefault(); 175 | } 176 | } 177 | 178 | onInputKeyup(ev: KeyboardEvent) { 179 | if (ev.key == 'Enter' || ev.key == 'Tab') { 180 | ((this as unknown) as FuzzySuggestModalExt).chooser.useSelectedItem( 181 | ev, 182 | ); 183 | } 184 | } 185 | } 186 | 187 | export class OpenNoteModal extends SearchModal { 188 | constructor(app: App, plugin: CitationPlugin) { 189 | super(app, plugin); 190 | 191 | this.setInstructions([ 192 | { command: '↑↓', purpose: 'to navigate' }, 193 | { command: '↵', purpose: 'to open literature note' }, 194 | { command: 'ctrl ↵', purpose: 'to open literature note in a new pane' }, 195 | { command: 'tab', purpose: 'open in Zotero' }, 196 | { command: 'shift tab', purpose: 'open PDF' }, 197 | { command: 'esc', purpose: 'to dismiss' }, 198 | ]); 199 | } 200 | 201 | onChooseItem(item: Entry, evt: MouseEvent | KeyboardEvent): void { 202 | if (evt instanceof MouseEvent || evt.key == 'Enter') { 203 | const newPane = 204 | evt instanceof KeyboardEvent && (evt as KeyboardEvent).ctrlKey; 205 | this.plugin.openLiteratureNote(item.id, newPane); 206 | } else if (evt.key == 'Tab') { 207 | if (evt.shiftKey) { 208 | const files = item.files || []; 209 | const pdfPaths = files.filter((path) => 210 | path.toLowerCase().endsWith('pdf'), 211 | ); 212 | if (pdfPaths.length == 0) { 213 | new Notice('This reference has no associated PDF files.'); 214 | } else { 215 | open(`file://${pdfPaths[0]}`); 216 | } 217 | } else { 218 | open(item.zoteroSelectURI); 219 | } 220 | } 221 | } 222 | } 223 | 224 | export class InsertNoteLinkModal extends SearchModal { 225 | constructor(app: App, plugin: CitationPlugin) { 226 | super(app, plugin); 227 | 228 | this.setInstructions([ 229 | { command: '↑↓', purpose: 'to navigate' }, 230 | { command: '↵', purpose: 'to insert literature note reference' }, 231 | { command: 'esc', purpose: 'to dismiss' }, 232 | ]); 233 | } 234 | 235 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 236 | onChooseItem(item: Entry, evt: unknown): void { 237 | this.plugin.insertLiteratureNoteLink(item.id).catch(console.error); 238 | } 239 | } 240 | 241 | export class InsertNoteContentModal extends SearchModal { 242 | constructor(app: App, plugin: CitationPlugin) { 243 | super(app, plugin); 244 | 245 | this.setInstructions([ 246 | { command: '↑↓', purpose: 'to navigate' }, 247 | { 248 | command: '↵', 249 | purpose: 'to insert literature note content in active pane', 250 | }, 251 | { command: 'esc', purpose: 'to dismiss' }, 252 | ]); 253 | } 254 | 255 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 256 | onChooseItem(item: Entry, evt: unknown): void { 257 | this.plugin.insertLiteratureNoteContent(item.id).catch(console.error); 258 | } 259 | } 260 | 261 | export class InsertCitationModal extends SearchModal { 262 | constructor(app: App, plugin: CitationPlugin) { 263 | super(app, plugin); 264 | 265 | this.setInstructions([ 266 | { command: '↑↓', purpose: 'to navigate' }, 267 | { command: '↵', purpose: 'to insert Markdown citation' }, 268 | { command: 'shift ↵', purpose: 'to insert secondary Markdown citation' }, 269 | { command: 'esc', purpose: 'to dismiss' }, 270 | ]); 271 | } 272 | 273 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 274 | onChooseItem(item: Entry, evt: MouseEvent | KeyboardEvent): void { 275 | const isAlternative = evt instanceof KeyboardEvent && evt.shiftKey; 276 | this.plugin 277 | .insertMarkdownCitation(item.id, isAlternative) 278 | .catch(console.error); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/obsidian-extensions.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Hackily exposes undocumented parts of the Obsidian implementation for our use. 3 | * Also extends some types to make our lives easier. 4 | */ 5 | 6 | import { EventRef, Vault, Workspace } from 'obsidian'; 7 | 8 | export class VaultExt extends Vault { 9 | getConfig(key: string): any; 10 | } 11 | -------------------------------------------------------------------------------- /src/obsidian-extensions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Declares properties and methods which are missing from the Obsidian API. 3 | */ 4 | 5 | import { Notice } from 'obsidian'; 6 | 7 | export class NoticeExt extends Notice { 8 | static DISAPPEARING_CLASS = 'mod-disappearing'; 9 | 10 | noticeEl: HTMLElement; 11 | } 12 | -------------------------------------------------------------------------------- /src/original-fs.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'original-fs' { 2 | import * as fs from 'fs'; 3 | export = fs; 4 | } 5 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AbstractTextComponent, 3 | App, 4 | DropdownComponent, 5 | FileSystemAdapter, 6 | PluginSettingTab, 7 | Setting, 8 | } from 'obsidian'; 9 | 10 | import CitationPlugin from './main'; 11 | import { IIndexable, DatabaseType, TEMPLATE_VARIABLES } from './types'; 12 | 13 | const CITATION_DATABASE_FORMAT_LABELS: Record = { 14 | 'csl-json': 'CSL-JSON', 15 | biblatex: 'BibLaTeX', 16 | }; 17 | 18 | export class CitationsPluginSettings { 19 | public citationExportPath: string; 20 | citationExportFormat: DatabaseType = 'csl-json'; 21 | 22 | literatureNoteTitleTemplate = '@{{citekey}}'; 23 | literatureNoteFolder = 'Reading notes'; 24 | literatureNoteContentTemplate: string = 25 | '---\n' + 26 | 'title: {{title}}\n' + 27 | 'authors: {{authorString}}\n' + 28 | 'year: {{year}}\n' + 29 | '---\n\n'; 30 | 31 | markdownCitationTemplate = '[@{{citekey}}]'; 32 | alternativeMarkdownCitationTemplate = '@{{citekey}}'; 33 | } 34 | 35 | export class CitationSettingTab extends PluginSettingTab { 36 | private plugin: CitationPlugin; 37 | 38 | citationPathLoadingEl: HTMLElement; 39 | citationPathErrorEl: HTMLElement; 40 | citationPathSuccessEl: HTMLElement; 41 | 42 | constructor(app: App, plugin: CitationPlugin) { 43 | super(app, plugin); 44 | this.plugin = plugin; 45 | } 46 | 47 | open(): void { 48 | super.open(); 49 | this.checkCitationExportPath( 50 | this.plugin.settings.citationExportPath, 51 | ).then(() => this.showCitationExportPathSuccess()); 52 | } 53 | 54 | addValueChangeCallback( 55 | component: AbstractTextComponent | DropdownComponent, 56 | settingsKey: string, 57 | cb?: (value: string) => void, 58 | ): void { 59 | component.onChange(async (value) => { 60 | (this.plugin.settings as IIndexable)[settingsKey] = value; 61 | this.plugin.saveSettings().then(() => { 62 | if (cb) { 63 | cb(value); 64 | } 65 | }); 66 | }); 67 | } 68 | 69 | buildValueInput( 70 | component: AbstractTextComponent | DropdownComponent, 71 | settingsKey: string, 72 | cb?: (value: string) => void, 73 | ): void { 74 | component.setValue((this.plugin.settings as IIndexable)[settingsKey]); 75 | this.addValueChangeCallback(component, settingsKey, cb); 76 | } 77 | 78 | display(): void { 79 | const { containerEl } = this; 80 | 81 | containerEl.empty(); 82 | containerEl.setAttr('id', 'zoteroSettingTab'); 83 | 84 | containerEl.createEl('h2', { text: 'Citation plugin settings' }); 85 | 86 | new Setting(containerEl) 87 | .setName('Citation database format') 88 | .addDropdown((component) => 89 | this.buildValueInput( 90 | component.addOptions(CITATION_DATABASE_FORMAT_LABELS), 91 | 'citationExportFormat', 92 | (value) => { 93 | this.checkCitationExportPath( 94 | this.plugin.settings.citationExportPath, 95 | ).then((success) => { 96 | if (success) { 97 | this.citationPathSuccessEl.addClass('d-none'); 98 | this.citationPathLoadingEl.removeClass('d-none'); 99 | 100 | this.plugin.loadLibrary().then(() => { 101 | this.citationPathLoadingEl.addClass('d-none'); 102 | this.showCitationExportPathSuccess(); 103 | }); 104 | } 105 | }); 106 | }, 107 | ), 108 | ); 109 | 110 | // NB: we force reload of the library on path change. 111 | new Setting(containerEl) 112 | .setName('Citation database path') 113 | .setDesc( 114 | 'Path to citation library exported by your reference manager. ' + 115 | 'Can be an absolute path or a path relative to the current vault root folder. ' + 116 | 'Citations will be automatically reloaded whenever this file updates.', 117 | ) 118 | .addText((input) => 119 | this.buildValueInput( 120 | input.setPlaceholder('/path/to/export.json'), 121 | 'citationExportPath', 122 | (value) => { 123 | this.checkCitationExportPath(value).then( 124 | (success) => 125 | success && 126 | this.plugin 127 | .loadLibrary() 128 | .then(() => this.showCitationExportPathSuccess()), 129 | ); 130 | }, 131 | ), 132 | ); 133 | 134 | this.citationPathLoadingEl = containerEl.createEl('p', { 135 | cls: 'zoteroSettingCitationPathLoading d-none', 136 | text: 'Loading citation database...', 137 | }); 138 | this.citationPathErrorEl = containerEl.createEl('p', { 139 | cls: 'zoteroSettingCitationPathError d-none', 140 | text: 141 | 'The citation export file cannot be found. Please check the path above.', 142 | }); 143 | this.citationPathSuccessEl = containerEl.createEl('p', { 144 | cls: 'zoteroSettingCitationPathSuccess d-none', 145 | text: 'Loaded library with {{n}} references.', 146 | }); 147 | 148 | new Setting(containerEl) 149 | .setName('Literature note folder') 150 | .addText((input) => this.buildValueInput(input, 'literatureNoteFolder')) 151 | .setDesc( 152 | 'Save literature note files in this folder within your vault. If empty, notes will be stored in the root directory of the vault.', 153 | ); 154 | 155 | containerEl.createEl('h3', { text: 'Template settings' }); 156 | const templateInstructionsEl = containerEl.createEl('p'); 157 | templateInstructionsEl.append( 158 | createSpan({ 159 | text: 160 | 'The following settings determine how the notes and links created by ' + 161 | 'the plugin will be rendered. You may specify a custom template for ' + 162 | 'each type of content. Templates are interpreted using ', 163 | }), 164 | ); 165 | templateInstructionsEl.append( 166 | createEl('a', { 167 | text: 'Handlebars', 168 | href: 'https://handlebarsjs.com/guide/expressions.html', 169 | }), 170 | ); 171 | templateInstructionsEl.append( 172 | createSpan({ 173 | text: ' syntax. You can make reference to the following variables:', 174 | }), 175 | ); 176 | 177 | const templateVariableUl = containerEl.createEl('ul', { 178 | attr: { id: 'citationTemplateVariables' }, 179 | }); 180 | Object.entries(TEMPLATE_VARIABLES).forEach((variableData) => { 181 | const [key, description] = variableData, 182 | templateVariableItem = templateVariableUl.createEl('li'); 183 | 184 | templateVariableItem.createEl('span', { 185 | cls: 'text-monospace', 186 | text: '{{' + key + '}}', 187 | }); 188 | 189 | templateVariableItem.createEl('span', { 190 | text: description ? ` — ${description}` : '', 191 | }); 192 | }); 193 | 194 | const templateEntryInstructionsEl = containerEl.createEl('p'); 195 | templateEntryInstructionsEl.append( 196 | createSpan({ text: 'Advanced users may also refer to the ' }), 197 | createSpan({ text: '{{entry}}', cls: 'text-monospace' }), 198 | createSpan({ 199 | text: 200 | ' variable, which contains the full object representation of the ' + 201 | 'reference as used internally by the plugin. See the ', 202 | }), 203 | createEl('a', { 204 | text: 'plugin documentation', 205 | href: 'http://www.foldl.me/obsidian-citation-plugin/classes/entry.html', 206 | }), 207 | createSpan({ text: " for information on this object's structure." }), 208 | ); 209 | 210 | containerEl.createEl('h3', { text: 'Literature note templates' }); 211 | 212 | new Setting(containerEl) 213 | .setName('Literature note title template') 214 | .addText((input) => 215 | this.buildValueInput(input, 'literatureNoteTitleTemplate'), 216 | ); 217 | 218 | new Setting(containerEl) 219 | .setName('Literature note content template') 220 | .addTextArea((input) => 221 | this.buildValueInput(input, 'literatureNoteContentTemplate'), 222 | ); 223 | 224 | containerEl.createEl('h3', { text: 'Markdown citation templates' }); 225 | containerEl.createEl('p', { 226 | text: 227 | 'You can insert Pandoc-style Markdown citations rather than literature notes by using the "Insert Markdown citation" command. The below options allow customization of the Markdown citation format.', 228 | }); 229 | 230 | new Setting(containerEl) 231 | .setName('Markdown primary citation template') 232 | .addText((input) => 233 | this.buildValueInput(input, 'markdownCitationTemplate'), 234 | ); 235 | 236 | new Setting(containerEl) 237 | .setName('Markdown secondary citation template') 238 | .addText((input) => 239 | this.buildValueInput(input, 'alternativeMarkdownCitationTemplate'), 240 | ); 241 | } 242 | 243 | /** 244 | * Returns true iff the path exists; displays error as a side-effect 245 | */ 246 | async checkCitationExportPath(filePath: string): Promise { 247 | this.citationPathLoadingEl.addClass('d-none'); 248 | 249 | try { 250 | await FileSystemAdapter.readLocalFile( 251 | this.plugin.resolveLibraryPath(filePath), 252 | ); 253 | this.citationPathErrorEl.addClass('d-none'); 254 | } catch (e) { 255 | this.citationPathSuccessEl.addClass('d-none'); 256 | this.citationPathErrorEl.removeClass('d-none'); 257 | return false; 258 | } 259 | 260 | return true; 261 | } 262 | 263 | showCitationExportPathSuccess(): void { 264 | if (!this.plugin.library) return; 265 | 266 | this.citationPathSuccessEl.setText( 267 | `Loaded library with ${this.plugin.library.size} references.`, 268 | ); 269 | this.citationPathSuccessEl.removeClass('d-none'); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import * as BibTeXParser from '@retorquere/bibtex-parser'; 2 | import { Entry as EntryDataBibLaTeX } from '@retorquere/bibtex-parser'; 3 | // Also make EntryDataBibLaTeX available to other modules 4 | export { Entry as EntryDataBibLaTeX } from '@retorquere/bibtex-parser'; 5 | 6 | // Trick: allow string indexing onto object properties 7 | export interface IIndexable { 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | [key: string]: any; 10 | } 11 | 12 | const databaseTypes = ['csl-json', 'biblatex'] as const; 13 | export type DatabaseType = typeof databaseTypes[number]; 14 | 15 | export const TEMPLATE_VARIABLES = { 16 | citekey: 'Unique citekey', 17 | abstract: '', 18 | authorString: 'Comma-separated list of author names', 19 | containerTitle: 20 | 'Title of the container holding the reference (e.g. book title for a book chapter, or the journal title for a journal article)', 21 | DOI: '', 22 | eprint: '', 23 | eprinttype: '', 24 | eventPlace: 'Location of event', 25 | note: '', 26 | page: 'Page or page range', 27 | publisher: '', 28 | publisherPlace: 'Location of publisher', 29 | title: '', 30 | titleShort: '', 31 | URL: '', 32 | year: 'Publication year', 33 | zoteroSelectURI: 'URI to open the reference in Zotero', 34 | }; 35 | 36 | export class Library { 37 | constructor(public entries: { [citekey: string]: Entry }) {} 38 | 39 | get size(): number { 40 | return Object.keys(this.entries).length; 41 | } 42 | 43 | /** 44 | * For the given citekey, find the corresponding `Entry` and return a 45 | * collection of template variable assignments. 46 | */ 47 | getTemplateVariablesForCitekey(citekey: string): Record { 48 | const entry: Entry = this.entries[citekey]; 49 | const shortcuts = { 50 | citekey: citekey, 51 | 52 | abstract: entry.abstract, 53 | authorString: entry.authorString, 54 | containerTitle: entry.containerTitle, 55 | DOI: entry.DOI, 56 | eprint: entry.eprint, 57 | eprinttype: entry.eprinttype, 58 | eventPlace: entry.eventPlace, 59 | note: entry.note, 60 | page: entry.page, 61 | publisher: entry.publisher, 62 | publisherPlace: entry.publisherPlace, 63 | title: entry.title, 64 | titleShort: entry.titleShort, 65 | URL: entry.URL, 66 | year: entry.year?.toString(), 67 | zoteroSelectURI: entry.zoteroSelectURI, 68 | }; 69 | 70 | return { entry: entry.toJSON(), ...shortcuts }; 71 | } 72 | } 73 | 74 | /** 75 | * Load reference entries from the given raw database data. 76 | * 77 | * Returns a list of `EntryData`, which should be wrapped with the relevant 78 | * adapter and used to instantiate a `Library`. 79 | */ 80 | export function loadEntries( 81 | databaseRaw: string, 82 | databaseType: DatabaseType, 83 | ): EntryData[] { 84 | let libraryArray: EntryData[]; 85 | 86 | if (databaseType == 'csl-json') { 87 | libraryArray = JSON.parse(databaseRaw); 88 | } else if (databaseType == 'biblatex') { 89 | const options: BibTeXParser.ParserOptions = { 90 | errorHandler: (err) => { 91 | console.warn( 92 | 'Citation plugin: non-fatal error loading BibLaTeX entry:', 93 | err, 94 | ); 95 | }, 96 | }; 97 | 98 | const parsed = BibTeXParser.parse( 99 | databaseRaw, 100 | options, 101 | ) as BibTeXParser.Bibliography; 102 | 103 | parsed.errors.forEach((error) => { 104 | console.error( 105 | `Citation plugin: fatal error loading BibLaTeX entry` + 106 | ` (line ${error.line}, column ${error.column}):`, 107 | error.message, 108 | ); 109 | }); 110 | 111 | libraryArray = parsed.entries; 112 | } 113 | 114 | return libraryArray; 115 | } 116 | 117 | export interface Author { 118 | given?: string; 119 | family?: string; 120 | } 121 | 122 | /** 123 | * An `Entry` represents a single reference in a reference database. 124 | * Each entry has a unique identifier, known in most reference managers as its 125 | * "citekey." 126 | */ 127 | export abstract class Entry { 128 | /** 129 | * Unique identifier for the entry (also the citekey). 130 | */ 131 | public abstract id: string; 132 | 133 | public abstract type: string; 134 | 135 | public abstract abstract?: string; 136 | public abstract author?: Author[]; 137 | 138 | /** 139 | * A comma-separated list of authors, each of the format ` `. 140 | */ 141 | public abstract authorString?: string; 142 | 143 | /** 144 | * The name of the container for this reference -- in the case of a book 145 | * chapter reference, the name of the book; in the case of a journal article, 146 | * the name of the journal. 147 | */ 148 | public abstract containerTitle?: string; 149 | 150 | public abstract DOI?: string; 151 | public abstract files?: string[]; 152 | 153 | /** 154 | * The date of issue. Many references do not contain information about month 155 | * and day of issue; in this case, the `issuedDate` will contain dummy minimum 156 | * values for those elements. (A reference which is only encoded as being 157 | * issued in 2001 is represented here with a date 2001-01-01 00:00:00 UTC.) 158 | */ 159 | public abstract issuedDate?: Date; 160 | 161 | /** 162 | * Page or page range of the reference. 163 | */ 164 | public abstract page?: string; 165 | public abstract title?: string; 166 | public abstract titleShort?: string; 167 | public abstract URL?: string; 168 | 169 | public abstract eventPlace?: string; 170 | 171 | public abstract publisher?: string; 172 | public abstract publisherPlace?: string; 173 | 174 | /** 175 | * BibLaTeX-specific properties 176 | */ 177 | public abstract eprint?: string; 178 | public abstract eprinttype?: string; 179 | 180 | protected _year?: string; 181 | public get year(): number { 182 | return this._year 183 | ? parseInt(this._year) 184 | : this.issuedDate?.getUTCFullYear(); 185 | } 186 | 187 | protected _note?: string[]; 188 | 189 | public get note(): string { 190 | return this._note 191 | ?.map((el) => el.replace(/(zotero:\/\/.+)/g, '[Link]($1)')) 192 | .join('\n\n'); 193 | } 194 | 195 | /** 196 | * A URI which will open the relevant entry in the Zotero client. 197 | */ 198 | public get zoteroSelectURI(): string { 199 | return `zotero://select/items/@${this.id}`; 200 | } 201 | 202 | toJSON(): Record { 203 | const jsonObj: Record = Object.assign({}, this); 204 | 205 | // add getter values 206 | const proto = Object.getPrototypeOf(this); 207 | Object.entries(Object.getOwnPropertyDescriptors(proto)) 208 | .filter(([, descriptor]) => typeof descriptor.get == 'function') 209 | .forEach(([key, descriptor]) => { 210 | if (descriptor && key[0] !== '_') { 211 | try { 212 | const val = (this as IIndexable)[key]; 213 | jsonObj[key] = val; 214 | } catch (error) { 215 | return; 216 | } 217 | } 218 | }); 219 | 220 | return jsonObj; 221 | } 222 | } 223 | 224 | export type EntryData = EntryDataCSL | EntryDataBibLaTeX; 225 | 226 | export interface EntryDataCSL { 227 | id: string; 228 | type: string; 229 | 230 | abstract?: string; 231 | author?: Author[]; 232 | 'container-title'?: string; 233 | DOI?: string; 234 | 'event-place'?: string; 235 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 236 | issued?: { 'date-parts': [any[]] }; 237 | page?: string; 238 | publisher?: string; 239 | 'publisher-place'?: string; 240 | title?: string; 241 | 'title-short'?: string; 242 | URL?: string; 243 | } 244 | 245 | export class EntryCSLAdapter extends Entry { 246 | constructor(private data: EntryDataCSL) { 247 | super(); 248 | } 249 | 250 | eprint: string = null; 251 | eprinttype: string = null; 252 | files: string[] = null; 253 | 254 | get id() { 255 | return this.data.id; 256 | } 257 | get type() { 258 | return this.data.type; 259 | } 260 | 261 | get abstract() { 262 | return this.data.abstract; 263 | } 264 | get author() { 265 | return this.data.author; 266 | } 267 | 268 | get authorString(): string | null { 269 | return this.data.author 270 | ? this.data.author.map((a) => `${a.given} ${a.family}`).join(', ') 271 | : null; 272 | } 273 | 274 | get containerTitle() { 275 | return this.data['container-title']; 276 | } 277 | 278 | get DOI() { 279 | return this.data.DOI; 280 | } 281 | 282 | get eventPlace() { 283 | return this.data['event-place']; 284 | } 285 | 286 | get issuedDate() { 287 | if ( 288 | !( 289 | this.data.issued && 290 | this.data.issued['date-parts'] && 291 | this.data.issued['date-parts'][0].length > 0 292 | ) 293 | ) 294 | return null; 295 | 296 | const [year, month, day] = this.data.issued['date-parts'][0]; 297 | return new Date(Date.UTC(year, (month || 1) - 1, day || 1)); 298 | } 299 | 300 | get page() { 301 | return this.data.page; 302 | } 303 | 304 | get publisher() { 305 | return this.data.publisher; 306 | } 307 | 308 | get publisherPlace() { 309 | return this.data['publisher-place']; 310 | } 311 | 312 | get title() { 313 | return this.data.title; 314 | } 315 | 316 | get titleShort() { 317 | return this.data['title-short']; 318 | } 319 | 320 | get URL() { 321 | return this.data.URL; 322 | } 323 | } 324 | 325 | const BIBLATEX_PROPERTY_MAPPING: Record = { 326 | abstract: 'abstract', 327 | booktitle: '_containerTitle', 328 | date: 'issued', 329 | doi: 'DOI', 330 | eprint: 'eprint', 331 | eprinttype: 'eprinttype', 332 | eventtitle: 'event', 333 | journal: '_containerTitle', 334 | journaltitle: '_containerTitle', 335 | location: 'publisherPlace', 336 | pages: 'page', 337 | shortjournal: 'containerTitleShort', 338 | title: 'title', 339 | shorttitle: 'titleShort', 340 | url: 'URL', 341 | venue: 'eventPlace', 342 | year: '_year', 343 | publisher: 'publisher', 344 | note: '_note', 345 | }; 346 | 347 | // BibLaTeX parser returns arrays of property values (allowing for repeated 348 | // property entries). For the following fields, just blindly take the first. 349 | const BIBLATEX_PROPERTY_TAKE_FIRST: string[] = [ 350 | 'abstract', 351 | 'booktitle', 352 | '_containerTitle', 353 | 'date', 354 | 'doi', 355 | 'eprint', 356 | 'eprinttype', 357 | 'eventtitle', 358 | 'journaltitle', 359 | 'location', 360 | 'pages', 361 | 'shortjournal', 362 | 'title', 363 | 'shorttitle', 364 | 'url', 365 | 'venue', 366 | '_year', 367 | 'publisher', 368 | ]; 369 | 370 | export class EntryBibLaTeXAdapter extends Entry { 371 | abstract?: string; 372 | _containerTitle?: string; 373 | containerTitleShort?: string; 374 | DOI?: string; 375 | eprint?: string; 376 | eprinttype?: string; 377 | event?: string; 378 | eventPlace?: string; 379 | issued?: string; 380 | page?: string; 381 | publisher?: string; 382 | publisherPlace?: string; 383 | title?: string; 384 | titleShort?: string; 385 | URL?: string; 386 | _year?: string; 387 | _note?: string[]; 388 | 389 | constructor(private data: EntryDataBibLaTeX) { 390 | super(); 391 | 392 | Object.entries(BIBLATEX_PROPERTY_MAPPING).forEach( 393 | (map: [string, string]) => { 394 | const [src, tgt] = map; 395 | if (src in this.data.fields) { 396 | let val = this.data.fields[src]; 397 | if (BIBLATEX_PROPERTY_TAKE_FIRST.includes(src)) { 398 | val = (val as any[])[0]; 399 | } 400 | 401 | (this as IIndexable)[tgt] = val; 402 | } 403 | }, 404 | ); 405 | } 406 | 407 | get id() { 408 | return this.data.key; 409 | } 410 | get type() { 411 | return this.data.type; 412 | } 413 | 414 | get files(): string[] { 415 | // For some reason the bibtex parser doesn't reliably parse file list to 416 | // array ; so we'll do it manually / redundantly 417 | let ret: string[] = []; 418 | if (this.data.fields.file) { 419 | ret = ret.concat(this.data.fields.file.flatMap((x) => x.split(';'))); 420 | } 421 | if (this.data.fields.files) { 422 | ret = ret.concat(this.data.fields.files.flatMap((x) => x.split(';'))); 423 | } 424 | 425 | return ret; 426 | } 427 | 428 | get authorString() { 429 | if (this.data.creators.author) { 430 | const names = this.data.creators.author.map((name) => { 431 | if (name.literal) return name.literal; 432 | const parts = [name.firstName, name.prefix, name.lastName, name.suffix]; 433 | // Drop any null parts and join 434 | return parts.filter((x) => x).join(' '); 435 | }); 436 | return names.join(', '); 437 | } else { 438 | return this.data.fields.author?.join(', '); 439 | } 440 | } 441 | 442 | get containerTitle() { 443 | if (this._containerTitle) { 444 | return this._containerTitle; 445 | } else if (this.data.fields.eprint) { 446 | const prefix = this.data.fields.eprinttype 447 | ? `${this.data.fields.eprinttype}:` 448 | : ''; 449 | const suffix = this.data.fields.primaryclass 450 | ? ` [${this.data.fields.primaryclass}]` 451 | : ''; 452 | return `${prefix}${this.data.fields.eprint}${suffix}`; 453 | } 454 | } 455 | 456 | get issuedDate() { 457 | return this.issued ? new Date(this.issued) : null; 458 | } 459 | 460 | get author(): Author[] { 461 | return this.data.creators.author?.map((a) => ({ 462 | given: a.firstName, 463 | family: a.lastName, 464 | })); 465 | } 466 | } 467 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import { Notice } from 'obsidian'; 2 | 3 | import PromiseWorker from 'promise-worker'; 4 | 5 | import { NoticeExt } from './obsidian-extensions'; 6 | 7 | export const DISALLOWED_FILENAME_CHARACTERS_RE = /[*"\\/<>:|?]/g; 8 | 9 | /** 10 | * Manages a category of notices to be displayed in the UI. Prevents multiple 11 | * notices being shown at the same time. 12 | */ 13 | export class Notifier { 14 | static DISAPPEARING_CLASS = 'mod-disappearing'; 15 | currentNotice?: NoticeExt; 16 | mutationObserver?: MutationObserver; 17 | 18 | constructor(public defaultMessage: string) {} 19 | 20 | unload(): void { 21 | this.hide(); 22 | } 23 | 24 | /** 25 | * @returns true if the notice was shown, and false otherwise 26 | */ 27 | show(message?: string): boolean { 28 | message = message || this.defaultMessage; 29 | if (this.currentNotice) return false; 30 | 31 | this.currentNotice = new Notice(message) as NoticeExt; 32 | 33 | // Set up mutation observer to watch for when the notice disappears. 34 | this.mutationObserver?.disconnect(); 35 | this.mutationObserver = new MutationObserver((changes, observer) => { 36 | const isDisappearing = changes.some((change) => { 37 | const el = change.target as HTMLElement; 38 | return ( 39 | change.type == 'attributes' && 40 | el.hasClass(NoticeExt.DISAPPEARING_CLASS) 41 | ); 42 | }); 43 | if (isDisappearing) { 44 | this.currentNotice = null; 45 | observer.disconnect(); 46 | this.mutationObserver = null; 47 | } 48 | }); 49 | this.mutationObserver.observe(this.currentNotice.noticeEl, { 50 | attributeFilter: ['class'], 51 | }); 52 | } 53 | 54 | hide(): void { 55 | this.currentNotice?.hide(); 56 | this.mutationObserver?.disconnect(); 57 | 58 | this.currentNotice = null; 59 | this.mutationObserver = null; 60 | } 61 | } 62 | 63 | /** 64 | * Manages a Worker, recording its state and optionally preventing 65 | * message postings before responses to prior messages have been received. 66 | */ 67 | export class WorkerManager { 68 | private worker = new PromiseWorker(this._worker); 69 | options: WorkerManagerOptions; 70 | 71 | /** 72 | * Only relevant when `blockingChannel` option is true. 73 | * Then this property is true iff the worker is currently processing a 74 | * received message, and has not yet posted a response. 75 | */ 76 | blocked = false; 77 | 78 | constructor(private _worker: Worker, options: WorkerManagerOptions) { 79 | this.options = { ...workerManagerDefaultOptions, ...options }; 80 | } 81 | 82 | /** 83 | * Attempt to post a message to the worker and return a promise response. 84 | * 85 | * If `blockingChannel` option is true and the channel is currently blocked, 86 | * the message will be discarded and an error will be thrown. 87 | */ 88 | async post(msg: TInput): Promise { 89 | if (this.options.blockingChannel && this.blocked) { 90 | throw new WorkerManagerBlocked(); 91 | } 92 | 93 | this.blocked = true; 94 | return this.worker.postMessage(msg).then( 95 | (result) => { 96 | this.blocked = false; 97 | return result; 98 | }, 99 | (error) => { 100 | this.blocked = false; 101 | throw error; 102 | }, 103 | ); 104 | } 105 | } 106 | 107 | export class WorkerManagerBlocked extends Error { 108 | constructor() { 109 | super('WorkerManager: discarded message because channel is blocked'); 110 | Object.setPrototypeOf(this, WorkerManagerBlocked.prototype); 111 | } 112 | } 113 | 114 | export interface WorkerManagerOptions { 115 | /** 116 | * If true, treat the worker channel as blocking -- when the worker receives 117 | * a message, no other messages can be sent until the worker sends a message. 118 | * Messages which are sent during the blocking period will be discarded. 119 | */ 120 | blockingChannel: boolean; 121 | } 122 | 123 | const workerManagerDefaultOptions: WorkerManagerOptions = { 124 | blockingChannel: false, 125 | }; 126 | -------------------------------------------------------------------------------- /src/worker.ts: -------------------------------------------------------------------------------- 1 | import registerPromiseWorker from 'promise-worker/register'; 2 | 3 | import { DatabaseType, EntryData, loadEntries } from './types'; 4 | 5 | registerPromiseWorker( 6 | (msg: { databaseRaw: string; databaseType: DatabaseType }): EntryData[] => { 7 | return loadEntries(msg.databaseRaw, msg.databaseType); 8 | }, 9 | ); 10 | -------------------------------------------------------------------------------- /src/workers.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'web-worker:*' { 2 | const WorkerFactory: new () => Worker; 3 | export default WorkerFactory; 4 | } 5 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | /** Citations modal **/ 2 | 3 | /* 4 | * Loading animation from 5 | * https://loading.io/css/ 6 | */ 7 | .zoteroModalLoading { 8 | color: var(--text-muted); 9 | text-align: center; 10 | } 11 | .zoteroModalLoadingAnimation { 12 | display: inline-block; 13 | width: 80px; 14 | height: 80px; 15 | } 16 | .zoteroModalLoadingAnimation { 17 | content: " "; 18 | display: block; 19 | width: 32px; 20 | height: 32px; 21 | margin: 10px auto; 22 | border-radius: 50%; 23 | border: 3px solid #eee; 24 | border-color: #eee transparent #eee transparent; 25 | animation: lds-dual-ring 1.2s linear infinite; 26 | } 27 | @keyframes lds-dual-ring { 28 | 0% { 29 | transform: rotate(0deg); 30 | } 31 | 100% { 32 | transform: rotate(360deg); 33 | } 34 | } 35 | 36 | #zoteroSettingTab .text-monospace { 37 | font-family: monospace; 38 | } 39 | 40 | .zoteroModalResults .suggestion-item { 41 | height: fit-content; 42 | line-height: 1.5rem; 43 | } 44 | 45 | .zoteroTitle { 46 | font-size: 14px; 47 | display: block; 48 | } 49 | .zoteroAuthors { 50 | color: #555; 51 | font-size: 13px; 52 | } 53 | .zoteroAuthorsEmpty::after { 54 | font-style: italic; 55 | content: 'Unknown authors'; 56 | } 57 | .zoteroCitekey { 58 | color: #555; 59 | font-size: 13px; 60 | font-family: monospace; 61 | display: inline-block; 62 | margin-right: 5px; 63 | padding-right: 5px; 64 | border-right: 1px solid #ccc; 65 | } 66 | 67 | .theme-dark .zoteroTitle { 68 | font-size: 14px; 69 | display: block; 70 | } 71 | .theme-dark .zoteroAuthors { 72 | color: #aaa; 73 | font-size: 13px; 74 | } 75 | .theme-dark .zoteroCitekey { 76 | color: #aaa; 77 | font-size: 13px; 78 | font-family: monospace; 79 | display: inline-block; 80 | margin-right: 5px; 81 | padding-right: 5px; 82 | border-right: 1px solid #aaa; 83 | } 84 | 85 | /** Settings dialog **/ 86 | .d-none { 87 | display: none; 88 | } 89 | .zoteroSettingCitationPathLoading, 90 | .zoteroSettingCitationPathError, 91 | .zoteroSettingCitationPathSuccess { 92 | font-size: 14px; 93 | } 94 | .zoteroSettingCitationPathLoading { 95 | color: var(--text-muted); 96 | } 97 | .zoteroSettingCitationPathError { 98 | color: var(--text-error); 99 | } 100 | .zoteroSettingCitationPathError:hover { 101 | color: var(--text-error-hover); 102 | } 103 | .zoteroSettingCitationPathSuccess { 104 | color: var(--text-accent); 105 | } 106 | .zoteroSettingCitationPathSuccess:hover { 107 | color: var(--text-accent-hover); 108 | } 109 | 110 | #zoteroSettingTab textarea { 111 | resize: vertical; 112 | width: 100%; 113 | min-height: 10em; 114 | } 115 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "es5", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "lib": [ 13 | "dom", 14 | "es5", 15 | "scripthost", 16 | "es2020" 17 | ] 18 | }, 19 | "include": [ 20 | "**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.1.2": "0.9.20", 3 | "0.1.3": "0.9.20", 4 | "0.2.0": "0.9.20", 5 | "0.2.1": "0.9.20", 6 | "0.3.0": "0.9.20", 7 | "0.3.1": "0.9.20", 8 | "0.3.2": "0.9.20", 9 | "0.3.3": "0.9.20", 10 | "0.3.4": "0.9.20", 11 | "0.4.0": "0.9.20", 12 | "0.4.1": "0.9.20", 13 | "0.4.2": "0.9.20", 14 | "0.4.3": "0.9.20", 15 | "0.4.4": "0.9.20", 16 | "0.4.5": "0.15.9" 17 | } 18 | --------------------------------------------------------------------------------