├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── Archived │ ├── Edge styling.md │ ├── Installation of Neo4j Graph View Plugin.md │ ├── Neo4j Graph View Plugin.md │ ├── Neo4j Stream │ │ ├── Installing the Neo4j Stream Plugin.md │ │ ├── Neo4j Stream Plugin.md │ │ └── Neo4j graph visualizations.md │ ├── Neo4j graph visualizations.md │ ├── Node styling.md │ ├── Semantic Markdown Converter Semantics.md │ ├── Semantic Markdown Converter.md │ ├── Semantic Obsidian edge styling.md │ ├── Semantic Obsidian node styling.md │ └── Using the Neo4j Graph View.md ├── Contributing.md ├── Discord.md ├── Emile van Krieken.md ├── Features │ ├── Breadcrumbs code blocks.md │ ├── Breadcrumbs integration.md │ ├── Export to other programs.md │ ├── Filtering.md │ ├── Global Graph mode.md │ ├── Juggl code block.md │ ├── Layouts.md │ ├── Local Graph mode.md │ ├── Nodes Pane.md │ ├── Styling │ │ ├── CSS Styling.md │ │ ├── Images.md │ │ ├── Style Pane.md │ │ ├── Styling.md │ │ └── YAML Styling.md │ └── Workspace mode │ │ ├── Workspace graph.md │ │ └── Workspace mode.md ├── Installing Juggl.md ├── Juggl API.md ├── Juggl.md ├── Link Types.md ├── Recommend plugins.md ├── Roadmap.md ├── Screen Recording 2021-04-14 at 17.29.09.mov ├── Search on Internet Plugin.md ├── Semantic Obsidian.md ├── files │ ├── CypherQuerying.png │ ├── Pasted image 20201231174344.png │ ├── Pasted image 20201231174425.png │ ├── Pasted image 20201231175530.png │ ├── Pasted image 20201231180930.png │ ├── Pasted image 20201231181003.png │ ├── Pasted image 20201231181103.png │ ├── Pasted image 20201231181917.png │ ├── Pasted image 20210101144050.png │ ├── Pasted image 20210101144311.png │ ├── Pasted image 20210102141444.png │ ├── Pasted image 20210102141514.png │ ├── Pasted image 20210102141546.png │ ├── Pasted image 20210112215108.png │ ├── Pasted image 20210315210604.png │ ├── Pasted image 20210320161536.png │ ├── Pasted image 20210320161754.png │ ├── Pasted image 20210329191901.png │ ├── Pasted image 20210330193014.png │ ├── Pasted image 20210402152820.png │ ├── Pasted image 20210402154645.png │ ├── Pasted image 20210402155246.png │ ├── Pasted image 20210402162157.png │ ├── Pasted image 20210403140754.png │ ├── Pasted image 20210403141344.png │ ├── Pasted image 20210403142046.png │ ├── Pasted image 20210403143207.png │ ├── Pasted image 20210403144754.png │ ├── Pasted image 20210403150555.png │ ├── Pasted image 20210403150726.png │ ├── Pasted image 20210403151146.png │ ├── Pasted image 20210403151424.png │ ├── Pasted image 20210408165620.png │ ├── Pasted image 20210408165722.png │ ├── Pasted image 20210408165744.png │ ├── Pasted image 20210408165833.png │ ├── Pasted image 20210410161017.png │ ├── Pasted image 20210410161051.png │ ├── Pasted image 20210413145227.png │ ├── Pasted image 20210413145921.png │ ├── Pasted image 20210413150208.png │ ├── Pasted image 20210414175943.png │ ├── Pasted image 20210414182140.png │ ├── Pasted image 20210421133609.png │ ├── Pasted image 20210421133938.png │ ├── Pasted image 20210422092407.png │ ├── Pasted image 20220121103800.png │ ├── Pasted image 20220127142536.png │ ├── Pasted image 20220127142903.png │ ├── Screen Recording 2021-01-02 at 14.19.42.mp4 │ ├── Screen Recording 2021-01-02 at 15.01.02.mp4 │ ├── Screen Recording 2021-01-02 at 15.05.43.mp4 │ ├── Screen Recording 2021-01-02 at 15.09.51.mp4 │ ├── Screen Recording 2021-01-02 at 15.14.06.mp4 │ ├── Screen Recording 2021-01-02 at 15.21.34.mp4 │ ├── Screen Recording 2021-01-02 at 15.24.43.mp4 │ ├── Screen Recording 2021-01-02 at 15.32.58 1.mp4 │ ├── Screen Recording 2021-01-02 at 15.32.58.mp4 │ ├── Screen Recording 2021-01-02 at 15.38.11.mp4 │ ├── Screen Recording 2021-01-02 at 15.43.17.mp4 │ ├── Screen Recording 2021-04-02 at 16.48.29.mov │ ├── Screen Recording 2021-04-03 at 13.58.20.mov │ ├── Screen Recording 2021-04-03 at 13.58.20.mp4 │ ├── Screen Recording 2021-04-03 at 14.59.13.mp4 │ ├── Screen Recording 2021-04-14 at 17.29.09.mp4 │ ├── Screenshot 2021-04-03 at 15.01.44.png │ ├── bloom_screenshot.jpg │ ├── browser_screenshot.png │ ├── code_fence.gif │ ├── context_iframe.gif │ ├── demo.gif │ ├── graphxr.gif │ ├── img.png │ ├── juggl trailer.gif │ ├── juggl_screenshot.png │ ├── layouts.gif │ ├── main.db.json │ ├── obsidian neo4j plugin.gif │ ├── save_workspace.gif │ ├── selectdrag.mp4 │ ├── style_pane.gif │ └── styled_screenshot.png ├── publish.css └── templates │ └── Template.md ├── main.js ├── manifest.json ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── .eslintrc.js ├── .gitignore ├── CHANGELOG.md ├── constants.ts ├── declarations.d.ts ├── events.ts ├── image-server.ts ├── interfaces.ts ├── main.ts ├── obsidian-store.ts ├── pane │ ├── NodesList.svelte │ ├── NodesPane.svelte │ ├── StyleGroups.svelte │ ├── StylePane.svelte │ ├── icon-modal.ts │ └── view.ts ├── resources │ ├── code_fence.gif │ ├── juggl_help.gif │ ├── juggl_screenshot.png │ ├── juggl_trailer.gif │ ├── layouts.gif │ ├── open_juggl.gif │ ├── save_workspace.gif │ ├── style_pane.gif │ └── workspace_mode.gif ├── settings.ts ├── ui │ ├── KoFi.svelte │ ├── SaveWorkspaceItem.svelte │ ├── SaveWorkspaces.svelte │ ├── help-view.ts │ ├── icons.ts │ ├── settings │ │ ├── AppearanceSettings.svelte │ │ ├── GlobalGraphModal.svelte │ │ └── global-graph-modal.ts │ ├── sidebar │ │ └── Sidebar.svelte │ ├── toolbar │ │ ├── HelpButton.svelte │ │ ├── Toolbar.svelte │ │ ├── ToolbarButton.svelte │ │ └── ToolbarLocal.svelte │ └── workspace-modal.ts └── viz │ ├── juggl-view.ts │ ├── layout-settings.ts │ ├── local-mode.ts │ ├── query-builder.ts │ ├── stylesheet.ts │ ├── visualization.ts │ └── workspaces │ ├── workspace-manager.ts │ └── workspace-mode.ts ├── styles.css ├── tsconfig.json └── versions.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [HEmile] 2 | ko_fi: Emile 3 | custom: ["https://paypal.me/EvanKrieken"] 4 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish plugin 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - "*" # Push events to matching any tag format, i.e. 1.0, 20.15.10 8 | 9 | env: 10 | PLUGIN_NAME: ${{ github.event.repository.name }} 11 | RELEASE_VER: ${{ github.ref }} 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Create release and Upload 20 | id: release 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | run: | 24 | TAG_NAME=${RELEASE_VER##*/} 25 | mkdir "${PLUGIN_NAME}" 26 | assets=() 27 | for f in main.js manifest.json styles.css; do 28 | if [[ -f $f ]]; then 29 | cp $f "${PLUGIN_NAME}/" 30 | assets+=(-a "$f") 31 | fi 32 | done 33 | zip -r "$PLUGIN_NAME".zip "$PLUGIN_NAME" 34 | hub release create "${assets[@]}" -a "$PLUGIN_NAME".zip -m "$TAG_NAME" "$TAG_NAME" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # celery beat schedule file 95 | celerybeat-schedule 96 | 97 | # SageMath parsed files 98 | *.sage.py 99 | 100 | # Environments 101 | .env 102 | .venv 103 | env/ 104 | venv/ 105 | ENV/ 106 | env.bak/ 107 | venv.bak/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | .spyproject 112 | 113 | # Rope project settings 114 | .ropeproject 115 | 116 | # mkdocs documentation 117 | /site 118 | 119 | # mypy 120 | .mypy_cache/ 121 | .dmypy.json 122 | dmypy.json 123 | 124 | # Pyre type checker 125 | .pyre/ 126 | 127 | # smd converter 128 | /markdown/ 129 | .idea/ 130 | out.cypher 131 | /docs/.obsidian/ 132 | /docs/Development/ 133 | 134 | # build 135 | *.js.map 136 | node_modules -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [1.4.0](https://github.com/HEmile/juggl/compare/1.3.3...1.4.0) (2022-03-14) 6 | 7 | 8 | ### Features 9 | 10 | * Hover over links with hover editor to open editor in right position ([ee28cfc](https://github.com/HEmile/juggl/commit/ee28cfc8a47efaf7ae5c6f133179fc3761a728e0)) 11 | 12 | ### [1.3.3](https://github.com/HEmile/juggl/compare/1.3.2...1.3.3) (2022-03-12) 13 | 14 | 15 | ### Features 16 | 17 | * Use default hover functionality for nodes ([7a6e135](https://github.com/HEmile/juggl/commit/7a6e135c9927d341addfca669d264e6365a71bfb)) 18 | 19 | ### [1.3.2](https://github.com/HEmile/juggl/compare/1.3.1...1.3.2) (2022-02-15) 20 | 21 | ### [1.3.1](https://github.com/HEmile/juggl/compare/1.3.0...1.3.1) (2022-02-04) 22 | 23 | 24 | ### Bug Fixes 25 | 26 | * Nested tags filtering doesnt work for depth 3+ ([9609d93](https://github.com/HEmile/juggl/commit/9609d93c63bf1a7d9bb66a53932eb51bfbede48a)) 27 | 28 | ## [1.3.0](https://github.com/HEmile/juggl/compare/1.2.9...1.3.0) (2022-01-29) 29 | 30 | 31 | ### Features 32 | 33 | * Now adds has-incoming-type and has-outgoing-type classes ([cac9fdc](https://github.com/HEmile/juggl/commit/cac9fdce9cbb5901b6320e5266a4dc7ed11c9bdd)) 34 | 35 | ### [1.2.9](https://github.com/HEmile/juggl/compare/1.2.8...1.2.9) (2022-01-24) 36 | 37 | ### [1.2.8](https://github.com/HEmile/juggl/compare/1.2.7...1.2.8) (2022-01-24) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * Better error handling ([fccb2ea](https://github.com/HEmile/juggl/commit/fccb2ead8cfac168e5f4b81a22720ef186150e26)) 43 | 44 | ### [1.2.7](https://github.com/HEmile/juggl/compare/1.2.6...1.2.7) (2022-01-23) 45 | 46 | ### [1.2.6](https://github.com/HEmile/juggl/compare/1.2.5...1.2.6) (2022-01-23) 47 | 48 | ### [1.2.5](https://github.com/HEmile/juggl/compare/1.2.4...1.2.5) (2022-01-15) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * Compatibility with Primary ([d3d2528](https://github.com/HEmile/juggl/commit/d3d25283697aed4cb1bad86009796a54512f09f4)) 54 | * Unloading problem if unloaded too quickly ([3f2b1b1](https://github.com/HEmile/juggl/commit/3f2b1b14a9c5028bb7e011b384ca8928e498845e)) 55 | 56 | ### [1.2.4](https://github.com/HEmile/juggl/compare/1.2.3...1.2.4) (2022-01-15) 57 | 58 | ### [1.2.3](https://github.com/HEmile/juggl/compare/1.2.2...1.2.3) (2022-01-15) 59 | 60 | 61 | ### Bug Fixes 62 | 63 | * Juggl does not properly unload when toolbar is not displayed ([19c402f](https://github.com/HEmile/juggl/commit/19c402f4667167d6682f46a48856835e705a75b0)) 64 | 65 | ### [1.2.2](https://github.com/HEmile/juggl/compare/1.2.1...1.2.2) (2022-01-14) 66 | 67 | 68 | ### Bug Fixes 69 | 70 | * Edge arrows missing ([c6cf885](https://github.com/HEmile/juggl/commit/c6cf885ef03858d4a00122a8a4902d44c50c988a)) 71 | 72 | ### [1.2.1](https://github.com/HEmile/juggl/compare/1.2.0...1.2.1) (2022-01-13) 73 | 74 | ## [1.2.0](https://github.com/HEmile/juggl/compare/1.1.5...1.2.0) (2022-01-12) 75 | 76 | 77 | ### Features 78 | 79 | * Added API method for creating JUGGL instances ([5891f1a](https://github.com/HEmile/juggl/commit/5891f1aeaa93bd908d6b9fca30540fc9b3d26f9f)) 80 | 81 | ### [1.1.5](https://github.com/HEmile/juggl/compare/v1.1.4...v1.1.5) (2022-01-11) 82 | 83 | ### [1.1.4](https://github.com/HEmile/juggl/compare/v1.1.3...v1.1.4) (2022-01-11) 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Buy Me A Coffee donate button 4 | 5 | Downloads 7 | 8 | Github latest release 10 | 11 | Documentation 13 | 14 | chat on Discord 16 |

17 | 18 | ## Juggl 19 | Juggl is a completely interactive, stylable and expandable graph view for [Obsidian](https://obsidian.md). It is designed as an advanced 'local' graph view called the 'workspace', where you can juggle all your thoughts with ease: By navigating your vault through a beautiful graph! 20 | 21 | For example, you can select what parts of the graph to expand, to make sure there is never too much information on the screen. You will have complete control over the style of your graph using the powerful [Cytoscape.js library](https://js.cytoscape.org): Juggl has a useful styling pane nodes colors, shapes, sizes, and icons. This helps you get an immediate overview over what the content of each node is. 22 | 23 | ![](https://raw.githubusercontent.com/HEmile/juggl/main/src/resources/juggl_trailer.gif) 24 | 25 | ## Features 26 | Juggl has many features unique to its graph view compared to the Obsidian graph view: 27 | - Complete control over the style of your graph using [CSS](https://juggl.io/features/styling/css-styling.html), [YAML](https://juggl.io/features/styling/yaml-styling.html) and the [Style Pane](https://juggl.io/features/styling/style-pane.html) . 28 | - Include images! 29 | - A [Workspace mode](https://juggl.io/Features/Workspace+mode/Workspace+mode) that lets you build your graph with all nodes that are relevant to your current project 30 | - Selectively browse and hide nodes, and pin their location so you never lose them 31 | - Write new ideas and see your graph evolve 32 | - Save your graph and continue working on it later 33 | - 4 different [layouts](https://juggl.io/features/layouts.html) to get unique insights 34 | - A [code block](https://juggl.io/features/juggl-code-block.html) that displays the graph within Obsidian notes 35 | - Link type support to label edges 36 | - [Tight integration](https://juggl.io/features/breadcrumbs-integration.html) with the very useful [Breadcrumbs plugin](https://github.com/SkepticMystic/breadcrumbs) 37 | - Extendable through other plugins 38 | - Works on mobile! 39 | 40 | ## Getting started 41 | You can open Juggl from the 'more options' menu on files: 42 | ![](https://raw.githubusercontent.com/HEmile/juggl/main/juggl/resources/open_juggl.gif) 43 | 44 | You can interact with the graph with many of the same options as in Obsidian. For further documentation, check out [juggl.io](https://juggl.io/), where you can find information on for example [styling](https://juggl.io/features/styling/styling.html) or the syntax of the [code block](https://juggl.io/features/juggl-code-block.html). 45 | You can also open the help vault with this button in Juggl: 46 | ![](https://raw.githubusercontent.com/HEmile/juggl/main/juggl/resources/juggl_help.gif) 47 | 48 | 49 | ## Extending Juggl 50 | Juggl is completely open source and has an API available for creating Obsidian plugins that extend or use Juggl. See https://github.com/HEmile/juggl-api . You will have complete control over the internals of [Cytoscape.js](https://js.cytoscape.org), which is an extremely powerful graph visualization library! 51 | -------------------------------------------------------------------------------- /docs/Archived/Edge styling.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | image: files/bloom_screenshot.jpg 4 | --- 5 | 6 | An example of node styling for [[Neo4j Graph View Plugin]] is provided at [[Semantic Obsidian edge styling]]. 7 | 8 | Styling of edges is done in .json format. The first key determines what types of links to apply this style to. 9 | For instance, `{"hasTopic":{"color":"yellow"}}` would color all edge with type `hasTopic` yellow. 10 | 11 | ## Special types 12 | - Use `{"defaultStyle": {}}` for the default styling of edges 13 | - Use`{"inline":{} }` for the styling of untyped links 14 | 15 | See [this link](https://visjs.github.io/vis-network/docs/network/edges.html)for all options for styling edges. 16 | 17 | --- 18 | #howto 19 | - hasTopic [[Neo4j Graph View Plugin]] 20 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Archived/Installation of Neo4j Graph View Plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [installation, install] 3 | --- 4 | 5 | **Warning: The [[Neo4j Graph View Plugin]] is deprecated and no longer supported. You can still [manually install it from Github](https://github.com/HEmile/obsidian-neo4j-graph-view/releases/tag/0.2.5) if you need the [[Neo4j]] support.** 6 | 7 | The general steps to install the plugin are as follows: 8 | 1. Make sure you have [[Python]] 3.6+ installed. See [[Installation of Neo4j Graph View Plugin#Install Python]]. 9 | 2. Make sure you have [[Neo4j Desktop]] installed. See [[Installation of Neo4j Graph View Plugin#Install Neo4j Desktop]]. 10 | 3. Create a new database in Neo4j desktop and start it. See [[Installation of Neo4j Graph View Plugin#Creating a Neo4j database]]. 11 | 5. Install the Obsidian plugin from the Third-party plugins list. See [[Installation of Neo4j Graph View Plugin#Installing the Obsidian plugin]] 12 | 6. In the settings of the plugin, enter the password. Then run the restart command. See [[Installation of Neo4j Graph View Plugin#Configuring the Obsidian plugin]]. 13 | 14 | If you are still running into problems after thorougly following these steps, see the [[Installation of Neo4j Graph View Plugin#Troubleshooting]] section. 15 | 16 | The next goal of the plugin (see [[Roadmap]]) is to remove the [[Python]] installation. This should make installation much more accessible. If you are not very technical and the installation of Python is failing, you can wait until this dependency is removed. The goal is to have this done before mid-end January 2021. 17 | 18 | ## 1. Install Python 19 | Make sure you've installed [Python 3.6+](https://www.python.org/downloads/) as your system Python 3. If you think you might already have it, run `python3 -V` in the terminal, which should return the Python version. 20 | 21 | Also make sure to add Python to PATH, especially on [[Windows]]! During the installation, enable the 'Add Python 3.x to PATH' to ensure this happens. 22 | 23 | ![](https://docs.python.org/3/_images/win_installer.png) 24 | 25 | [[Linux]] might already have Python installed by default. If a version lower than 3.6 is installed, update Python. Run `python3 -V` in the terminal to find out what version you have installed. 26 | 27 | ### Possible problems 28 | For plugin versions under 0.2.5, the plugin doesn't work if Python's package manager "pip" is outdated. Please update to plugin version 0.2.5, as it updates pip for you. 29 | 30 | On [[Mac]], some people got `xcrun: error: invalid active developer path... missing xcrun at...`: [Install xcrun)](https://apple.stackexchange.com/questions/254380/why-am-i-getting-an-invalid-active-developer-path-when-attempting-to-use-git-a) in terminal using `xcode-select --install` 31 | 32 | ## 2. Install Neo4j Desktop 33 | Download [Neo4j desktop](https://neo4j.com/download/). Neo4j wants you to fill a form before giving you the link. Save the Neo4j Desktop Activation Key they provide for later. 34 | 35 | ![[Pasted image 20210112215108.png]] 36 | 37 | One user reported the activation key doesn't appear. [According to the Neo4j developers](https://community.neo4j.com/t/installing-and-activation-key/6173), that doesn't actually matter and you can proceed without using a key, curiously. I haven't tried this, though. 38 | 39 | The download can take a while because it's a pretty big file and the servers aren't very quick. 40 | 41 | Installation of Neo4j desktop is pretty straightforward. You have to input the key Neo4j gave you after registration. There's an [installation video](https://www.youtube.com/watch?v=pPhJi9twN9Q&feature=emb_title) if you need more help. 42 | 43 | 44 | ## 3. Creating a Neo4j database 45 | In a Project in Neo4j Desktop, click "+ Add Database" under projects: 46 | ![[Pasted image 20201231174344.png|300]] 47 | 48 | Then select "Create a Local Database". Next follows this screen: 49 | 50 | ![[Pasted image 20201231174425.png|300]] 51 | 52 | You have to set a password here. We're also going to use it in the plugin. Make sure not to choose a sensitive password here! [[Neo4j Graph View Plugin]] doesn't store passwords encrypted. 53 | You can choose any name. For the Neo4j version, I have tested on 4.2.0, but it should not matter much. 54 | 55 | It'll create your database! Then, click the "Start" button. Note that you have to start your database every time whenever your computer reboots: It needs to remain active while you use the plugin. 56 | 57 | ### Possible problems 58 | Every now and then, the Graph database fails to start. 59 | - Sometimes, it just says it 'timed out'. Retrying it a few times may help. 60 | - Port in use: It's unlikely the port is actually in use. Restart Neo4j desktop and try again. It's currently not possible to set a port different than the default port (request on [[Github]] if needed). 61 | - "Could not change the password": This has been reported with a [[Windows]] user. See [this post](https://stackoverflow.com/questions/49342422/neo4j-database-failed-to-create-error-could-not-change-password) for possible guidance. 62 | 63 | ## 4. Installing the Obsidian plugin 64 | Time to install the plugin! Since Neo4j Graph View is an old version of Juggl, you will have to download and install it manually. 65 | 66 | 1. Download the plugin from [this link](https://github.com/HEmile/obsidian-neo4j-graph-view/releases/tag/0.2.5). 67 | 1. Open the Obsidian settings, and go to Third-party plugins. Disable the "Safe mode" toggle, then click "Turn off safe mode" to confirm this. 68 | 2. Now, more options appear. Click on the "open plugin folders" icon 69 | 3. In the resulting folder, create a new directory called `neo4j-graph-view` 70 | 4. Extract the downloaded file into this folder. 71 | 5. Close the current screen. In the Third-party plugins settings, enable Neo4j graph view with the slider. 72 | 6. It should look like this: 73 | 74 | ![[Pasted image 20201231175530.png]] 75 | 76 | 77 | ## 5. Configuring the Obsidian plugin 78 | We need to do one more thing before we can get playing with the plugin: Setting the password. 79 | 1. Go to the [[Neo4j Graph View settings]], which has appeared under Plugin options in the [[Obsidian]] settings. 80 | 2. In the password field, input the password you set during [[Installation of Neo4j Graph View Plugin#Creating a Neo4j database]]. ![[Pasted image 20201231180930.png]] 81 | 3. Close the settings view. 82 | 4. Run the Obsidian command: "Neo4j Graph View: Restart Neo4j stream". You can run a command by using ctrl/cmd + p. ![[Pasted image 20201231181003.png]] 83 | 5. The plugin is succesfully installed if the following notice appears in the top-right corner: ![[Pasted image 20201231181103.png|300]] (note: for some reason, it doesn't always appear even though the server did properly start...) 84 | 85 | If a different notice appears, something went wrong. Let's try to figure out what! 86 | ## Troubleshooting 87 | If a notice appears that doesn't say the Neo4j stream is online, something went wrong. Two simple notices are 88 | - "Please provide a password in the Neo4j Graph View settings": This means your user credentials weren't accepted by the Neo4j database. Check if the password is set correctly during [[Installation of Neo4j Graph View Plugin#Configuring the Obsidian plugin]]. 89 | - "No connection to Neo4j database. Please start Neo4j Database in Neo4j Desktop": This means there's no connection to a Neo4j database on port 7687. Check Neo4j desktop if the database is online. 90 | 91 | The third notice is scariest: "Error during initialization of the Neo4j stream. Check the console for crash report.". Here are some steps to help figure out how to resolve this: 92 | 1. Enable "Debug" mode in the Neo4j Graph View settings ![[Pasted image 20201231181917.png]] 93 | 2. Open the Developer Tools. This option is under the View menu. If the View menu doesn't show, the keyboard shortcut to open it is ctrl+shift+i on windows and option+cmd+i on mac. 94 | 3. Look at the error in the Console. 95 | 96 | If the error is related to `pip3`, or `smdc` not being found, there's likely something wrong with your Python installation. See [[Installation of Neo4j Graph View Plugin#Install Python]] for a bit of guidance. 97 | Otherwise, it's likely that there's some bug in the plugin in that it cannot handle something that's present in your vault. Since this version is not supported by me anymore, you will have to fork the plugin to fix this. There are plans to add Neo4j support to [[Juggl]] sometime through an external plugin. 98 | You can also contact [[Emile van Krieken|me]] on Twitter, Github or [[Discord]] if you need help. 99 | 100 | --- 101 | #howto 102 | - hasTopic [[Neo4j Graph View]] 103 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Archived/Neo4j Graph View Plugin.md: -------------------------------------------------------------------------------- 1 | This plugin is deprecated and replaced by [[Juggl]]. It will be removed from the community plugins store in the near future. 2 | 3 | ![[styled_screenshot.png]] 4 | 5 | 6 | Adds a much more customizable and interactable [[Neo4j Graph View|graph view]] to [[Obsidian]]. It does so by connecting to a [[Neo4j]] database. Features: 7 | - Selectively [[Node styling|style nodes]] and [[Edge styling|edges]] by tags, folders and link types ^87894b 8 | - Selectively expand and hide parts of the grapha 9 | - View images within the graph 10 | - Execute advanced queries using [[Cypher]] 11 | - [[Link Types]] 12 | - Optional [[Hierarchical layout]] 13 | 14 | This vault acts as a demo for the Graph View. You can [download](https://github.com/HEmile/semantic-obsidian) the documentation as an Obsidian vault, and use the Graph View to visualize it. Styling is provided at [[Semantic Obsidian node styling]] and [[Semantic Obsidian edge styling]]. 15 | 16 | ## Install and use 17 | First [[Installation of Neo4j Graph View Plugin|install]] the plugin. Then, read [[Using the Neo4j Graph View|this guide with a lot of videos]] to get started with the graph view. 18 | 19 | To get a feel for how the plugin interprets your data, see [[Semantic Markdown Converter Semantics|the explanation on semantics]]. 20 | 21 | There are more graph visualization options available! See [[Neo4j graph visualizations]] for some options. 22 | 23 | ## Development 24 | Lots of ideas exist to extend this plugin! There is a [[Roadmap]] available. A [[Juggl API]] is also being planned, which allows for easily extending the Graph View. 25 | 26 | Contributions are very much welcomed. The easiest way to get started is to join the [[Discord]] server! See [[Contributing]] for an overview of ways you can help. 27 | 28 | 29 | --- 30 | #plugin 31 | - hasTopic [[Neo4j]] 32 | - hasTopic [[Obsidian]] 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/Archived/Neo4j Stream/Installing the Neo4j Stream Plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [installation, install] 3 | --- 4 | 5 | 6 | 7 | The general steps to install the plugin are as follows: 8 | 1. Make sure you have [[Neo4j Desktop]] installed. See [[Install Neo4j Stream Plugin#Install Neo4j Desktop]]. 9 | 3. Create a new database in Neo4j desktop and start it. See [[Install Neo4j Stream Plugin#Creating a Neo4j database]]. 10 | 5. Install the Obsidian plugin from the Third-party plugins list. See [[Install Neo4j Stream Plugin#Installing the Obsidian plugin]] 11 | 6. In the settings of the plugin, enter the password. Then run the restart command. See [[Install Neo4j Stream Plugin#Configuring the Obsidian plugin]]. 12 | 13 | If you are still running into problems after thorougly following these steps, see the [[Install Neo4j Stream Plugin#Troubleshooting]] section. 14 | 15 | ## 1. Install Neo4j Desktop 16 | Download [Neo4j desktop](https://neo4j.com/download/). Neo4j wants you to fill a form before giving you the link. Save the Neo4j Desktop Activation Key they provide for later. 17 | 18 | ![[Pasted image 20210112215108.png]] 19 | 20 | One user reported the activation key doesn't appear. [According to the Neo4j developers](https://community.neo4j.com/t/installing-and-activation-key/6173), that doesn't actually matter and you can proceed without using a key, curiously. I haven't tried this, though. 21 | 22 | The download can take a while because it's a pretty big file and the servers aren't very quick. 23 | 24 | Installation of Neo4j desktop is pretty straightforward. You have to input the key Neo4j gave you after registration. There's an [installation video](https://www.youtube.com/watch?v=pPhJi9twN9Q&feature=emb_title) if you need more help. 25 | 26 | 27 | ## 2. Creating a Neo4j database 28 | In a Project in Neo4j Desktop, click "+ Add Database" under projects: 29 | ![[Pasted image 20201231174344.png|300]] 30 | 31 | Then select "Create a Local Database". Next follows this screen: 32 | 33 | ![[Pasted image 20201231174425.png|300]] 34 | 35 | You have to set a password here. We're also going to use it in the plugin. Make sure not to choose a sensitive password here! [[Neo4j Stream Plugin]] doesn't store passwords encrypted. You can choose any name. For the Neo4j version, I have tested on 4.2.0, but it should not matter much. 36 | 37 | It'll create your database! Then, click the "Start" button. Note that you have to start your database every time whenever your computer reboots: It needs to remain active while you use the plugin. 38 | 39 | ### Possible problems 40 | Every now and then, the Graph database fails to start. 41 | - Sometimes, it just says it 'timed out'. Retrying it a few times may help. 42 | - Port in use: It's unlikely the port is actually in use. Restart Neo4j desktop and try again. It's currently not possible to set a port different than the default port (request on [[Github]] if needed). 43 | - "Could not change the password": This has been reported with a [[Windows]] user. See [this post](https://stackoverflow.com/questions/49342422/neo4j-database-failed-to-create-error-could-not-change-password) for possible guidance. 44 | 45 | ## 3. Installing the Obsidian plugin 46 | Time to install the plugin! Since Neo4j Stream is unfinished, you will have to download and install it manually. 47 | 48 | 1. Download the plugin from [this link](https://github.com/HEmile/obsidian-neo4j-stream/releases). 49 | 1. Open the Obsidian settings, and go to Third-party plugins. Disable the "Safe mode" toggle, then click "Turn off safe mode" to confirm this. 50 | 2. Now, more options appear. Click on the "open plugin folders" icon 51 | 3. In the resulting folder, create a new directory called `neo4j-stream` 52 | 4. Extract the downloaded file into this folder. 53 | 5. Close the current screen. In the Third-party plugins settings, enable Neo4j Stream with the slider. 54 | 55 | ## 4. Configuring Neo4j Stream 56 | We need to do one more thing before we can get playing with the plugin: Setting the password. 57 | 1. Go to the Neo4j Stream settings, which has appeared under Plugin options in the Obsidian settings. 58 | 2. In the password field, input the password you set during [[Install Neo4j Stream Plugin#Creating a Neo4j database]]. ![[Pasted image 20201231180930.png]] 59 | 3. Close the settings view. 60 | 4. Run the Obsidian command: "Neo4j Graph View: Restart Neo4j stream". You can run a command by using ctrl/cmd + p. ![[Pasted image 20201231181003.png]] 61 | 5. The plugin is succesfully installed if the following notice appears in the top-right corner: ![[Pasted image 20201231181103.png|300]] (note: for some reason, it doesn't always appear even though the server did properly start...) 62 | 63 | If a different notice appears, something went wrong. Let's try to figure out what! 64 | 65 | ## Troubleshooting 66 | If a notice appears that doesn't say the Neo4j stream is online, something went wrong. Two simple notices are 67 | - "Please provide a password in the Neo4j Stream settings": This means your user credentials weren't accepted by the Neo4j database. Check if the password is set correctly during [[Install Neo4j Stream Plugin#Configuring the Obsidian plugin]]. 68 | - "No connection to Neo4j database. Please start Neo4j Database in Neo4j Desktop": This means there's no connection to a Neo4j database on port 7687. Check Neo4j desktop if the database is online. 69 | 70 | The third notice is scariest: "Error during initialization of the Neo4j stream. Check the console for crash report.". Here are some steps to help figure out how to resolve this: 71 | 1. Enable "Debug" mode in the Neo4j Stream settings ![[Pasted image 20201231181917.png]] 72 | 2. Open the Developer Tools. This option is under the View menu. If the View menu doesn't show, the keyboard shortcut to open it is Ctrl+Shift+I on windows and Option+Cmd+I on mac. 73 | 3. Look at the error in the Console. 74 | 75 | Otherwise, it's likely that there's some bug in the plugin in that it cannot handle something that's present in your vault. Since this version is unfinished, you will have to fork the plugin to fix this. 76 | 77 | You can also contact [[Emile van Krieken|me]] on Twitter, Github or [[Discord]] if you need help. 78 | 79 | --- 80 | #howto 81 | - hasTopic [[Neo4j Graph View]] 82 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Archived/Neo4j Stream/Neo4j Stream Plugin.md: -------------------------------------------------------------------------------- 1 | This plugin helps by streaming your vault to a [Neo4j](https://neo4j.com/) database. This is a separate plugin to ensure non-advanced users of [[Juggl]] will not have to deal with installing Neo4j, and is completely optional for advanced users. 2 | 3 | ⚠ Note: This is an unfinshed pluggin. Absolutely no guarantees are given that this works. 4 | ⚠ Note: Development on this project has stopped out of lack of personal interest for the use case. Anyone interested in further developing this can contact me. 5 | 6 | Please refer to [[Install Neo4j Stream Plugin]] and [[Neo4j graph visualizations]] for more information 7 | 8 | ## Development 9 | [[Roadmap]]: [[Roadmap#^2d1fd7]] 10 | 11 | Contributions are very much welcomed. The easiest way to get started is to join the [[Discord]] server! See [[Contributing]] for an overview of ways you can help. 12 | 13 | [HEmile/obsidian-neo4j-stream](https://github.com/HEmile/obsidian-neo4j-stream "HEmile/obsidian-neo4j-stream") 14 | 15 | --- 16 | #plugin 17 | - hasTopic [[Neo4j]] 18 | - hasTopic [[Obsidian]] 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/Archived/Neo4j Stream/Neo4j graph visualizations.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | The main use case for the [[Neo4j Stream Plugin]] is to use your Obsidian vault in one of the many apps in the [[Neo4j Desktop]] 6 | Graph Apps Store. Using with this plugin active will automatically connect it to your vault. Here are some suggestions: 7 | 8 | ### Neo4j Bloom 9 | [Neo4j Bloom](https://neo4j.com/product/bloom/) is very powerful graph visualization software. Compared to the embedded 10 | graph view in Obsidian, it offers much more freedom in customization. 11 | 12 | ![[bloom_screenshot.jpg]] 13 | 14 | 15 | ### GraphXR 16 | [GraphXR](https://www.kineviz.com/) is a 3D graph view, which looks quite gorgeous! 17 | 18 | ![[graphxr.gif]] 19 | 20 | 21 | ### Neo4j Browser 22 | A query browser that uses the [[Cypher]] language to query your vault. Can be used for advanced queries or data anlysis of your vault. 23 | 24 | ![[browser_screenshot.png]] 25 | 26 | ### Gephi 27 | Neo4j can export to the graph visualization software [[Gephi]]. See the [Neo4j document](https://neo4j.com/labs/apoc/4.1/export/gephi/) for help on how to do this. Note that it requires installing the "APOC" plugin to your Neo4j Graph Database. 28 | 29 | 30 | 31 | --- 32 | #topic 33 | - hasTopic [[Neo4j]], [[Neo4j Stream Plugin]] 34 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Archived/Neo4j graph visualizations.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | Another use case for the [[Neo4j Graph View Plugin]] is to use your Obsidian vault in one of the many apps in the [[Neo4j Desktop]] 6 | Graph Apps Store. Using with this plugin active will automatically connect it to your vault. Here are some suggestions: 7 | 8 | ### Neo4j Bloom 9 | [Neo4j bloom](https://neo4j.com/product/bloom/) is very powerful graph visualization software. Compared to the embedded 10 | graph view in Obsidian, it offers much more freedom in customization. 11 | 12 | ![[bloom_screenshot.jpg]] 13 | 14 | 15 | ### GraphXR 16 | [GraphXR](https://www.kineviz.com/) is a 3D graph view, which looks quite gorgeous! 17 | 18 | ![[graphxr.gif]] 19 | 20 | 21 | ### Neo4j Browser 22 | A query browser that uses the [[Cypher]] language to query your vault. Can be used for advanced queries or data anlysis of your vault. 23 | 24 | ![[browser_screenshot.png]] 25 | 26 | ### Gephi 27 | Neo4j can export to the graph visualization software [[Gephi]]. See the [Neo4j document](https://neo4j.com/labs/apoc/4.1/export/gephi/) for help on how to do this. Note that it requires installing the "APOC" plugin to your Neo4j Graph Database. 28 | 29 | 30 | 31 | --- 32 | #topic 33 | - hasTopic [[Neo4j]], [[Neo4j Graph View Plugin]] 34 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Archived/Node styling.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | image: https://raw.githubusercontent.com/HEmile/obsidian-neo4j-graph-view/main/neo4j-graph-view/resources/styled_screenshot.png 4 | --- 5 | 6 | An example of node styling for this vault is provided at [[Semantic Obsidian node styling]]. 7 | 8 | Styling of nodes is done in .json format. 9 | The first key determines what tags or folders to apply this style to. For instance, `{"exampleTag":{"color":"yellow"}}` would color all notes with \#exampleTag yellow. You can style nodes using images in your vault with `{"shape": "image", "image": "http://localhost:3000/path/to/image"}.` 10 | 11 | When color-coding is set to Folders, use the path to the folder for this key. For instance, if you have a folder called `dailies`, use `{"dailies": {}}`. Use `{"/": {}}` for the root folder. 12 | 13 | 14 | See [this link](https://visjs.github.io/vis-network/docs/network/nodes.html) for all options for styling the nodes. 15 | 16 | Join the [[Discord]] for additional help (and to show off your configuration!). 17 | 18 | ## Special types 19 | - Use `{"defaultStyle": {}}` for the default styling of nodes 20 | - Use `{"image": {}}` to style images 21 | - Use `{"SMD_dangling": {}}` to style dangling notes (notes that don't have a real file, but are linked to) 22 | 23 | --- 24 | #howto 25 | - hasTopic [[Neo4j Graph View Plugin]] 26 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Archived/Semantic Markdown Converter Semantics.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | [[Semantic Markdown Converter]] collects all notes with extension .md in the input directory (default: `markdown/`). Each note is interpreted as follows: 6 | - Interprets [[tags]] as [[entity types]] 7 | - Interprets YAML frontmatter as entity properties 8 | - Interprets wikilinks as links with type `inline`, and adds content 9 | - Lines of the format `"- linkType [[note 1]], [[note 2|alias]]"` creates links with type `linkType` from the current note to `note 1` and `note 2`. 10 | - The name of the note is stored in the property `name` 11 | - The content of the note (everything except YAML frontmatter and typed links) is stored in the property `content` 12 | - Links to notes that do not exist yet are created without any types. 13 | 14 | This uses a very simple syntax for typed links. There is no agreed-upon [[Markdown]] syntax for this as of yet. See [[Link Types]] for a discussion on different formalizations. 15 | 16 | 17 | --- 18 | #howto 19 | - hasTopic 20 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Archived/Semantic Markdown Converter.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | This article is about the [[Python]] code that the [[Neo4j Graph View Plugin]] uses. In the near future, this code will be replaced with [[Javascript]]. See [[Roadmap#Port semantic markdown convert to Javascript]]. 6 | 7 | ## Semantic Markdown to Neo4j 8 | The [[Neo4j Graph View Plugin]] uses the Python package `semantic-markdown-converter`. The code is [in the same repository](https://github.com/HEmile/semantic-markdown-converter/tree/main/smdc). 9 | It creates an active data stream from a folder of Markdown notes to a Neo4j database. 10 | 11 | ### Getting started 12 | Note: The obsidian plugin automatically installs this package! 13 | 14 | Requires python 3.5+ and Neo4j desktop 15 | 16 | - Install with `pip install --upgrade semantic-markdown-converter` 17 | - Create a new database in Neo4j desktop and start it 18 | - Run `smds --input "folder with notes" --password "neo4j database password"` 19 | 20 | WARNING: This clears all current data in the active neo4j database! 21 | ### Supported input formats 22 | There is currently only one input format supported. An issue or use a pull request for different formats are appreciated! In particular for different markdown syntax for interpreting semantic links. 23 | 24 | ### Semantic Markdown to Neo4j server 25 | The command `smds` first uploads the complete folder of notes into the active Neo4j database. Then, it listens to changes in the notes to update the Neo4j database. 26 | 27 | #### Options 28 | - `--password`: Provide the password of the Neo4j database 29 | - `--input`: Provide the folder where to look for notes 30 | - `--index_content`: Set to true if you want Neo4j Bloom to search through the content of your notes when using the search bar. Can impact performance. 31 | 32 | ### Conversion mode 33 | The command `smdc` only converts the input folder, but does not create a stream. 34 | #### Neo4j 35 | Streams the input into the currently active Neo4j database. WARNING: This clears all the data in your database by default! Run with `--retaindb` if this is not desired. 36 | 1. Start the database in Neo4j you want to use 37 | 2. Run using `smdc --input "folder with notes" --password "neo4j database password"`. This can take a couple of minutes for large vaults. 38 | 39 | --- 40 | #project #topic 41 | - hasTopic [[Semantic Obsidian]] 42 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Archived/Semantic Obsidian edge styling.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | An example of edge styling which can be used for the [[Semantic Obsidian]] vault (ie, this vault). 6 | 7 | ```json 8 | {"defaultStyle":{ 9 | "font":{"size":12,"strokeWidth":2}, 10 | "width":0.5 11 | }, "inline": { 12 | "width": 0.3, 13 | "color": "gray" 14 | }, "hasTopic": { 15 | "width": 1, 16 | "color": "#F79767" 17 | }, "subset": { 18 | "width": 2.7, 19 | "color": "#F79767" 20 | }, "author": { 21 | "width": 0.3, 22 | "color": "black", 23 | "arrows": "", 24 | "font": {"size": 0} 25 | } 26 | } 27 | ``` 28 | 29 | --- 30 | #example 31 | - hasTopic [[Edge styling]] 32 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Archived/Semantic Obsidian node styling.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | An example of node styling which can be used for the [[Semantic Obsidian]] vault (ie, this vault). 6 | 7 | ```json 8 | {"defaultStyle":{ 9 | "size":9, 10 | "font":{"size":12,"strokeWidth":1}, 11 | "borderWidth":0, 12 | "widthConstraint":{"maximum":150}, 13 | "shape": "dot" 14 | },"image":{ 15 | "size":40, 16 | "font":{"size": 0} 17 | },"author":{ 18 | "size":5, 19 | "color":"black", 20 | "font":{"size": 8, "strokeWidth": 0, "color": "black"}, 21 | "shape": "text" 22 | }, "howto":{ 23 | "color": "#90cfff", 24 | "shape": "box", 25 | "font": {"size": 10, "strokeWidth": 1} 26 | }, "topic": { 27 | "color": "#F79767", 28 | "size": 18, 29 | "font": {"size": 16, "strokeWidth": 1}, 30 | "shape": "ellipse" 31 | } 32 | ``` 33 | 34 | --- 35 | #example 36 | - author [[Emile van Krieken]] 37 | - hasTopic [[Node styling]] -------------------------------------------------------------------------------- /docs/Archived/Using the Neo4j Graph View.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | ## Getting started 5 | ### Opening the Graph View 6 | On an open note, click the three dots in the right-upper corner, and select: "Open Neo4j Graph View": 7 | 8 | ![[Screen Recording 2021-01-02 at 15.32.58.mp4]] 9 | 10 | You can also use the command "Neo4j Graph View: Open local graph of note". You can run commands using ctrl/cmd+p. Alternatively, you can bind this command to a hotkey in the Obsidian settings. 11 | 12 | ![[Pasted image 20210101144050.png]] 13 | 14 | Finally, you can right-click on a file and open the graph from there: 15 | ![[Pasted image 20210102141546.png|300]] 16 | 17 | The local graph of a node shows the opened note and all other notes that are linked to it (including 'backlinks'). It will show also show images in your notes within the graph. I'll work on doing a preview of videos as well. 18 | 19 | Hovering over a node or a relationship gives a preview of the corresponding note and context of the relation. 20 | ![[Screen Recording 2021-01-02 at 15.38.11.mp4]] 21 | ### Interacting with the graph 22 | Click on a node to open it in Obsidian: 23 | 24 | ![[Screen Recording 2021-01-02 at 15.01.02.mp4|300]] 25 | 26 | Double-click on a node to show ("expand") its neighbors. Neighbors are the outgoing links and the backlinks. 27 | 28 | ![[Screen Recording 2021-01-02 at 15.05.43.mp4]] 29 | 30 | Hold shift, then click and drag in the graph view to select nodes. 31 | ![[Screen Recording 2021-01-02 at 15.09.51.mp4]] 32 | 33 | Right-click in the graph view to open the context menu. This can be used to open the corresponding file in several places. 34 | 35 | ![[Screen Recording 2021-01-02 at 15.14.06.mp4]] 36 | 37 | The context menu has some options to manipulate what you see in the graph. One very useful thing is to hide some nodes that clutter up the view. This is done by shift-draggin to select the nodes to hide, then using the context menu and click on "Hide selection". You can also use the "H" button to hide the selected nodes. 38 | 39 | ![[Screen Recording 2021-01-02 at 15.24.43.mp4]] 40 | 41 | The other options are: 42 | - 43 | - **Expand selection** (hotkey E): "Expand" the neighbors of all selected nodes. This will add all nodes related to the selected nodes. 44 | - **Invert selection** (hotkey I): Select all the nodes that are not currently selected. 45 | - **Select all** (hotkey A): Select all nodes. 46 | 47 | The graph view will also add nodes corresponding to files you visit in Obsidian, to create a "path" of nodes you visited. 48 | 49 | ![[Screen Recording 2021-01-02 at 15.43.17.mp4]] 50 | 51 | The graph view will also automatically update with changes you make in the note. 52 | 53 | ![[Screen Recording 2021-01-02 at 14.19.42.mp4]] 54 | 55 | The settings contains several options, such as coloring based on folders and a hierarchical layout. 56 | 57 | ## Advanced use 58 | ### Styling 59 | You can style both nodes and edges, as shown in the above videos. See [[Node styling]] and [[Edge styling]] for guidance on how to do this. 60 | ### Cypher Querying 61 | You can do very complicated querying using the [[Cypher]] query language. Create code blocks with language `cypher`. In this code block, create your Cypher query. Then, when the cursor is on this code block, use the Obsidian command 'Neo4j Graph View: Execute Cypher query'. Example: 62 | 63 | ![[cypher_querying.png]] 64 | 65 | [[Contributing]] 66 | 67 | --- 68 | #howto 69 | - hasTopic [[Neo4j Graph View Plugin]] 70 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | Contributions are very much welcomed. The easiest way to get started is to join the [[Discord]] server to get talking! Here are some ways to help: 6 | 7 | ## Designing plugins 8 | Ideally, the features of [[Juggl]] and plugins using the [[Juggl API]] would be designed with the Obsidian community. Your input is very welcome, especially on features mentioned on the [[Roadmap]]. The best way to help here is by joining the [[Discord]] server. 9 | 10 | ## Contributing to this vault 11 | This vault is publicly available at https://github.com/HEmile/juggl/tree/main/docs. If you know of a cool way to contribute, for example by writing or improving a guide or sharing something cool you made with a plugin, please submit a pull request! 12 | If you do, please add - author \[\[\]\] links to your profile. 13 | 14 | ## Help develop Juggl and other plugins 15 | If you know some programming, help is definitely appreciated! See the [[Roadmap]] for possible features to help on, or submit a pull request to the github repo https://github.com/HEmile/juggl . 16 | 17 | 18 | --- 19 | #development 20 | - hasTopic [[Juggl]] 21 | - author [[Emile van Krieken]] 22 | -------------------------------------------------------------------------------- /docs/Discord.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | image: https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/91_Discord_logo_logos-512.png 4 | --- 5 | 6 | The [Discord](https://discord.com/) server for [[Juggl]] is open for everyone! You can join the server with [this link](https://discord.gg/sAmSGpaPgM). 7 | 8 | Current goals of the server: 9 | - Discuss [[Juggl]] 10 | - Show off graphs 11 | - Discussing advanced use 12 | - [[Styling]] of the graph, in particular [[CSS Styling|CSS]] and [[YAML Styling|YAML]]. 13 | - [[Workspace mode]] 14 | - Usage of the [[Juggl code block|code fence]] 15 | - Feature requests and discussion 16 | - Discuss ideas related to [[Semantic Obsidian]] 17 | - [[Obsidian]] plugin ideas 18 | - [[Link Types]] formalizations 19 | - Provide a space to collaborate on plugins using the [[Juggl API]] 20 | 21 | --- 22 | #development 23 | - hasTopic [[Semantic Obsidian]] 24 | - hasTopic [[Juggl]] 25 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Emile van Krieken.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [Emile] 3 | image: https://pbs.twimg.com/profile_images/1087342750679347200/mQMh0604_400x400.jpg 4 | --- 5 | 6 | I am the developer of [[Juggl]]! Nice to meet you :) 7 | 8 | ## Links 9 | - [Twitter](https://twitter.com/EmilevanKrieken) 10 | - [Github](https://github.com/HEmile) 11 | - [Personal website](https://emilevankrieken.com/) 12 | - [LinkedIn](https://www.linkedin.com/in/emile-van-krieken-135a43b7) 13 | 14 | 15 | --- 16 | #author 17 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Features/Breadcrumbs code blocks.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | As part of the [[Breadcrumbs integration]], you can use code blocks for querying in the Breadcrumbs plugin can visualize the results using [[Juggl]]! See this guide for more information https://github.com/SkepticMystic/breadcrumbs/wiki/Codeblocks . 6 | 7 | These code blocks support the same options as [[Juggl code block]]s: 8 | ![[Juggl code block#Code block options]] 9 | 10 | --- 11 | 12 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Features/Breadcrumbs integration.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | Juggl is tightly integrated with the [Breadcrumbs](https://github.com/SkepticMystic/breadcrumbs) plugin! If you have Breadcrumbs installed, it will add all its edges to your Juggl graphs using [[Link Types]]. This can be used to incorporate the Dataview inline attributes syntax, such as `hasTopic:: [[artificial intelligence]]`. 6 | 7 | You can visualize the *up* and down *hierarchies* using the [Juggl View](https://github.com/SkepticMystic/breadcrumbs/wiki/Views#juggl-view). It uses a custom [[Layouts|hierarchical layout]] for the best insight. See [the Breadcrumbs wiki for documentation](https://github.com/SkepticMystic/breadcrumbs/wiki/Views#juggl-view). 8 | 9 | ![[Pasted image 20220127142903.png]] 10 | 11 | Custom hierarchy visualizations can also be created using [[Breadcrumbs code blocks]]. 12 | 13 | --- 14 | 15 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Features/Export to other programs.md: -------------------------------------------------------------------------------- 1 | Juggl supports exporting the graph to [Cytoscape](https://cytoscape.org/ "Cytoscape: An Open Source Platform for Complex Network Analysis and Visualization"). 2 | 3 | # Cytoscape 4 | You can save the graph in the [[Workspace mode]], then look in the Juggl plugin folder for the saved json file. You should be able to import this into Cytoscape desktop 5 | 6 | --- 7 | #feature 8 | -------------------------------------------------------------------------------- /docs/Features/Filtering.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [Filter, filter, filters] 3 | --- 4 | 5 | You can filter the nodes in the graph using the Filter toolbar: 6 | 7 | ![[Pasted image 20210413145227.png]] 8 | 9 | This filter also appears in the [[Style Pane]]: 10 | 11 | ![[Pasted image 20210413145921.png]] 12 | 13 | The syntax of this simulates [the search syntax in Obsidian](https://help.obsidian.md/Plugins/Search), with some limitations and extra features. 14 | 15 | # Search Operators 16 | - `file:`, `path:`, `content:` and `tag:` all work as documented in the [Obsidian help vault](https://help.obsidian.md/Plugins/Search) 17 | - `class:` Search based on [[CSS Styling#Classes|CSS class]]. 18 | - `raw:` Search using a [[CSS Styling#Selectors|CSS selector]]. For example, if you have a YAML attribute like `year`, you can get all nodes after 2000 using `raw:[year>2000]`. 19 | - Any attribute you use in your YAML frontmatter can be used for querying, for instance `aliases:`, `color:` and `title:`. 20 | 21 | ## Tips 22 | - Hiding **attachments**: `-class:file` 23 | - Hiding **images**: `-class:image` 24 | - Hiding **dangling nodes**: `-class:dangling` 25 | - You can add those filters to the [[Style Pane]] to quickly hide and unhide them 26 | 27 | ## Limitations 28 | Regex does not work, nor do the `section:`, `line:` and `block:` operators. 29 | 30 | --- 31 | #feature 32 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Features/Global Graph mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [global graph] 3 | --- 4 | 5 | [[Juggl]] supports a global graph like the one in [[Obsidian]] that shows all your notes in a single graph. You can open it from the left ribbon: 6 | 7 | ![[Pasted image 20210330193014.png]] 8 | 9 | You will get a warning sign when you first open the Global Graph view: 10 | ![[Pasted image 20210402152820.png]] 11 | 12 | Like this warning says, Juggl is not designed for larger vaults, and opening the global graph will very likely freeze Obsidian! This functionality is only provided as a convenience option for smaller vaults (like the one you're looking at). 13 | 14 | If you are looking for a global graph for larger vaults, we advise to stick with the vanilla [[Obsidian]] graph. It is exceptionally well optimized and can look very beautiful with the color group functionality. While there are plans to include a performance mode to scale to slightly larger graphs, it is highly unlikely Juggl will ever manage graphs as large as in the vanilla Obsidian graph. 15 | 16 | --- 17 | #feature 18 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Features/Juggl code block.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [code fence, code block] 3 | --- 4 | 5 | ![[code_fence.gif|400]] 6 | 7 | You can use 'code fences' to embed [[Juggl]] graphs inside notes. You can also do this using the [[Breadcrumbs]] plugin with [[Breadcrumbs code blocks]]. These code blocks have tonnes of (shared) options. See [[#Code block options]] for a full overview. 8 | 9 | For example, the following code block: 10 | 11 | ~~~ 12 | ```juggl 13 | local: Juggl code fence 14 | ``` 15 | ~~~ 16 | 17 | will display when using Juggl as: 18 | 19 | ![[Pasted image 20210413150208.png|300]] 20 | 21 | The code fence is in the familiar YAML syntax. 22 | 23 | ## Creating a Juggl code block 24 | The code fence requires one of currently two **modes**. You can use either `local` for the graph around a node... (To write!) 25 | 26 | ## Code block options 27 | Add these fields as options in YAML syntax. The default value is in between parentheses. 28 | - `layout` (force-directed). The layout used for the graph. Choose from `force-directed, circle, grid, hierarchy` 29 | - `fdgdLayout` (cola): The algorithm to use for force-directed layouts. Choose from `cola, d3-force` 30 | - `filter` (''): A [[Filtering|Filter]] to use on the graph 31 | - `width` (100%): The width of the canvas created by the code block 32 | - `height` (750px): The height of the canvas created by the code block 33 | - `limit` (250): The maximum amount of nodes to display in the visualization 34 | - `metaKeyHover` (true): Whether to only show hover previews when the meta key (ctrl/cmd) is down 35 | - `navigator` (true): Whether to show the 'mini-map' in the bottom-right corner 36 | - `toolbar` (true): Whether to show the toolbar on top 37 | - `zoomSpeed` (1): How quickly to zoom in and out 38 | - `autoAddNodes` (false): Whether to automatically add the corresponding node when you switch to a note 39 | - `autoExpand` (false): 40 | - `autoZoom` (false): Whether to automatically zoom such that the whole graph is visible. This is done when a layout is finished 41 | - `expandInitial`: (false): Whether to automatically expand the sets of nodes from the query. Warning: This can create very big graphs! 42 | 43 | --- 44 | #feature 45 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Features/Layouts.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [layout, hierarchical layout] 3 | --- 4 | 5 | ![[layouts.gif|400]] 6 | 7 | [[Juggl]] contains (currently) four different layout options. You can choose a layout from the buttons on the left of the toolbar: 8 | ![[Pasted image 20210403151424.png|400]] 9 | 10 | In order, these are the following layouts: 11 | 12 | - **Force directed layout**: A standard force directed layout, where nodes push each other away and links try to keep nodes together. Juggl uses the [Cola layout](https://github.com/cytoscape/cytoscape.js-cola) by default, but this can sometimes give weird vertical results. A more standard approach that is [like Obsidians graph](https://github.com/shichuanpo/cytoscape.js-d3-force) can be used from the settings under **Extensions > Force Directed Layout**. ![[Pasted image 20210408165620.png|400]] 13 | - **Concentric**: Puts all nodes in a circle, with the 'focused nodes' in the center. The focused nodes are the nodes around which we use the local mode, or expanded nodes. ![[Pasted image 20210408165722.png|400]] 14 | - **Grid**: A standard grid-like layout, similar to Roam Research. ![[Pasted image 20210408165744.png||400]] 15 | - **Hierarchical**: Puts the notes in a hierarchy over links. This uses the [Dagre algorithm](https://github.com/cytoscape/cytoscape.js-dagre). Used in the [[Breadcrumbs integration]] for best effect. ![[Pasted image 20210408165833.png|400]] 16 | 17 | 18 | --- 19 | #feature 20 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Features/Local Graph mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | The local graph mode simulates the local graph of [[Obsidian]]. It will display the currently active note and the notes around it. 6 | 7 | I'm planning to implement a depth-slider for this later! 8 | 9 | --- 10 | #feature 11 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Features/Nodes Pane.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | The nodes pane opens, together with the [[Style Pane]], in the right sidebar of [[Obsidian]]. It shows a list of nodes in the currently active [[Juggl]] graph. 5 | 6 | Clicking on an item in the list currently opens the associated file. You can also right-click on items in the list to open a context menu. 7 | 8 | The Nodes Pane can also be used to restore nodes you filtered from the graph under 'Hidden nodes'. 9 | 10 | ![[Pasted image 20210403151146.png]] 11 | 12 | The color of the items is the same as the color of the nodes in the graph. 13 | 14 | --- 15 | #feature 16 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Features/Styling/CSS Styling.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [CSS] 3 | image: "files/CypherQuerying.png" 4 | --- 5 | 6 | For complete control over [[Styling]], you can use CSS. This has a somewhat higher learning curve, but is extremely powerful! It allows you to create rule-based styling so you can visually represent concepts or attributes in your vault. 7 | 8 | To use the CSS functionality, edit `.obsidian/plugins/juggl/graph.css`. Edits in this file should automatically change the style of currently open graphs. 9 | You can also open this file from the Juggl settings: 10 | 11 | ![[Pasted image 20210422092407.png]] 12 | 13 | This document acts as an overview of common and useful options for Juggl. For documentation on everything that's possible (_a lot!_), see the [Cytoscape.js styling documentation](https://js.cytoscape.org/#style). 14 | 15 | We will first discuss how to [[#Selectors|select certain objects]], then discuss common [[#Properties|properties]] you might want to change, then finally show a couple of [[#Snippets|snippets]] to get started. 16 | 17 | If you prefer to start off with examples, I'd recommend immediately jumping to [[#Snippets]]. 18 | 19 | If you need more help, feel free to join the [[Discord]] where help is provided for all your styling questions! 20 | 21 | # Selectors 22 | [Selectors](https://www.w3schools.com/cssref/css_selectors.asp) are used to filter what objects to style. The two main 'elements' that can be targeted are `node` and `edge`. 23 | ## Classes 24 | Classes can be selected using `.class`. Available classes that are useful to style different nodes in Juggl are: 25 | - `.tag-tagname`: Targets all nodes with the tag #tagname 26 | - `.type-typename`: Targets all edges with the [[Link Types|type]] 'typename' 27 | - `.has-incoming-typename`: Targets all nodes that have an edge with [[type]] 'typename' coming into it. 28 | - `.has-outgoing-typename`: Targets all nodes that have an edge with [[type]] 'typename' going out of it. 29 | - Any class in the `cssclass` property in your [[YAML Styling|YAML]] frontmatter is also available on that node. 30 | - `.dangling`: Targets dangling nodes (nodes that aren't created as a file) 31 | - `.image`: Targets all nodes that are rendered as images. This is image files, but also notes that are styled with the `image` [[YAML Styling|YAML]] property. Similar for `.audio`, `.video` and `.pdf`. 32 | - `.file`: Targets all attachments (everything that does not have the `.md` extension) 33 | - `.note`: Targets all nodes that represent `.md` files. 34 | - `.global-3`: Targets nodes in the **fourth** global [[Style Pane|style group]](note: zero-based indexing!) 35 | - `.local-5`: Targets nodes in the fifth local style group 36 | 37 | There are also several classes to target nodes with a certain state: 38 | - `:selected`: Targets selected nodes 39 | - `.pinned`: Targets 'locked' nodes 40 | - `.expanded`: Targets [[Workspace mode|expanded]] nodes 41 | - `.hover`, `.unhover`: Targets hovered nodes 42 | - `.active-node`: Targets currently active node (usually currently open note) 43 | - `.connected-active-node`: Targets neighbours of active node 44 | - `.inactive-node`: Targets nodes not in the neigbourhood of the active node 45 | - `.filtered`: Targets nodes filtered using the [[Filtering|filter]] toolbar 46 | - `.hard-filtered`: Targets nodes filtered using user interaction 47 | - `.protected`: Internal class that targets nodes that cannot be automatically removed 48 | - `:loop`: Targets edges that are self-loops (same source and target) 49 | 50 | ## Attributes 51 | Attributes are values on nodes that you can use for styling. You can refer to these attributes using `data(attribute_name)`. You can also use them to create linear maps using `mapData(attribute_name, in_min, in_max, out_min, out_max);`. Attributes can also be used to select elements, see [this page](https://js.cytoscape.org/#selectors/data) for all options. 52 | 53 | ### Nodes 54 | - Any [[YAML Styling|YAML]] property can be referenced by name. For example, if you have `cooking_time: 34` in your YAML frontmatter, you can use 55 | ```css 56 | node[cooking_time] { 57 | width: data(cooking_time); 58 | } 59 | ``` 60 | to scale the width of nodes by cooking time. 61 | - `name`: The name of the node 62 | - `path`: The path (relative to vault) of the file the node represents. This can be used to select based on folders: 63 | ```css 64 | node[path ^= 'folder1/folder2/'] { 65 | background-color: red; 66 | } 67 | ``` 68 | This means: Select all nodes with paths that start with `folder1/folder2`, ie files in that folder. 69 | - `content`: The content of the note. You can use this to search for some text, for instance: 70 | ```css 71 | node[content @*= 'juggl'] { 72 | background-color: red; 73 | } 74 | ``` 75 | This means: Select all notes that contain 'juggl', case insensitive. 76 | 77 | ### Edges 78 | - `context`: The line the link is in. This gives some context about where the link is used and is used in the edge hover preview. Can be used to filter for certain lines with the same structure. 79 | - `alias`: The alias used on the link. Only present if an alias is used. 80 | - `degree`: The amount of edges connected to the node. 81 | - `edgeCount`: When edges are merged (default), this is the total amount of edges of a certain type that are merged together. This is used by default to make lines thicker for merged edges that represent more links. 82 | # Properties 83 | Cytoscape.js provides many properties to target for styling, both for [nodes](https://js.cytoscape.org/#style/node-body) and [edges](https://js.cytoscape.org/#style/edge-line). 84 | 85 | ## Nodes 86 | Useful common properties are listed as following. See [this link](https://js.cytoscape.org/#style/node-body) for all options. There are _way_ more than listed here, so if you're looking for something specific, make sure to check out that link. 87 | - `width`, `height`: Change width and height, individually 88 | - `shape`: Node shape, choose from the same options as in the [[Style Pane]]. 89 | - `background-color`: Color of the node 90 | - Check [this link](https://js.cytoscape.org/#style/node-body) for options with gradient-coloring 91 | - `background-opacity`: Opacity of the node 92 | - `border-width`, `border-color`, `border-opacity`: Style the border of the node 93 | - `border-style`: Choose from `solid, dotted, dashed, double` 94 | - `background-image`: URL to the background image. See [this link](https://js.cytoscape.org/#style/background-image) and [[YAML Styling]] for nuances 95 | - There is a significant amount of options for dealing with images, such as how it is contained in the node, smoothing, opacity, offset, etc. See [this link](https://js.cytoscape.org/#style/background-image). 96 | - `label`: The text on the node, usually the name of a note. 97 | - Many standard options for styling this are available like the `font-family`, see [this link](https://js.cytoscape.org/#style/labels). You can for example change the positioning of the text to be inside the node. The 98 | - `display`: Set to none to not display the element. 99 | 100 | ## Edges 101 | Edges can also be completely styled. See [this link](https://js.cytoscape.org/#style/edge-line) for all options. 102 | - `width`: Width of the line 103 | - `curve-style`: The style of the curve of the line. There are many, complex options. See [the full documentation](https://js.cytoscape.org/#style/edge-line). By default, Juggl uses Bezier edges, which is relatively expensive. For performance reasons, you can use the haystack style. 104 | - `line-color`: Color of the edge 105 | - `line-style`: Choose from `solid, dotted, dashed` 106 | - `line-opacity`: Opacity of the edge. 107 | - `label`: The text on the node 108 | # Snippets 109 | **Style [[Link Types]]**: Color links with the `author` type red. 110 | ```css 111 | .type-author { 112 | line-color: red; 113 | } 114 | ``` 115 | 116 | **Map cooking time to colour**: Changes from the color blue to red depending on how long it takes to cook a meal: 117 | ```css 118 | node[cooking-time] { 119 | background-color: mapData(cooking-time, 1, 120, blue, red); 120 | } 121 | ``` 122 | 123 | **Change opacity and width of a line**, depending on how many connections (from 1 to 15) there are from one node to the other: 124 | ```css 125 | edge[edgeCount] { 126 | width: mapData(edgeCount, 1, 15, 0.5, 3); 127 | line-opacity: mapData(edgeCount, 1, 15, 0.5, 0.9); 128 | } 129 | ``` 130 | 131 | **Display the context of a link**: This will display the sentences around where the link appears. This can be a bit messy! 132 | ```css 133 | edge.inline { 134 | label: data(context); 135 | text-opacity: 0.8; 136 | font-size: 2; 137 | text-wrap: ellipsis; 138 | text-max-width: 100px; 139 | } 140 | ``` 141 | 142 | ![[Pasted image 20210414175943.png|500]] 143 | 144 | You can also only show this whenever you are hovering over the edge. You have to activate 'Hover on edges' in the Juggl settings for this feature: 145 | ```css 146 | edge.inline.hover { 147 | label: data(context); 148 | ... 149 | } 150 | ``` 151 | 152 | **Text in a box**: One styling I love is to have rectangle nodes that contain some text. This can be achieved (imperfectly!) using 153 | ```css 154 | .tag-paper { 155 | shape: rectangle; 156 | width: 50px; 157 | height: 45px; 158 | font-size: 5; 159 | text-valign: center; 160 | text-max-width: 45px; 161 | text-opacity: 1; 162 | } 163 | ``` 164 | 165 | ![[Pasted image 20210414182140.png|400]] 166 | 167 | **Text wrapping for non-Latin script**: The text in a box will not work for languages using different script, like Japanese. For these, you could try the following snippet by Kazdon: 168 | ```css 169 | .note { 170 | shape: rectangle; 171 | width: 40px; 172 | height: 20px; 173 | text-valign: center; 174 | text-max-width: 35px; 175 | text-overflow-wrap: anywhere; 176 | } 177 | ``` 178 | And similar for inline context on edges: 179 | ```css 180 | edge.inline { 181 | label:data(context); 182 | text-wrap: wrap; 183 | text-max-width: 250px; 184 | text-overflow-wrap: anywhere; 185 | } 186 | ``` 187 | **Only show icons, not shapes**: If you only want to show the icon of a node and not the shape around it, you can use 188 | ```css 189 | node { 190 | background-opacity: 0; 191 | border: 0; 192 | shape: rectangle; 193 | } 194 | ``` 195 | ![[Pasted image 20220121103800.png]] 196 | 197 | # Current limitations 198 | 199 | - CSS variables like `var(--background-primary)` will not be recognized. If this is something you need, please add a pull request. 200 | 201 | - `not()` does not seem to work. 202 | 203 | --- 204 | #feature 205 | - author [[Emile van Krieken]] 206 | - hasTopic [[Styling]] -------------------------------------------------------------------------------- /docs/Features/Styling/Images.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | Images are implemented using a bit of a hack with a local server that hosts the images in your vault. Importantly, **if you use multiple vaults** you will need to assign a unique port to each vault to ensure Juggl knows what vault to look up. 6 | This can be done in the settings under `Advanced -> Image Server Port`. 7 | 8 | --- 9 | - hasTopic [[Styling]] 10 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Features/Styling/Style Pane.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [style group] 3 | --- 4 | 5 | ![[style_pane.gif|400]] 6 | 7 | The easiest way to [[Styling|style]] a group of nodes is the Style Pane. You can style nodes using a simple user interface that already provides a large amount of options. 8 | 9 | The style pane is a separate view which opens by default in the right sidebar of [[Obsidian]]: 10 | 11 | ![[Screen Recording 2021-04-14 at 17.29.09.mp4]] 12 | 13 | A group of nodes is chosen using a [[Filtering|Filter]]. The styling options for each group of nodes are 14 | - Show or hide the group 15 | - Shape and color 16 | - Icon and icon color 17 | - Relative size of the nodes and text 18 | 19 | 20 | 21 | ## Global and local style groups 22 | With the style pane you can create both local and global style groups. What's the difference? 23 | 24 | ### Global style groups 25 | Global style groups are applied to every [[Juggl]] graph you open. They are saved together with your global Juggl settings. 26 | 27 | ### Local style groups 28 | Local style groups are specific to one graph. The style pane will show the local style groups of your currently active graph. So, if you click on the workspace leaf where your graph is opened, it will show the local style groups associated with that graph! 29 | 30 | Local style groups are saved together with a [[Workspace graph]]. 31 | 32 | --- 33 | #feature 34 | - author [[Emile van Krieken]] 35 | - hasTopic [[Styling]] -------------------------------------------------------------------------------- /docs/Features/Styling/Styling.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [style] 3 | --- 4 | 5 | You can style the graph of [[Juggl]] in many ways. On this page you can find for each use case how best to use the styling options provided. 6 | 7 | ## Styling groups of nodes and edges 8 | Styling groups of nodes can be done using the [[Style Pane]], which is easy but limited, and using [[CSS Styling|CSS]], which has a lot more options but is also harder to get started with. 9 | 10 | It is recommended to start with the Style Pane, but if you want to go all-in to the plugin, it can be worth it to learn CSS styling! 11 | 12 | ## Styling individual nodes 13 | The third option for styling is using the [[YAML Styling|YAML frontmatter]] of Obsidian notes. This allows for styling individual nodes. You can for example add an image in YAML that Juggl will show in the node to make it easily recognizable. 14 | 15 | ## Styling individual edges 16 | Juggl does not yet support styling individual edges, but this will be possible in the future through the new implementationo [[Link Types]]. You can use [[CSS Styling|CSS]] to style based on simple link types, though! 17 | 18 | ## FAQ 19 | ### What priority does each styling option get? 20 | In general, the idea is to make more specific styling options have higher priority. That is, in decreasing order of priority, we have [[YAML Styling|YAML]], local [[Style Pane|style groups]], global style groups, CSS and default styling. 21 | 22 | 23 | 24 | --- 25 | #howto 26 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Features/Styling/YAML Styling.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [YAML, YAML frontmatter] 3 | title: YAML 4 | color: blue 5 | image: files/img.png 6 | --- 7 | 8 | YAML frontmatter styling is used to selectively style individual nodes. A cool application is to assign images to nodes! 9 | The following properties are supported out of the box, but you can use any property you like (read on!). 10 | - `title`: Change the displayed label on a node. Works very well with Zettlkasten workflows 11 | - `color`: Change the color of a node. For example, you can do hexidecimals like `color: '#123456'` or with color names: `color: blue`. 12 | - `shape`: Change the shape of a node. You can use the same shapes as in the [[Style Pane]]. 13 | - `width` and `height`: Change the width and height of a node. 14 | - `image`: Use the given image as a background image on the node. You can use both external links (https) to a website image, or an image in your vault. Some caveats: External images can only be used if the site is configured to accept cross-reference requests! This means it won't work for the majority of external links. 15 | - `cssclass`: Can be used to assign [[CSS Styling#Classes|classes]] to the notes for [[CSS Styling]]. 16 | A better option is to download the image and save it to your vault. To reference an image in your vault, you need to use the **path to the image**, not just the name of the image. For instance, if your image `img.png` is in the `files` folder, you should do `image: files/img.png`. 17 | - `cssclass`: List of [[CSS Styling|CSS]] classes that can be used for additional styling of nodes. 18 | 19 | ## Using other YAML properties 20 | If you have been using other YAML properties that you would like to map to some styling option, that's possible! You will need to use some [[CSS Styling|CSS]] though. 21 | 22 | Here is how some of the given properties above are implemented in CSS: 23 | 24 | ```css 25 | node[title] { 26 | label: data(title); 27 | } 28 | 29 | node[shape] { 30 | shape: data(shape); 31 | } 32 | 33 | node[image] { 34 | background-image: data(image); 35 | } 36 | ``` 37 | 38 | Something similar can be done for any YAML property! Select nodes that have the property using `node[property_name]`, then assign the styling attribute using `attribute: data(property_name)`. 39 | 40 | If you have been using categories, like the genre of an album, you can use the following CSS: 41 | 42 | ```css 43 | node[genre = 'drama'] { 44 | background-color: black; 45 | } 46 | ``` 47 | 48 | More ways to filter data are found in the [Cytoscape.js documentation](https://js.cytoscape.org/#selectors/data). 49 | 50 | --- 51 | #feature 52 | - author [[Emile van Krieken]] 53 | - hasTopic [[Styling]] -------------------------------------------------------------------------------- /docs/Features/Workspace mode/Workspace graph.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | --- 6 | #feature 7 | - author [[Emile van Krieken]] 8 | - hasTopic [[Workspace mode]] -------------------------------------------------------------------------------- /docs/Features/Workspace mode/Workspace mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | The Workspace Mode is the unique graph interactivity mode implemented in [[Juggl]]. It gives you full control over navigation in the graph, over what nodes are displayed and how nodes are laid out. Furthermore, you can save and load [[Workspace graph]]s to continue working on a graph. 6 | 7 | As an introduction, the design of the Workspace Mode can be compared to: 8 | 1. A 'desktop' of ideas, where everything you're currently working on is spatially laid out. 9 | 2. A 'brain' of visual concepts, that you can search through in a nonlinear fashion: Expand concepts and see where they lead, and find new connections between ideas. 10 | 11 | # Getting Started 12 | You can open the Workspace Mode by starting from a note. The easiest is using the 'file options' menu just like the local graph. This can be found in the hamburger menu above a note: 13 | 14 | ![[Pasted image 20210402154645.png|400]] 15 | 16 | With the default setting this opens the [[Local mode]]. You can activate the Workspace mode by clicking on this button in the toolbar: 17 | 18 | ![[Pasted image 20210402155246.png]] 19 | 20 | You can set Juggl to always open in the Workspace mode in the settings under **Extensions > Default mode**. 21 | 22 | ## Basic interaction 23 | In the workspace mode, you can drag around the screen to move it, and also drag nodes around to move them, like in other modes. However, the workspace mode contains additional interaction options. 24 | 25 | While holding shift, you can click and drag around the screen to select a group of nodes: 26 | ![[selectdrag.mp4]] 27 | 28 | Selected nodes can be interacted with through the [[#Toolbar]]. 29 | 30 | ### Radial context menu 31 | To interact with a single node, click and hold on that node to open the **radial context menu**: 32 | ![[Screen Recording 2021-04-03 at 13.58.20.mp4]] 33 | 34 | **Open file**: 35 | ![[Pasted image 20210403140754.png|200]] 36 | This opens the select file in an existing or new leaf. Clicking on a node also opens the file. 37 | 38 | **Expand or Collapse** 39 | ![[Pasted image 20210403141344.png|200]] 40 | 41 | Expanding and collapsing is a central concept of the Workspace mode. When you **expand** a node, all its neighbors are added to the graph. When you **collapse** an expanded node, its neighbors are removed again. There are some subtleties here, which we will discuss later. 42 | Expanding nodes is the easiest way to explore your graph! You can also expand by **double-clicking** a node, or by pressing the **E** button. 43 | 44 | **Locking and unlocking** 45 | ![[Pasted image 20210403142046.png|200]] 46 | With this button, you can lock and unlock a node into one spot. This means it won't move using the layout options. This can be useful when you want to manually choose the positions of some nodes to ensure they're always in the same position. 47 | 48 | 49 | **Filter node** 50 | ![[Pasted image 20210403144754.png|200]] 51 | 52 | Filters the node from the view. You can add the node back again using the [[Nodes Pane]]. 53 | 54 | **Fit view** 55 | ![[Pasted image 20210403143207.png|200]] 56 | 57 | Focus on the selected nodes and its neighbors, and zoom the view to fit on its neighborhood. 58 | 59 | ### Toolbar 60 | The toolbar is the set of buttons on the top of the Juggl view, with the following functions: 61 | ![[Pasted image 20210402162157.png]] 62 | 63 | These functions will apply to the full selection of nodes selected using shift-dragging! For instance, to hide a group of nodes, you would do: 64 | 65 | ![[Screen Recording 2021-04-03 at 14.59.13.mp4]] 66 | 67 | The first four buttons allow you to choose a [[Layouts|layout]]. To the right of that, you have the 'fit view' button which centers the view on the graph, a button to return to the [[Local mode]], and a button that navigates to the help vault (that you are reading right now). 68 | 69 | ### Saving and loading 70 | ![[save_workspace.gif||400]] 71 | ![[Screenshot 2021-04-03 at 15.01.44.png|200]] 72 | 73 | The highlighted button will let you save and load [[Workspace graph]]s. Click the button to open the Manage Workspace Graphs UI: 74 | 75 | ![[Pasted image 20210403150726.png]] 76 | 77 | This UI functions just like the [Manage Workspaces UI in Obsidian](https://help.obsidian.md/Plugins/Workspaces). 78 | 79 | --- 80 | #howto #feature 81 | - author [[Emile van Krieken]] 82 | - hasTopic [[Juggl]] -------------------------------------------------------------------------------- /docs/Installing Juggl.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | Installing Juggl is as easy as downloading and installing it from the 'Community plugins' section of the [[Obsidian]] settings. 6 | 1. Make sure 'safe mode' is turned off 7 | 2. Browse the Community plugins 8 | 3. Search for 'Juggl' 9 | 4. Click on the 'install' button. This might take a while, Juggl is 12+mb. 10 | 5. Activate the plugin ![[Pasted image 20210320161754.png]] 11 | 12 | # Pre-release versions 13 | There are currently no pre-release versions. 14 | 15 | ## Installation instructions for pre-release versions 16 | Unzip the downloaded file in `.obsidian/plugins/juggl/`. The easiest way to find this folder is to follow these steps: 17 | - Open the settings in Obsidian 18 | - Go to the 'Community plugins' tab 19 | - Make sure you deactivated safe mode 20 | - Click on the Open Plugins Folder icon: ![[Pasted image 20210320161536.png]] 21 | - In the resulting folder, create the `juggl` folder if you haven't already, then extract the downloaded file in there. 22 | - Activate the plugin ![[Pasted image 20210320161754.png]] 23 | 24 | --- 25 | #development 26 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Juggl API.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [API] 3 | --- 4 | 5 | The first release of the Juggl API is now available from https://github.com/HEmile/juggl-api . 6 | 7 | ![[Roadmap#^82c7c4]] 8 | 9 | ## Requirements 10 | Users of the API should be able to extend [[Juggl]] in the following ways. If something is missing, please [[Discord|let me know.]] 11 | 12 | ### Add additional data 13 | Add additional nodes and relations to the graph. This could for instance be used to [connect papers to their citations](https://forum.obsidian.md/t/show-online-literature-connections/10924) (and to papers they cite), like in [Connected Papers](https://www.connectedpapers.com/). There are many other sources of information to extract data from. 14 | 15 | Other ideas might be extracting a graph from the Markdown, such as adding the outline of a note like in the [Mind Map plugin](https://github.com/lynchjames/obsidian-mind-map), or developing new syntax for generating graphs. 16 | 17 | ### Manipulate the Graph View 18 | Add UI's on top of the graph view, or add ways of interacting with the visualization. 19 | Possible hooks: 20 | - Initialization of the visualization 21 | - Context menu 22 | - Before query 23 | - After nodes are added 24 | 25 | ### Manipulate styling of the graph 26 | Allow plugins to further manipulate styling of the graph. 27 | 28 | ## Development 29 | - All interfaces 30 | - Utility methods like tags parsing 31 | 32 | --- 33 | #development 34 | - hasTopic [[Juggl]] 35 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Juggl.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | Welcome to the documentation of Juggl. 6 | 7 | Juggl is the next generation of PKM-focused graph views! It is completely customizable and extendable, with many advanced features out of the box. 8 | 9 | - **Code** is on Github: https://github.com/HEmile/juggl 10 | - **Support the development** of Juggl: 11 | - Buy me a kofi: https://ko-fi.com/Emile 12 | - Paypal.me: https://paypal.me/EvanKrieken 13 | 14 | # Features 15 | ![[juggl trailer.gif]] 16 | ## Styling 17 | With Juggl you have completely control over the [[Styling|Style]] of your graph. You can use a special [[Style Pane]] that is very easy to use. For more advanced styling, you can use [[CSS Styling|CSS]] and [[YAML Styling|YAML]]. 18 | 19 | ## Workspace mode 20 | A very extensive new [[Workspace mode]] designed for keeping the focus on just the right notes. 21 | **Interactivity**: You can control the highly interactive graph using a special **[[Workspace mode#Radial context menu|radial context menu]]** and the [[Workspace mode#Toolbar|toolbar]]. 22 | - Select, expand and collapse nodes 23 | - Pin nodes in place 24 | - Hide and filter nodes from view 25 | 26 | **[[Workspace mode#Saving and loading|Save and load graphs]]** so you can always continue your work from where you left it. 27 | 28 | You can choose from **four [[Layouts|layout]] options**: 29 | 1. Force-directed 30 | 2. Circle 31 | 3. Grid 32 | 4. Hierarchical 33 | 34 | ### Breadcrumbs integration 35 | The popular Obsidian plugin for maintaining hierarchies '[Breadcrumbs](https://github.com/SkepticMystic/breadcrumbs)' is [[Breadcrumbs integration|tightly integrated]] with Juggl! You can render hierachies using Juggl or create custom [[Breadcrumbs code blocks]]! This one of the most useful applications of Juggl. 36 | 37 | ![[Pasted image 20220127142536.png]] 38 | 39 | ### Code block 40 | You can use "[[Juggl code block|code block]]s" to embed graphs within your Obsidian note! You can even use the graph you saved in the [[Workspace mode]]. 41 | 42 | ### More! 43 | - **Mobile ready!** While still buggy, the graph works on mobile 44 | - Has a **navigation element** that keeps an overview of the total graph 45 | - Supports stylable [[Link Types]]. You can use this to add labels to edges, for example 46 | - Supports a [[Global Graph mode|global graph]] for small vaults, and an Obsidian-like [[Local Graph mode|local graph]]. 47 | - Ready to be extended by other plugins through the [[Juggl API]] 48 | 49 | # Implementations and licensing 50 | Juggl currently only has an implementation for [[Obsidian]]. However, the meat of the code is not necessarily reliant on Obsidian and could be ported to other PKM software. If you are interested, you can contact [[Emile van Krieken|me]], preferrably on [[Discord]]. 51 | 52 | Note that Juggl is GPL3 **dual-**licensed. Contact me for details. 53 | 54 | 55 | 56 | 57 | --- 58 | #plugin 59 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Link Types.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [type] 3 | --- 4 | You can created Typed Links using the following list-based syntax: 5 | `- linkType [[note 1]], [[note 2|alias]]`. If you have [[Breadcrumbs integration|Breadcrumbs]] installed, its typed links such as `linkType:: [[note 1]]` are also added. 6 | 7 | For example, if you have a \[\[Rembrant\]\] note and you want to link it to \[\[The Night Watch\]\], you would add `- painted [[The Night Watch]]` to your Rembrant note. 8 | 9 | This vault contains many examples of such links at the bottom of the page. These will be visualized by [[Juggl]] as text on edges. For example, the typed links at the bottom of this page will be displayed as follows: 10 | 11 | ![[Pasted image 20210421133609.png|500]] 12 | 13 | Additionally, you can apply different styling using [[CSS Styling|CSS]] based on the type of the links to make these more distinct. This will [[Roadmap|in the future]] be possible using the [[Style Pane]]: 14 | 15 | ![[Pasted image 20210421133938.png|500]] 16 | 17 | Note: The `linkType` can only be a single word! This means `- has painted [[The Night Watch]]` will not be parsed as a typed link `has painted`. 18 | 19 | Furthermore, if you have the Breadcrumbs plugin installed, then with the [[Breadcrumbs integration]], it will also add all typed edges from that plugin. This includes those created with the Dataview inline attributes syntax `hasPainted:: [[The Night Watch]]`. 20 | 21 | ## Development 22 | This syntax has multiple issues. In this section we discuss ways to improve it that are not currently implemented: 23 | 1. It is metadata, and not inline. Most of the links people write in Obsidian are 'inline': They are part of the text, instead of seperate lines. 24 | 2. This syntax doesn't allow adding properties. For example, the relation `publishedIn` could have useful additional metadata, such as the year it was published. Similarly, when creating a list of ingredients, it'd be useful to also register the quantity needed for each ingredient. 25 | 3. You can only link the current note as a source. However, if, for example, you are writing a daily note, you might write that "Today, Joe Biden becomes the president of the US". The corresponding link might be \[Joe Biden\] -President->\[US\]. Otherwise, one would have to write this in a new "Joe Biden" note, even if this doesn't necessarily make sense while writing. 26 | 27 | To solve these issues we will work on a new, more general syntax. Currently, the idea is the following: `This recipe requires 20 grams of [[Rice|rice|ingredient|quantity=20 grams]]`. This is an inline syntax of the form `[[Note name|alias|linkType|property1=value1|property2=value]]`. However, this does not yet solve issue 3! 28 | 29 | A similar idea which is used in [Semantic Mediawiki](https://www.semantic-mediawiki.org/wiki/Semantic_MediaWiki) and which is going to be used in [Keypoints](https://keypoints.app), is `This recipe requires 20 grams of [[quantity=20 grams::ingredient::Rice|rice]]`. This puts the link type in the beginning, which can be more natural to read. It also helps standardize this syntax. 30 | 31 | Input for this syntax and ease of use is highly appreciated. We welcome you to join the discussion at [[Discord]]. 32 | 33 | 34 | --- 35 | #topic 36 | - subset [[Semantic Obsidian]] 37 | - author [[Emile van Krieken]] 38 | - hasTopic [[Juggl]] -------------------------------------------------------------------------------- /docs/Recommend plugins.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | These plugins work especially well together with [[Juggl]], and are kept in mind while developing Juggl to ensure compatibility. 5 | 6 | - **[Breadcrumbs](https://github.com/SkepticMystic/breadcrumbs/)**: [[Breadcrumbs integration|Tightly integrated]] with Juggl using a custom view. Can render its hierarchies within [[Juggl]] using [[Breadcrumbs code blocks]] 7 | ![[Pasted image 20220127142903.png]] 8 | - **[Supercharged links](https://github.com/mdelobelle/obsidian_supercharged_links)**: Can be used to mimic the styling of links such that they have the same color as the juggl nodes. 9 | - **[Pane Relief](https://github.com/pjeby/pane-relief)**: Pane-based history is very useful when you use Juggl in the [[Workspace mode]] next to a markdown view. 10 | - **[Hover editor](https://github.com/nothingislost/obsidian-hover-editor)**: Allows editing notes right from within Juggl by hovering over nodes. 11 | --- 12 | 13 | - author [[Emile van Krieken]] 14 | -------------------------------------------------------------------------------- /docs/Roadmap.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | 6 | # Juggl 7 | ## Planned 8 | ### [[Neo4j Stream]] 9 | Export your vault to [[Neo4j]]. 10 | Later on, this will allow for [[Cypher]] querying in [[Juggl]] ^2d1fd7 11 | 12 | 13 | ### [[Link Types]] 14 | Add better support for creating and maintaining link types. Also provide new syntax for inline link types and properties on links. A discussion on syntax is in [[Link Types]]. 15 | - Inline typed links 16 | - Properties on links 17 | - Preview typed links using templates 18 | - Autocomplete typed links 19 | 20 | ### [[Style Pane]] for edges 21 | 22 | Makes it easier to style typed links, and to filter edges. 23 | ### Outline in compound nodes 24 | 25 | The outline of a note is essentially a tree. As it is completely hierarchical wrt the node representing the note, it can be nicely rendered and collapsed using compound nodes, like in 26 | 27 | ![](https://cdn.discordapp.com/attachments/794501737062203422/798921299404652574/JfMR9BuCHr.gif)__ 28 | 29 | This will likely be used in the Style Pane for edges. 30 | 31 | ### Edit your data in the graph 32 | - Create edges by dragging nodes together 33 | - Add new nodes to the graph 34 | - Rename files from the graph 35 | - Edit YAML metadata 36 | - Drag files into the graph 37 | 38 | ### [[Juggl API]] 39 | It is easy using [[Cytoscape.js]] to create an API for other plugin developers to use, to interact with [[Juggl]]. This could allow extending the graph view with eg automatically adding data from external sources, such as citation graphs, or with a different syntax for creating graphs. ^82c7c4 40 | 41 | ### Minor 42 | - Preview movie and pdf files using thumbnails 43 | - Add external (Markdown) links to the graph. Clicking on that node opens the link in your browser 44 | - Size individual notes using YAML metadata 45 | 46 | ## Ideas to discuss 47 | 48 | ### Compound nodes to represent hierachies 49 | 50 | 51 | --- 52 | #development 53 | - hasTopic [[Semantic Obsidian]], [[Juggl]] 54 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Screen Recording 2021-04-14 at 17.29.09.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/Screen Recording 2021-04-14 at 17.29.09.mov -------------------------------------------------------------------------------- /docs/Search on Internet Plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | ![[context_iframe.gif]] 6 | See the plugin [on Github](https://github.com/HEmile/obsidian-search-on-internet). 7 | 8 | ## Search on Internet 9 | Adds the option to search selected text on external websites, like Google and Wikipedia. You can add your own websites! 10 | 11 | ![](https://raw.githubusercontent.com/HEmile/obsidian-search-on-internet/master/resources/modal_demo.gif) 12 | 13 | It also adds the search options to the file context menu to search based on the title of a note: 14 | 15 | ![](https://raw.githubusercontent.com/HEmile/obsidian-search-on-internet/master/resources/demo.gif) 16 | 17 | You can also right-click on an internal link to perform a search on that link: 18 | 19 | ![](https://raw.githubusercontent.com/HEmile/obsidian-search-on-internet/master/resources/internal_link.png) 20 | 21 | 22 | ### Settings 23 | By default, the plugin comes with searches on Google and Wikipedia. 24 | You can add your own websites to search on in the settings. 25 | 26 | ![](https://raw.githubusercontent.com/HEmile/obsidian-search-on-internet/master/resources/img.png) 27 | 28 | For each website, fill in the following three fields: 29 | - Name: The name of the search. This will be displayed in the search bar and the context menu. 30 | - URL: The URL to open. `{{title}}` will be replaced by the current notes title. This is used as the 'query'. 31 | - Tags (optional): A list of tags for notes to display the search option on. 32 | In the example screenshot, this is used to only add the IMDB search on notes tagged with `#actor`, `#movie` or `#director` (in Dutch!) 33 | 34 | It's recommended to assign the command: "Search on Internet: Perform search" to a hotkey: 35 | 36 | ![](https://raw.githubusercontent.com/HEmile/obsidian-search-on-internet/master/resources/hotkey.png) 37 | 38 | 39 | ### Credits 40 | Settings code is mainly taken from the [Templater plugin](https://github.com/SilentVoid13/Templater) by [SilentVoid13](https://github.com/SilentVoid13) 41 | 42 | Modal code is inspired by the [Citation plugin](https://github.com/hans/obsidian-citation-plugin/blob/master/src/modals.ts) 43 | 44 | --- 45 | #plugin 46 | - hasTopic [[Semantic Obsidian]] 47 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/Semantic Obsidian.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | ## Introduction 6 | Semantic Obsidian is a long-term project to think of and develop plugins inspired by [[Semantic Markdown]], [[Property Graphs]] and [[Semantic Desktop]]s. 7 | 8 | The goal is to create from Obsidian a [[Personal Knowledge Management]] system that is traversed mainly from graphs in [[Juggl]]. This requires very strong and interactable graph visualization. It also requires a data-format that can easily be queried and extended, and can model linear, non-linear and hierarchical relations between files and notes. 9 | 10 | In Semantic Obsidian, notes are the main entities. They have both loose associations through inline wikilinks and backlinks, and strong links through [[Link Types]] with properties. They can be used to provide cues for files that are related. 11 | 12 | This gives the following requirements for the Graph View Plugin: 13 | - A clear and interactable graph view 14 | - Save and load graphs 15 | - Style the graphs 16 | - A text-first data format 17 | - Data format should easily be extended and converted to other formats 18 | - Data format should be local 19 | - Data format should be optional 20 | - Users should be free to choose to formalize data to any degree 21 | - Advanced use should not be in view if not needed. 22 | - Annotate links with [[Link Types]] and properties 23 | - Easily extendable through an [[Juggl API|API]] 24 | 25 | --- 26 | #topic #project 27 | - hasTopic [[Obsidian]] 28 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /docs/files/CypherQuerying.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/CypherQuerying.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20201231174344.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20201231174344.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20201231174425.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20201231174425.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20201231175530.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20201231175530.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20201231180930.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20201231180930.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20201231181003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20201231181003.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20201231181103.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20201231181103.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20201231181917.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20201231181917.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210101144050.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210101144050.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210101144311.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210101144311.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210102141444.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210102141444.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210102141514.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210102141514.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210102141546.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210102141546.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210112215108.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210112215108.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210315210604.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210315210604.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210320161536.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210320161536.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210320161754.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210320161754.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210329191901.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210329191901.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210330193014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210330193014.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210402152820.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210402152820.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210402154645.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210402154645.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210402155246.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210402155246.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210402162157.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210402162157.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210403140754.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210403140754.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210403141344.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210403141344.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210403142046.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210403142046.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210403143207.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210403143207.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210403144754.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210403144754.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210403150555.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210403150555.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210403150726.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210403150726.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210403151146.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210403151146.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210403151424.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210403151424.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210408165620.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210408165620.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210408165722.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210408165722.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210408165744.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210408165744.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210408165833.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210408165833.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210410161017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210410161017.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210410161051.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210410161051.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210413145227.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210413145227.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210413145921.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210413145921.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210413150208.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210413150208.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210414175943.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210414175943.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210414182140.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210414182140.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210421133609.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210421133609.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210421133938.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210421133938.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20210422092407.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20210422092407.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20220121103800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20220121103800.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20220127142536.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20220127142536.png -------------------------------------------------------------------------------- /docs/files/Pasted image 20220127142903.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Pasted image 20220127142903.png -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-01-02 at 14.19.42.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-01-02 at 14.19.42.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-01-02 at 15.01.02.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-01-02 at 15.01.02.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-01-02 at 15.05.43.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-01-02 at 15.05.43.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-01-02 at 15.09.51.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-01-02 at 15.09.51.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-01-02 at 15.14.06.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-01-02 at 15.14.06.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-01-02 at 15.21.34.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-01-02 at 15.21.34.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-01-02 at 15.24.43.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-01-02 at 15.24.43.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-01-02 at 15.32.58 1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-01-02 at 15.32.58 1.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-01-02 at 15.32.58.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-01-02 at 15.32.58.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-01-02 at 15.38.11.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-01-02 at 15.38.11.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-01-02 at 15.43.17.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-01-02 at 15.43.17.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-04-02 at 16.48.29.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-04-02 at 16.48.29.mov -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-04-03 at 13.58.20.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-04-03 at 13.58.20.mov -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-04-03 at 13.58.20.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-04-03 at 13.58.20.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-04-03 at 14.59.13.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-04-03 at 14.59.13.mp4 -------------------------------------------------------------------------------- /docs/files/Screen Recording 2021-04-14 at 17.29.09.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screen Recording 2021-04-14 at 17.29.09.mp4 -------------------------------------------------------------------------------- /docs/files/Screenshot 2021-04-03 at 15.01.44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/Screenshot 2021-04-03 at 15.01.44.png -------------------------------------------------------------------------------- /docs/files/bloom_screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/bloom_screenshot.jpg -------------------------------------------------------------------------------- /docs/files/browser_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/browser_screenshot.png -------------------------------------------------------------------------------- /docs/files/code_fence.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/code_fence.gif -------------------------------------------------------------------------------- /docs/files/context_iframe.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/context_iframe.gif -------------------------------------------------------------------------------- /docs/files/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/demo.gif -------------------------------------------------------------------------------- /docs/files/graphxr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/graphxr.gif -------------------------------------------------------------------------------- /docs/files/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/img.png -------------------------------------------------------------------------------- /docs/files/juggl trailer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/juggl trailer.gif -------------------------------------------------------------------------------- /docs/files/juggl_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/juggl_screenshot.png -------------------------------------------------------------------------------- /docs/files/layouts.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/layouts.gif -------------------------------------------------------------------------------- /docs/files/main.db.json: -------------------------------------------------------------------------------- 1 | {"creationTime":1613388819658,"updateTime":1613388819665,"id":"files","notes":[],"links":[],"idCounter":0,"screenPosition":{"translateX":200,"translateY":200,"scale":1},"initialNodePosition":{"x":0,"y":0},"pinnedNotes":[]} -------------------------------------------------------------------------------- /docs/files/obsidian neo4j plugin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/obsidian neo4j plugin.gif -------------------------------------------------------------------------------- /docs/files/save_workspace.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/save_workspace.gif -------------------------------------------------------------------------------- /docs/files/selectdrag.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/selectdrag.mp4 -------------------------------------------------------------------------------- /docs/files/style_pane.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/style_pane.gif -------------------------------------------------------------------------------- /docs/files/styled_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/docs/files/styled_screenshot.png -------------------------------------------------------------------------------- /docs/templates/Template.md: -------------------------------------------------------------------------------- 1 | --- 2 | aliases: [] 3 | --- 4 | 5 | --- 6 | 7 | - author [[Emile van Krieken]] -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "juggl", 3 | "name": "Juggl", 4 | "version": "1.5.0", 5 | "minAppVersion": "1.4.16", 6 | "description": "Adds a completely interactive, stylable and expandable graph view to Obsidian.", 7 | "author": "Emile", 8 | "authorUrl": "https://emilevankrieken.com", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "juggl", 3 | "version": "1.5.0", 4 | "description": "Adds a completely interactive, stylable and expandable graph view to Obsidian.", 5 | "main": "main.js", 6 | "type": "module", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/HEmile/juggl.git" 10 | }, 11 | "scripts": { 12 | "dev": "node_modules/.bin/rollup --config rollup.config.js -w", 13 | "build": "node_modules/.bin/rollup --config rollup.config.js", 14 | "release": "node_modules/.bin/standard-version" 15 | }, 16 | "standard-version": { 17 | "t": "" 18 | }, 19 | "keywords": [], 20 | "author": "", 21 | "devDependencies": { 22 | "@egjs/hammerjs": "^2.0.17", 23 | "@rollup/plugin-commonjs": "^25.0.7", 24 | "@rollup/plugin-json": "^6.0.1", 25 | "@rollup/plugin-node-resolve": "^15.2.3", 26 | "@rollup/plugin-terser": "^0.4.4", 27 | "@rollup/plugin-typescript": "^11.1.5", 28 | "@tsconfig/svelte": "^5.0.2", 29 | "@types/cytoscape": "^3.19.14", 30 | "@types/jquery": "^3.5.25", 31 | "@types/node": "^20.8.9", 32 | "@typescript-eslint/eslint-plugin": "^6.9.0", 33 | "@typescript-eslint/parser": "^6.9.0", 34 | "component-emitter": "^1.3.0", 35 | "eslint": "^8.52.0", 36 | "eslint-config-google": "^0.14.0", 37 | "eslint-config-standard": "^17.1.0", 38 | "eslint-plugin-import": "^2.29.0", 39 | "eslint-plugin-node": "^11.1.0", 40 | "eslint-plugin-promise": "^6.1.1", 41 | "hammerjs": "^2.0.8", 42 | "keycharm": "^0.4.0", 43 | "moment": "^2.29.4", 44 | "obsidian": "^1.4.11", 45 | "rollup": "^4.0.0", 46 | "rollup-plugin-copy": "^3.5.0", 47 | "rollup-plugin-ignore": "^1.0.10", 48 | "rollup-plugin-svelte": "7.1.6", 49 | "svelte": "^4.2.2", 50 | "svelte-check": "^3.5.2", 51 | "svelte-preprocess": "5.0.4", 52 | "timsort": "^0.3.0", 53 | "tslib": "^2.6.2", 54 | "typescript": "^5.2.2", 55 | "uuid": "^9.0.1" 56 | }, 57 | "dependencies": { 58 | "@mdi/js": "^7.3.67", 59 | "cytoscape": "^3.26.0", 60 | "cytoscape-avsdf": "^1.0.0", 61 | "cytoscape-cola": "^2.5.1", 62 | "cytoscape-cose-bilkent": "^4.1.0", 63 | "cytoscape-cxtmenu": "https://github.com/HEmile/cytoscape.js-cxtmenu/tarball/master", 64 | "cytoscape-d3-force": "^1.1.4", 65 | "cytoscape-dagre": "github:HEmile/cytoscape.js-dagre", 66 | "cytoscape-dblclick": "^0.3.1", 67 | "cytoscape-navigator": "^2.0.2", 68 | "cytoscape-popper": "^2.0.0", 69 | "juggl-api": "github:HEmile/juggl-api", 70 | "search-query-parser": "^1.6.0", 71 | "standard-version": "^9.5.0" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import typescript from '@rollup/plugin-typescript'; 3 | import {nodeResolve} from '@rollup/plugin-node-resolve'; 4 | import commonjs from '@rollup/plugin-commonjs'; 5 | import copy from 'rollup-plugin-copy'; 6 | import autoPreprocess from 'svelte-preprocess'; 7 | import ignore from 'rollup-plugin-ignore'; 8 | import json from '@rollup/plugin-json'; 9 | import {env} from 'process'; 10 | import terser from "@rollup/plugin-terser"; 11 | 12 | const plugins = [ 13 | ignore(["path", "url"], { commonjsBugFix: true }), 14 | commonjs({ 15 | include: ['node_modules/**', '../cytoscape.js-cxtmenu/**'], 16 | }), 17 | json(), 18 | svelte({ 19 | emitCss: false, 20 | preprocess: autoPreprocess(), 21 | }), 22 | typescript({sourceMap: false}), 23 | nodeResolve({browser: true, 24 | dedupe: ['svelte'], 25 | })] 26 | 27 | const DEV = env.npm_lifecycle_event === "dev"; 28 | 29 | if (!DEV) { 30 | plugins.push(terser()); 31 | } 32 | 33 | plugins.push(copy({ 34 | targets: [ 35 | {src: 'main.js', dest: 'docs/.obsidian/plugins/juggl'}, 36 | {src: 'styles.css', dest: 'docs/.obsidian/plugins/juggl'}, 37 | ], 38 | hook: 'writeBundle', 39 | })); 40 | 41 | export default { 42 | input: 'src/main.ts', 43 | output: { 44 | dir: '.', 45 | sourcemap: DEV ? 'inline' : false, 46 | format: 'cjs', 47 | exports: 'default', 48 | // banner: '/* This file is bundled with rollup. For the source code, see Github */', 49 | }, 50 | external: ['obsidian'], 51 | plugins: plugins, 52 | }; 53 | -------------------------------------------------------------------------------- /src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'es2015': true, 5 | }, 6 | 'extends': [ 7 | 'google', 8 | ], 9 | 'parser': '@typescript-eslint/parser', 10 | 'parserOptions': { 11 | 'ecmaVersion': 12, 12 | 'sourceType': 'module', 13 | }, 14 | 'plugins': [ 15 | '@typescript-eslint', 16 | ], 17 | 'rules': { 18 | }, 19 | 'ignorePatterns': [ 20 | 'main.js', 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij 2 | *.iml 3 | .idea 4 | 5 | # npm 6 | node_modules 7 | 8 | # build 9 | *.js.map -------------------------------------------------------------------------------- /src/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.1.3](https://github.com/HEmile/juggl/compare/v1.1.2...v1.1.3) (2022-01-11) 6 | 7 | ### [1.1.2](https://github.com/HEmile/juggl/compare/v1.0.2...v1.1.2) (2022-01-11) 8 | 9 | ### 1.0.2 (2022-01-11) 10 | 11 | 12 | ### Bug Fixes 13 | 14 | * Raw filter queries were broken ([1a5c1ab](https://github.com/HEmile/juggl/commit/1a5c1abf6444f31d3c135614d5cf43ae34c94373)) 15 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import type {Vault} from 'obsidian'; 2 | 3 | export const CLASS_PINNED = 'pinned'; 4 | export const CLASS_EXPANDED = 'expanded'; 5 | export const CLASS_ACTIVE_NODE = 'active-node'; 6 | export const CLASS_INACTIVE_NODE = 'inactive-node'; 7 | export const CLASS_CONNECTED_ACTIVE_NODE = 'connected-active-node'; 8 | export const CLASS_HOVER = 'hover'; 9 | export const CLASS_UNHOVER = 'unhover'; 10 | export const CLASS_PROTECTED = 'protected'; 11 | export const CLASS_CONNECTED_HOVER = 'connected-hover'; 12 | export const CLASS_FILTERED = 'filtered'; 13 | export const CLASS_HARD_FILTERED = 'hard-filtered'; 14 | export const CLASSES = [CLASS_PINNED, CLASS_EXPANDED, CLASS_ACTIVE_NODE, 15 | CLASS_INACTIVE_NODE, CLASS_CONNECTED_ACTIVE_NODE, CLASS_HOVER, CLASS_UNHOVER, 16 | CLASS_CONNECTED_HOVER, CLASS_PROTECTED, CLASS_FILTERED, CLASS_HARD_FILTERED]; 17 | 18 | 19 | export const VIEWPORT_ANIMATION_TIME = 250; 20 | export const LAYOUT_ANIMATION_TIME = 1500; 21 | export const DISCRETE_LAYOUT_ANIMATION_TIME = 500; 22 | export const DISCRETE_SPACING_FACTOR = 0.5; 23 | export const DEBOUNCE_FOLLOW = 500; 24 | export const DEBOUNCE_LAYOUT = 2300; 25 | 26 | export const MIN_NODE_SIZE = 5; 27 | export const MAX_NODE_SIZE = 35; 28 | export const MIN_FONT_SIZE = 5; 29 | export const MAX_FONT_SIZE = 11; 30 | export const MIN_TEXT_WIDTH = 65; 31 | export const MAX_TEXT_WIDTH = 100; 32 | 33 | 34 | export const DATA_FOLDER = function(vault: Vault) { 35 | return `${vault.configDir}/plugins/juggl/`; 36 | }; 37 | -------------------------------------------------------------------------------- /src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'cytoscape-popper'; 2 | declare module 'cytoscape-navigator'; 3 | declare module 'cytoscape-dagre'; 4 | declare module 'cytoscape-d3-force'; 5 | declare module 'cytoscape-cola'; 6 | declare module 'cytoscape-avsdf'; 7 | declare module 'cytoscape-cxtmenu' 8 | -------------------------------------------------------------------------------- /src/events.ts: -------------------------------------------------------------------------------- 1 | import {EventRef, Events} from 'obsidian'; 2 | 3 | export class DataStoreEvents extends Events { 4 | trigger(name: 'renameNode', oldName: string, newName: string): void; 5 | trigger(name: 'deleteNode', param: string): void; 6 | trigger(name: 'modifyNode', param: string): void; 7 | trigger(name: 'createNode', param: string): void; 8 | trigger(name: string, ...data: any[]): void { 9 | super.trigger(name, ...data); 10 | } 11 | 12 | public on(name: 'renameNode', callback: (oldName: string, newName: string) => any, ctx?: any): EventRef; 13 | public on(name: 'deleteNode', callback: (name: string) => any, ctx?: any): EventRef; 14 | public on(name: 'modifyNode', callback: (name: string) => any, ctx?: any): EventRef; 15 | public on(name: 'createNode', callback: (name: string) => any, ctx?: any): EventRef; 16 | on(name: string, callback: (...data: any[]) => any, ctx?: any): EventRef { 17 | return super.on(name, callback, ctx); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/image-server.ts: -------------------------------------------------------------------------------- 1 | import type {IncomingMessage, Server, ServerResponse} from 'http'; 2 | import {Component, FileSystemAdapter, Notice, Platform, TFile} from 'obsidian'; 3 | import type {IJugglPluginSettings} from './settings'; 4 | import type JugglPlugin from './main'; 5 | 6 | export class ImageServer extends Component { 7 | settings: IJugglPluginSettings; 8 | plugin: JugglPlugin; 9 | imgServer: Server; 10 | 11 | constructor(plugin: JugglPlugin) { 12 | super(); 13 | this.settings = plugin.settings; 14 | this.plugin = plugin; 15 | this.imgServer = null; 16 | } 17 | 18 | public async onload() { 19 | super.onload(); 20 | if (Platform.isMobile || !this.settings.useImgServer) { 21 | return; 22 | } 23 | const path = require('path'); 24 | const http = require('http'); 25 | const fs = require('fs'); 26 | 27 | let dir:string = null; 28 | try { 29 | dir = path.join(this.plugin.path); 30 | } catch (e) { 31 | console.log('Couldn\'t start image server. This is likely because we\'re on mobile!'); 32 | console.log('Alternatively, windows might block it using the firewall'); 33 | console.log(e); 34 | return; 35 | } 36 | 37 | const mime = { 38 | gif: 'image/gif', 39 | jpg: 'image/jpeg', 40 | jpeg: 'image/jpeg', 41 | png: 'image/png', 42 | svg: 'image/svg+xml', 43 | }; 44 | const settings = this.settings; 45 | const vault = this.plugin.app.vault; 46 | this.imgServer = http.createServer(function(req: IncomingMessage, res: ServerResponse) { 47 | const reqpath = req.url.toString().split('?')[0]; 48 | if (req.method !== 'GET') { 49 | res.statusCode = 501; 50 | res.setHeader('Content-Type', 'text/plain'); 51 | return res.end('Method not implemented'); 52 | } 53 | let file = path.join(dir, decodeURI(reqpath.replace(/\/$/, '/index.html'))); 54 | file = (vault.adapter as FileSystemAdapter).getFullPath(file); 55 | // console.log(vault.getResourcePath(nFile as TFile)); 56 | if (settings.debug) { 57 | console.log('entering server query'); 58 | console.log(req); 59 | console.log(file); 60 | } 61 | // if (file.indexOf(dir + path.sep) !== 0) { 62 | // res.statusCode = 403; 63 | // res.setHeader('Content-Type', 'text/plain'); 64 | // return res.end('Forbidden'); 65 | // } 66 | // @ts-ignore 67 | const type = mime[path.extname(file).slice(1)]; 68 | const s = fs.createReadStream(file); 69 | s.on('open', function() { 70 | res.setHeader('Content-Type', type); 71 | res.setHeader('Access-Control-Allow-Origin', '*'); 72 | s.pipe(res); 73 | }); 74 | s.on('error', function() { 75 | console.log('Here3'); 76 | res.setHeader('Content-Type', 'text/plain'); 77 | res.statusCode = 404; 78 | res.end('Not found'); 79 | }); 80 | }); 81 | try { 82 | const port = this.settings.imgServerPort; 83 | this.imgServer.listen(port, function() { 84 | console.log('Image server listening on http://localhost:' + port + '/'); 85 | }); 86 | } catch (e) { 87 | console.log(e); 88 | new Notice('Juggl: Couldn\'t start image server, see console'); 89 | } 90 | } 91 | 92 | public async onunload() { 93 | super.onunload(); 94 | if (this.imgServer) { 95 | this.imgServer.close(); 96 | this.imgServer = null; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | 2 | // From https://github.com/jplattel/obsidian-query-language/blob/4840e5da6ff5d94dfcba8ff6c900421300466de6/src/search.ts#L3 3 | export interface IFuseFile { 4 | [index:string]: any; 5 | title: string; 6 | path: string; 7 | content: string; 8 | created: any; 9 | modified: any; 10 | tags?: string[]; 11 | frontmatter?: any; 12 | } 13 | -------------------------------------------------------------------------------- /src/obsidian-store.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CachedMetadata, 3 | Component, FrontmatterLinkCache, 4 | getLinkpath, 5 | iterateCacheRefs, 6 | MetadataCache, Reference, ReferenceCache, 7 | TFile, 8 | Vault, 9 | } from 'obsidian'; 10 | import type {ICoreDataStore, IMergedToGraph, IJuggl} from 'juggl-api'; 11 | import {DataStoreEvents} from './events'; 12 | import type JugglPlugin from './main'; 13 | import type { 14 | NodeDefinition, 15 | EdgeDefinition, 16 | NodeCollection, EdgeDataDefinition, 17 | } from 'cytoscape'; 18 | import {CLASS_EXPANDED} from './constants'; 19 | import {nodeDangling, nodeFromFile, parseRefCache, VizId} from 'juggl-api'; 20 | 21 | export const OBSIDIAN_STORE_NAME = 'Obsidian'; 22 | 23 | export class ObsidianStore extends Component implements ICoreDataStore { 24 | plugin: JugglPlugin; 25 | events: DataStoreEvents; 26 | metadata: MetadataCache; 27 | vault: Vault 28 | constructor(plugin: JugglPlugin) { 29 | super(); 30 | this.plugin = plugin; 31 | this.events = new DataStoreEvents(); 32 | this.metadata = plugin.app.metadataCache; 33 | this.vault = plugin.app.vault; 34 | } 35 | 36 | getEvents(view: IJuggl): DataStoreEvents { 37 | return this.events; 38 | } 39 | 40 | async createEdges(srcFile: TFile, srcId: string, toNodes: NodeCollection, view: IJuggl): Promise { 41 | if (!(srcFile.extension === 'md')) { 42 | return []; 43 | } 44 | const cache = this.metadata.getFileCache(srcFile); 45 | if (!cache) { 46 | return []; 47 | } 48 | 49 | const edges: Record = {}; 50 | const content = (await this.vault.cachedRead(srcFile)).split('\n'); 51 | this.iterLinks(cache, (ref, isRefCache) => { 52 | // Iterate over all links (both in frontmatter and document) 53 | const otherId = this.getOtherId(ref, srcFile.path).toId(); 54 | if (toNodes.$id(otherId).length > 0) { 55 | const edgeId = `${srcId}->${otherId}`; 56 | const count = edgeId in edges ? edges[edgeId].length + 1 : 1; 57 | const id = `${edgeId}${count}` 58 | let edge; 59 | if (isRefCache) { 60 | // Add edges for the links appearing in the document 61 | edge = parseRefCache(ref as ReferenceCache, content, id, srcId, otherId, this.plugin.settings.typedLinkPrefix); 62 | } 63 | else { 64 | // Add typed edges for the links appearing in the frontmatter 65 | // TODO: Probably worth including line number etc. 66 | const link = ref as FrontmatterLinkCache; 67 | const split = link.key.split(".") 68 | let type; 69 | if (split.length > 1) 70 | type = split.slice(0, -1).join(); 71 | else 72 | type = link.key; 73 | edge = { 74 | group: 'edges', 75 | data: { 76 | id, 77 | source: srcId, 78 | target: otherId, 79 | context: "", 80 | edgeCount: 1, 81 | type 82 | } as EdgeDataDefinition, 83 | classes: [type, "type-" + type, "type-" + type.replaceAll(" ", "-")] 84 | } as EdgeDefinition; 85 | } 86 | if (edgeId in edges) { 87 | edges[edgeId].push(edge); 88 | } else { 89 | edges[edgeId] = [edge]; 90 | } 91 | } 92 | }); 93 | if (view.settings.mergeEdges) { 94 | // Merges inline edges. 95 | const returnEdges: EdgeDefinition[] = []; 96 | for (const edgeId of Object.keys(edges)) { 97 | const connectedEdges: EdgeDefinition[] = edges[edgeId]; 98 | let inlineEdge: EdgeDefinition = null; 99 | let countInline = 0; 100 | for (const edge of connectedEdges) { 101 | if (edge.classes === ' inline') { 102 | if (inlineEdge) { 103 | inlineEdge.data.context += ` 104 | 105 | --- 106 | 107 | ${edge.data.context}`; 108 | countInline += 1; 109 | } else { 110 | inlineEdge = edge; 111 | countInline = 1; 112 | } 113 | } else { 114 | returnEdges.push(edge); 115 | } 116 | } 117 | if (inlineEdge) { 118 | inlineEdge.data.edgeCount = countInline; 119 | returnEdges.push(inlineEdge); 120 | } 121 | } 122 | return returnEdges; 123 | } 124 | return [].concat(...Object.values(edges)); 125 | } 126 | 127 | async connectNodes(allNodes: NodeCollection, newNodes: NodeCollection, view: IJuggl): Promise { 128 | const edges: EdgeDefinition[] = []; 129 | // Find edges from newNodes to other nodes 130 | // @ts-ignore 131 | for (const node of newNodes) { 132 | const id = VizId.fromNode(node); 133 | if (id.storeId === this.storeId()) { 134 | const file = this.getFile(id); 135 | if (file) { 136 | const srcId = id.toId(); 137 | 138 | edges.push(...await this.createEdges(file, srcId, allNodes, view)); 139 | } 140 | } 141 | } 142 | // @ts-ignore 143 | for (const node of allNodes.difference(newNodes)) { 144 | // For all nodes other than the new nodes 145 | const id = VizId.fromNode(node); 146 | if (id.storeId === this.storeId()) { 147 | const file = this.getFile(id); 148 | if (file) { 149 | const srcId = id.toId(); 150 | 151 | // Connect only to newNodes! 152 | edges.push(...await this.createEdges(file, srcId, newNodes, view)); 153 | } 154 | } 155 | } 156 | return edges; 157 | } 158 | 159 | getOtherId(link: Reference, sourcePath: string) : VizId { 160 | const path = getLinkpath(link.link); 161 | const file = this.metadata.getFirstLinkpathDest(path, sourcePath); 162 | if (file) { 163 | return new VizId(file.name, this.storeId()); 164 | } else { 165 | return new VizId(path, this.storeId() ); 166 | } 167 | } 168 | 169 | async getNodeFromLink(link: Reference, sourcePath: string, graph: IJuggl) : Promise { 170 | const path = getLinkpath(link.link); 171 | const file = this.metadata.getFirstLinkpathDest(path, sourcePath); 172 | if (file) { 173 | return await nodeFromFile(file, this.plugin, graph.settings); 174 | } else { 175 | return nodeDangling(path); 176 | } 177 | } 178 | 179 | getFile(nodeId: VizId): TFile | null { 180 | return this.metadata.getFirstLinkpathDest(nodeId.id, ''); 181 | } 182 | 183 | async fillWithBacklinks(nodes: Record, nodeId: VizId, graph: IJuggl) { 184 | // Could be an expensive operation... No cached backlinks implementation is available in the Obsidian API though. 185 | if (nodeId.storeId === 'core') { 186 | const file = this.getFile(nodeId); 187 | if (!file) { 188 | console.log("Couldn't get file when filling with backlinks. This should not happen."); 189 | return; 190 | } 191 | const path = file.path; 192 | const resolvedLinks = this.metadata.resolvedLinks; 193 | for (const otherPath of Object.keys(resolvedLinks)) { 194 | if (path in resolvedLinks[otherPath]) { 195 | const file = this.vault.getAbstractFileByPath(otherPath) as TFile; 196 | const id = VizId.fromFile(file).toId(); 197 | if (!(id in nodes)) { 198 | nodes[id] = await nodeFromFile(file, this.plugin, graph.settings); 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | 206 | iterLinks(cache: CachedMetadata, cb: (ref: Reference, refCache: boolean) => void): void { 207 | iterateCacheRefs(cache, (ref_) => cb(ref_, true)); 208 | if (cache.frontmatterLinks) { 209 | for (const link of cache.frontmatterLinks) { 210 | cb(link, false); 211 | } 212 | } 213 | } 214 | 215 | 216 | async getNeighbourhood(nodeIds: VizId[], viz: IJuggl): Promise { 217 | const nodes: Record = {}; 218 | for (const nodeId of nodeIds) { 219 | if (nodeId.storeId === this.storeId()) { 220 | const file = this.getFile(nodeId); 221 | if (file === null) { 222 | continue; 223 | } 224 | const cache = this.metadata.getFileCache(file); 225 | if (cache === null) { 226 | continue; 227 | } 228 | if (!(nodeId.toId() in nodes)) { 229 | nodes[nodeId.toId()] = await nodeFromFile(file, this.plugin, viz.settings); 230 | } 231 | const promiseNodes: Record> = {}; 232 | this.iterLinks(cache, (ref, _) => { 233 | const id = this.getOtherId(ref, file.path).toId(); 234 | if (!(id in nodes)) { 235 | promiseNodes[id] = this.getNodeFromLink(ref, file.path, viz); 236 | } 237 | }); 238 | for (const id of Object.keys(promiseNodes)) { 239 | if (!(id in nodes)) { 240 | nodes[id] = await promiseNodes[id]; 241 | } 242 | } 243 | await this.fillWithBacklinks(nodes, nodeId, viz); 244 | } 245 | } 246 | return Object.values(nodes); 247 | } 248 | 249 | storeId(): string { 250 | return 'core'; 251 | } 252 | 253 | get(nodeId: VizId, view: IJuggl): Promise { 254 | const file = this.getFile(nodeId); 255 | if (file === null) { 256 | return Promise.resolve(null); 257 | } 258 | const cache = this.metadata.getFileCache(file); 259 | if (cache === null) { 260 | console.log('returning empty cache', nodeId, view); 261 | return Promise.resolve(null); 262 | } 263 | return Promise.resolve(nodeFromFile(file, this.plugin, view.settings)); 264 | } 265 | 266 | async refreshNode(id: VizId, view: IJuggl) { 267 | const idS = id.toId(); 268 | let correctEdges: IMergedToGraph; 269 | let node = view.viz.$id(idS); 270 | if (this.getFile(id) === null) { 271 | // File does not exist 272 | if (node) { 273 | // If a node exists for this file, remove it. 274 | node.remove(); 275 | view.onGraphChanged(true, true); 276 | } 277 | return; 278 | } 279 | if (node.length > 0 && node.hasClass(CLASS_EXPANDED)) { 280 | correctEdges = await view.expand(node, true, false); 281 | } else { 282 | const nodeDef = await this.get(id, view); 283 | if (!nodeDef) { 284 | console.log("Failed to get node definition on refresh. This should not happen!"); 285 | return; 286 | } 287 | view.mergeToGraph([nodeDef], true, false); 288 | node = view.viz.$id(idS); 289 | const edges = await view.buildEdges(node); 290 | correctEdges = view.mergeToGraph(edges, true, false); 291 | } 292 | // Remove outgoing edges that no longer exist. 293 | const removed = node.connectedEdges() 294 | .difference(correctEdges.merged) 295 | .remove(); 296 | if (removed.length > 0 || correctEdges.added.length > 0) { 297 | view.onGraphChanged(true, true); 298 | } 299 | } 300 | 301 | onload() { 302 | super.onload(); 303 | const store = this; 304 | this.registerEvent( 305 | this.metadata.on('changed', (file) => { 306 | store.plugin.activeGraphs().forEach(async (v) => { 307 | await store.refreshNode(VizId.fromFile(file), v); 308 | }); 309 | })); 310 | this.registerEvent( 311 | this.vault.on('rename', (file, oldPath) => { 312 | if (file instanceof TFile) { 313 | const id = VizId.fromFile(file); 314 | const oldId = VizId.fromPath(oldPath); 315 | store.plugin.activeGraphs().forEach(async (v) => { 316 | setTimeout(async ()=> { 317 | // Changing the ID of a node in Cytoscape is not allowed, so remove and then restore. 318 | // Put in setTimeout because Obsidian doesn't immediately update the metadata on rename... 319 | v.viz.$id(oldId.toId()).remove(); 320 | await store.refreshNode(id, v); 321 | }, 500); 322 | }); 323 | } 324 | })); 325 | this.registerEvent( 326 | this.vault.on('delete', (file) => { 327 | if (file instanceof TFile) { 328 | store.plugin.activeGraphs().forEach((v) => { 329 | v.viz.$id(VizId.fromFile(file).toId()).remove(); 330 | }); 331 | } 332 | })); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/pane/NodesList.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 | {name} 20 |
21 | {#if displayList} 22 | {#each nodes.sort((a, b) => a.data("name").localeCompare(b.data("name"))) as v} 23 |
24 |
25 |
onClickText(v, e)} 26 | on:contextmenu={(e) => ctxmenu(v, e)} 27 | style="color: {v.style('background-color')}"> 28 | {v.data("name")} 29 |
30 | {#if icon} 31 | 36 | {/if} 37 |
38 |
39 | {/each} 40 | {/if} -------------------------------------------------------------------------------- /src/pane/NodesPane.svelte: -------------------------------------------------------------------------------- 1 | 45 | 46 |
47 | 48 | 49 | 51 |
-------------------------------------------------------------------------------- /src/pane/StyleGroups.svelte: -------------------------------------------------------------------------------- 1 | 46 | 47 |
48 | {title} 49 |
50 |
51 | {#each groups as group} 52 | {#if group.showInPane} 53 |
54 |
55 |
56 | 57 | 59 | 60 | 61 |
62 |
63 | 64 | 66 | 67 | 68 |
69 |
70 | 71 |
72 | 73 | 75 | 76 | 77 |
78 |
79 |
80 | 81 |
82 | 87 |
88 |
89 | {#if group.icon.path} 90 | 91 | {/if} 92 |
93 | 102 |
103 |
104 | {Math.round(group.size* 100) / 100} 105 |
106 | 107 |
108 | {:else} 109 |
110 |
111 | 112 | 114 | 115 | 116 |
117 | {group.filter} 118 |
119 |
120 | {/if} 121 | {/each} 122 |
123 | 126 |
127 |
128 | -------------------------------------------------------------------------------- /src/pane/StylePane.svelte: -------------------------------------------------------------------------------- 1 | 41 |
42 | 43 | 44 |
-------------------------------------------------------------------------------- /src/pane/icon-modal.ts: -------------------------------------------------------------------------------- 1 | import {App, FuzzyMatch, FuzzySuggestModal} from 'obsidian'; 2 | import * as mdiIcons from '@mdi/js'; 3 | import type {Icon} from 'juggl-api'; 4 | import {pathToSvg} from '../ui/icons'; 5 | 6 | export class IconModal extends FuzzySuggestModal { 7 | callback: (item: Icon) => any; 8 | color: string; 9 | constructor(app: App, callback: (item: Icon) => any, color: string) { 10 | super(app); 11 | this.callback = callback; 12 | this.resultContainerEl.addClass('juggl-icon-picker'); 13 | this.color = color; 14 | } 15 | getItemText(item: Icon): string { 16 | return item.name; 17 | } 18 | 19 | getItems(): Icon[] { 20 | const icons: Icon[] = [{name: 'No icon', path: '', color: this.color}]; 21 | icons.push(...Object.keys(mdiIcons).map((k) => { 22 | return {name: k.slice(3).replace(/([A-Z])/g, ' $1').trim(), 23 | // @ts-ignore 24 | path: mdiIcons[k] as string, 25 | color: this.color}; 26 | })); 27 | return icons; 28 | } 29 | 30 | onChooseItem(item: Icon, evt: MouseEvent | KeyboardEvent): void { 31 | this.callback(item); 32 | } 33 | 34 | renderSuggestion(item: FuzzyMatch, el: HTMLElement) { 35 | el.empty(); 36 | 37 | const iconHtml = pathToSvg(item.item.path); 38 | // // const renderedResult = el.createEl('span', {cls: ''}); 39 | const innerResult = el.createEl('span', { 40 | cls: 'react-icon ', 41 | }); 42 | innerResult.innerHTML = iconHtml; 43 | // el.createEl('span', { 44 | super.renderSuggestion(item, el); 45 | // cls: '', 46 | // text: item.item.name, 47 | // }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/pane/view.ts: -------------------------------------------------------------------------------- 1 | import {EventRef, ItemView, WorkspaceLeaf} from 'obsidian'; 2 | import type JugglPlugin from '../main'; 3 | import type {Juggl} from '../viz/visualization'; 4 | import type {JugglView} from '../viz/juggl-view'; 5 | import NodesPane from './NodesPane.svelte'; 6 | import StylePane from './StylePane.svelte'; 7 | import {JUGGL_NODES_VIEW_TYPE, JUGGL_STYLE_VIEW_TYPE, JUGGL_VIEW_TYPE} from "juggl-api"; 8 | 9 | export abstract class JugglPane extends ItemView { 10 | plugin: JugglPlugin; 11 | activeViz: Juggl = null; 12 | changeRef: EventRef = null; 13 | constructor(leaf: WorkspaceLeaf, plugin: JugglPlugin) { 14 | super(leaf); 15 | this.plugin = plugin; 16 | const view = this; 17 | this.registerEvent(this.plugin.app.workspace.on('active-leaf-change', (leaf) => { 18 | if (this.changeRef) { 19 | this.activeViz.offref(this.changeRef); 20 | this.changeRef = null; 21 | } 22 | if (leaf) { 23 | if (leaf.view.getViewType() === JUGGL_VIEW_TYPE) { 24 | const activeViz = (leaf.view as JugglView).juggl; 25 | this.changeRef = activeViz.on('elementsChange', () => { 26 | view.onActiveVizChange(); 27 | }); 28 | if (activeViz === this.activeViz) { 29 | return; 30 | } 31 | this.activeViz = activeViz; 32 | } else if (!(leaf.view instanceof JugglPane)) { 33 | this.activeViz = null; 34 | } 35 | } else { 36 | this.activeViz = null; 37 | } 38 | 39 | this.onActiveVizChange(); 40 | })); 41 | } 42 | 43 | abstract onActiveVizChange(): void; 44 | 45 | setViz(viz: Juggl) { 46 | this.activeViz = viz; 47 | this.onActiveVizChange(); 48 | } 49 | } 50 | 51 | export class JugglNodesPane extends JugglPane { 52 | pane: NodesPane; 53 | constructor(leaf: WorkspaceLeaf, plugin: JugglPlugin) { 54 | super(leaf, plugin); 55 | this.icon = 'ag-node-list'; 56 | } 57 | 58 | onload() { 59 | super.onload(); 60 | this.pane = new NodesPane({target: this.contentEl}); 61 | } 62 | 63 | getDisplayText(): string { 64 | return 'Juggl nodes'; 65 | } 66 | 67 | getViewType(): string { 68 | return JUGGL_NODES_VIEW_TYPE; 69 | } 70 | 71 | onActiveVizChange(): void { 72 | if (this.pane) { 73 | this.pane.setViz.bind(this.pane)(this.activeViz); 74 | } 75 | } 76 | } 77 | export class JugglStylePane extends JugglPane { 78 | pane: NodesPane; 79 | constructor(leaf: WorkspaceLeaf, plugin: JugglPlugin) { 80 | super(leaf, plugin); 81 | this.icon = 'ag-style'; 82 | } 83 | 84 | onload() { 85 | super.onload(); 86 | this.pane = new StylePane({target: this.contentEl, props: { 87 | plugin: this.plugin, 88 | }}); 89 | } 90 | 91 | getDisplayText(): string { 92 | return 'Juggl style'; 93 | } 94 | 95 | getViewType(): string { 96 | return JUGGL_STYLE_VIEW_TYPE; 97 | } 98 | 99 | onActiveVizChange(): void { 100 | if (this.pane) { 101 | this.pane.setViz.bind(this.pane)(this.activeViz); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/resources/code_fence.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/src/resources/code_fence.gif -------------------------------------------------------------------------------- /src/resources/juggl_help.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/src/resources/juggl_help.gif -------------------------------------------------------------------------------- /src/resources/juggl_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/src/resources/juggl_screenshot.png -------------------------------------------------------------------------------- /src/resources/juggl_trailer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/src/resources/juggl_trailer.gif -------------------------------------------------------------------------------- /src/resources/layouts.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/src/resources/layouts.gif -------------------------------------------------------------------------------- /src/resources/open_juggl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/src/resources/open_juggl.gif -------------------------------------------------------------------------------- /src/resources/save_workspace.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/src/resources/save_workspace.gif -------------------------------------------------------------------------------- /src/resources/style_pane.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/src/resources/style_pane.gif -------------------------------------------------------------------------------- /src/resources/workspace_mode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HEmile/juggl/4c18c9ed009b8074da704ae3053df607f21e66ed/src/resources/workspace_mode.gif -------------------------------------------------------------------------------- /src/ui/KoFi.svelte: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 13 | 14 |
15 | -------------------------------------------------------------------------------- /src/ui/SaveWorkspaceItem.svelte: -------------------------------------------------------------------------------- 1 | 6 |
7 |
8 | {name} 9 |
10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 |
18 |
-------------------------------------------------------------------------------- /src/ui/SaveWorkspaces.svelte: -------------------------------------------------------------------------------- 1 | 25 | 30 |
31 |
32 | {#each savedGraphs as graphName} 33 | 34 | 35 | {/each} 36 |
-------------------------------------------------------------------------------- /src/ui/help-view.ts: -------------------------------------------------------------------------------- 1 | import {ItemView} from 'obsidian'; 2 | import {JUGGL_HELP_VIEW} from "juggl-api"; 3 | 4 | export class JugglHelpView extends ItemView { 5 | frame: HTMLElement = null; 6 | 7 | async onOpen() { 8 | this.frame = activeDocument.createElement('iframe'); 9 | this.frame.addClass(`juggl-site`); 10 | this.frame.setAttr('style', 'height: 100%; width:100%'); 11 | this.frame.setAttr('src', 'https://juggl.io'); 12 | this.frame.setAttr('tabindex', '0'); 13 | this.containerEl.children[1].appendChild(this.frame); 14 | } 15 | 16 | getDisplayText(): string { 17 | return 'Juggl help'; 18 | } 19 | 20 | getViewType(): string { 21 | return JUGGL_HELP_VIEW; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ui/icons.ts: -------------------------------------------------------------------------------- 1 | // Attribution: https://github.com/Reocin/obsidian-markdown-formatting-assistant-plugin/blob/main/src/icons.ts 2 | import {addIcon} from 'obsidian'; 3 | import * as mdiIcons from '@mdi/js'; 4 | 5 | export function pathToSvg(icon: string) { 6 | return ` 7 | 8 | 9 | `;// 10 | } 11 | export const icons: Record = { 12 | ag_expand: mdiIcons.mdiArrowExpandAll, 13 | ag_collapse: mdiIcons.mdiArrowCollapseAll, 14 | ag_select_all: mdiIcons.mdiSelectAll, 15 | ag_select_inverse: mdiIcons.mdiSelectCompare, 16 | ag_select_neighbors: mdiIcons.mdiSelectGroup, 17 | ag_lock: mdiIcons.mdiLock, 18 | ag_unlock: mdiIcons.mdiLockOpenVariantOutline, 19 | ag_hide: mdiIcons.mdiEyeOff, 20 | ag_unhide: mdiIcons.mdiEye, 21 | ag_fit: mdiIcons.mdiFitToPageOutline, 22 | ag_image: mdiIcons.mdiImage, 23 | ag_workspace: mdiIcons.mdiToolboxOutline, 24 | ag_local: mdiIcons.mdiFlare, 25 | ag_fdgd: mdiIcons.mdiGrain, 26 | ag_concentric: mdiIcons.mdiGraphql, 27 | ag_grid: mdiIcons.mdiDotsGrid, 28 | ag_hierarchy: mdiIcons.mdiGraph, 29 | ag_file: mdiIcons.mdiFileOutline, 30 | ag_filter: mdiIcons.mdiFilterOutline, 31 | ag_save: mdiIcons.mdiContentSave, 32 | ag_node_list: mdiIcons.mdiFormatListBulletedType, 33 | ag_style: mdiIcons.mdiPaletteOutline, 34 | ag_help: mdiIcons.mdiHelp, 35 | }; 36 | 37 | export const addIcons = (): void => { 38 | Object.keys(icons).forEach((key) => { 39 | addIcon(key.replace('_', '-').replace('_', '-'), pathToSvg(icons[key])); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /src/ui/settings/AppearanceSettings.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 |

30 | Appearance 31 |

32 |

33 | You can style the graph with css. This is done in the {stylesheetPath} file. 34 | See this page for help with styling. 35 |

36 | 37 | 40 | -------------------------------------------------------------------------------- /src/ui/settings/GlobalGraphModal.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | Warning: Juggl is not optimized for large graphs. We advise not to use the global graph if your vault 7 | contains more than 250 notes to prevent Obsidian from freezing.
8 | 9 | 10 | -------------------------------------------------------------------------------- /src/ui/settings/global-graph-modal.ts: -------------------------------------------------------------------------------- 1 | import {App, Modal} from 'obsidian'; 2 | import GlobalGraphModal from './GlobalGraphModal.svelte'; 3 | 4 | export class GlobalWarningModal extends Modal { 5 | constructor(app: App, callback: () => any) { 6 | super(app); 7 | const modal = this; 8 | new GlobalGraphModal({target: this.modalEl, props: { 9 | cancelCallback: this.close.bind(modal), 10 | continueCallback: callback, 11 | }}); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ui/sidebar/Sidebar.svelte: -------------------------------------------------------------------------------- 1 | 7 |
8 | {#if hidden} 9 | 17 | 18 | {:else} 19 | 20 | {/if} 21 |
-------------------------------------------------------------------------------- /src/ui/toolbar/HelpButton.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | -------------------------------------------------------------------------------- /src/ui/toolbar/Toolbar.svelte: -------------------------------------------------------------------------------- 1 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | 54 |
55 |
56 | 57 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 66 | 68 | 70 |
71 |
72 | 73 | 75 | 77 |
78 |
79 | 81 | 83 |
84 | 85 |
-------------------------------------------------------------------------------- /src/ui/toolbar/ToolbarButton.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/ui/toolbar/ToolbarLocal.svelte: -------------------------------------------------------------------------------- 1 | 33 | 34 |
35 | 36 | 37 | 38 | 39 |
40 |
41 | 42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
-------------------------------------------------------------------------------- /src/ui/workspace-modal.ts: -------------------------------------------------------------------------------- 1 | import {App, Modal} from 'obsidian'; 2 | import SaveWorkspaces from './SaveWorkspaces.svelte'; 3 | import type {WorkspaceManager} from '../viz/workspaces/workspace-manager'; 4 | import type {Juggl} from '../viz/visualization'; 5 | 6 | export class WorkspaceModal extends Modal { 7 | manager: WorkspaceManager; 8 | view: Juggl; 9 | constructor(app: App, workspaceManager: WorkspaceManager, view: Juggl) { 10 | super(app); 11 | this.manager = workspaceManager; 12 | this.view = view; 13 | } 14 | onOpen() { 15 | super.onOpen(); 16 | this.titleEl.innerHTML = 'Manage workspace graphs'; 17 | new SaveWorkspaces({ 18 | target: this.contentEl, 19 | props: { 20 | onSave: (s:string ) => this.manager.saveGraph(s, this.view), 21 | savedGraphs: this.manager.graphs, 22 | onLoad: (s: string) => this.manager.loadGraph(s, this.view), 23 | onDelete: (s: string) => this.manager.deleteGraph(s, this.view), 24 | }, 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/viz/juggl-view.ts: -------------------------------------------------------------------------------- 1 | import {ItemView, WorkspaceLeaf} from 'obsidian'; 2 | import type JugglPlugin from '../main'; 3 | import {Juggl} from './visualization'; 4 | import type {IDataStore, IJugglStores} from 'juggl-api'; 5 | import type {IJugglSettings} from 'juggl-api'; 6 | import {JUGGL_VIEW_TYPE} from "juggl-api"; 7 | 8 | export class JugglView extends ItemView { 9 | juggl: Juggl; 10 | constructor(leaf: WorkspaceLeaf, settings: IJugglSettings, plugin: JugglPlugin, initialNodes: string[]) { 11 | super(leaf); 12 | // TODO: Maybe make this configurable 13 | leaf.setPinned(true); 14 | const coreStore = plugin.coreStores[settings.coreStore]; 15 | const stores: IJugglStores ={ 16 | dataStores: [coreStore as IDataStore].concat(plugin.stores), 17 | coreStore: coreStore}; 18 | this.juggl = new Juggl(this.containerEl.children[1], plugin, stores, settings, initialNodes); 19 | this.addChild(this.juggl); 20 | } 21 | 22 | getDisplayText(): string { 23 | // TODO: Make this interactive: Either the active workspace or the local graph 24 | return 'Juggl'; 25 | } 26 | 27 | getViewType(): string { 28 | return JUGGL_VIEW_TYPE; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/viz/layout-settings.ts: -------------------------------------------------------------------------------- 1 | import type {Collection, Layouts} from 'cytoscape'; 2 | import { 3 | CLASS_ACTIVE_NODE, CLASS_EXPANDED, 4 | DISCRETE_LAYOUT_ANIMATION_TIME, 5 | DISCRETE_SPACING_FACTOR, 6 | LAYOUT_ANIMATION_TIME, 7 | } from '../constants'; 8 | import type {NodeSingular} from 'cytoscape'; 9 | import type {JugglLayouts, AllLayouts} from '../settings'; 10 | import type {LayoutOptions} from 'cytoscape'; 11 | import type {IJuggl, IJugglSettings, LayoutSettings} from 'juggl-api'; 12 | 13 | 14 | export class ColaGlobalLayout implements LayoutSettings { 15 | static DEFAULT: LayoutOptions = { 16 | name: 'cola', 17 | // @ts-ignore 18 | animate: true, // whether to show the layout as it's running 19 | refresh: 2, // number of ticks per frame; higher is faster but more jerky 20 | maxSimulationTime: LAYOUT_ANIMATION_TIME, // max length in ms to run the layout 21 | ungrabifyWhileSimulating: false, // so you can't drag nodes during layout 22 | fit: false, // on every layout reposition of nodes, fit the viewport 23 | padding: 30, // padding around the simulation 24 | nodeDimensionsIncludeLabels: true, // whether labels should be included in determining the space used by a node 25 | // positioning options 26 | randomize: false, // use random node positions at beginning of layout 27 | avoidOverlap: true, // if true, prevents overlap of node bounding boxes 28 | handleDisconnected: true, // if true, avoids disconnected components from overlapping 29 | convergenceThreshold: 0.01, // when the alpha value (system energy) falls below this value, the layout stops 30 | nodeSpacing: 10, // extra spacing around nodes 31 | }; 32 | options: LayoutOptions; 33 | constructor(options?: LayoutOptions, animate?: boolean) { 34 | this.options = Object.assign({}, ColaGlobalLayout.DEFAULT, options, {animate: animate ? 'end' : false}); 35 | } 36 | 37 | startLayout(collection: Collection): Layouts { 38 | return collection.layout(this.options).start(); 39 | } 40 | } 41 | 42 | export class D3GlobalLayout implements LayoutSettings { 43 | options: LayoutOptions; 44 | constructor(options?: LayoutOptions, animate?: boolean) { 45 | this.options = Object.assign({}, D3GlobalLayout.DEFAULT, options, {animate: animate ? 'end' : false}); 46 | } 47 | startLayout(collection: Collection): Layouts { 48 | return collection.layout(Object.assign(this.options, {linkId: function id(d: any) { 49 | return d.id; 50 | }, // sets the node id accessor to the specified function 51 | })).start(); 52 | } 53 | 54 | static DEFAULT: LayoutOptions = { 55 | name: 'd3-force', 56 | // @ts-ignore 57 | animate: 'end', // whether to show the layout as it's running; special 'end' value makes the layout animate like a discrete layout 58 | maxIterations: 0, // max iterations before the layout will bail out 59 | maxSimulationTime: LAYOUT_ANIMATION_TIME, // max length in ms to run the layout 60 | ungrabifyWhileSimulating: false, // so you can't drag nodes during layout 61 | fixedAfterDragging: false, // fixed node after dragging 62 | fit: false, // on every layout reposition of nodes, fit the viewport 63 | padding: 30, // padding around the simulation 64 | /** d3-force API**/ 65 | alpha: 1, // sets the current alpha to the specified number in the range [0,1] 66 | alphaMin: 0.001, // sets the minimum alpha to the specified number in the range [0,1] 67 | alphaDecay: 1 - Math.pow(0.001, 1 / 300), // sets the alpha decay rate to the specified number in the range [0,1] 68 | alphaTarget: 0, // sets the current target alpha to the specified number in the range [0,1] 69 | velocityDecay: 0.4, // sets the velocity decay factor to the specified number in the range [0,1] 70 | collideRadius: 60, // sets the radius accessor to the specified number or function 71 | collideStrength: 0.9, // sets the force strength to the specified number in the range [0,1] 72 | collideIterations: 1, // sets the number of iterations per application to the specified number 73 | linkDistance: 150, // sets the distance accessor to the specified number or function 74 | linkStrength: 0.7, // sets the strength accessor to the specified number or function. Could do something smart here 75 | linkIterations: 1, // sets the number of iterations per application to the specified number 76 | manyBodyStrength: -600, 77 | manyBodyDistanceMin: 5, 78 | xStrength: 0.1, // sets the strength accessor to the specified number or function 79 | xX: 0, // sets the x-coordinate accessor to the specified number or function 80 | yStrength: 0.1, // sets the strength accessor to the specified number or function 81 | yY: 0, // sets the y-coordinate accessor to the specified number or function 82 | radialStrength: 0.1, 83 | radialX: 0, // sets the x-coordinate of the circle center to the specified number 84 | radialY: 0, // sets the y-coordinate of the circle center to the specified number 85 | radialRadius: 10, 86 | // positioning optsions 87 | randomize: false, // use random node positions at beginning of layout 88 | // infinite layout options 89 | infinite: false, // overrides all other options for a forces-all-the-time mode 90 | 91 | } 92 | } 93 | 94 | export class GridGlobalLayout implements LayoutSettings { 95 | constructor(options?: LayoutOptions) { 96 | this.options = Object.assign({}, GridGlobalLayout.DEFAULT, options); 97 | } 98 | startLayout(collection: Collection): Layouts { 99 | return collection.layout(this.options).start(); 100 | } 101 | 102 | options: LayoutOptions; 103 | static DEFAULT: LayoutOptions = { 104 | name: 'grid', 105 | animate: true, // whether to show the layout as it's running (should this be end? 106 | animationDuration: DISCRETE_LAYOUT_ANIMATION_TIME, 107 | // animationEasing // Should probably add something here 108 | spacingFactor: DISCRETE_SPACING_FACTOR, 109 | fit: false, // on every layout reposition of nodes, fit the viewport 110 | padding: 30, // padding around the simulation 111 | nodeDimensionsIncludeLabels: true, // whether labels should be included in determining the space used by a node 112 | // positioning options 113 | avoidOverlap: true, // if true, prevents overlap of node bounding boxes 114 | } 115 | } 116 | 117 | export class DagreGlobalLayout implements LayoutSettings { 118 | constructor(options?: LayoutOptions) { 119 | this.options = Object.assign({}, DagreGlobalLayout.DEFAULT, options); 120 | } 121 | startLayout(collection: Collection): Layouts { 122 | return collection.layout(this.options).start(); 123 | } 124 | options: cytoscape.LayoutOptions; 125 | static DEFAULT = { 126 | name: 'dagre', 127 | // @ts-ignore 128 | animate: true, // whether to show the layout as it's running (should this be end? 129 | animationDuration: DISCRETE_LAYOUT_ANIMATION_TIME, 130 | spacingFactor: DISCRETE_SPACING_FACTOR, 131 | // animationEasing // Should probably add something here 132 | fit: false, // on every layout reposition of nodes, fit the viewport 133 | padding: 30, // padding around the simulation 134 | nodeDimensionsIncludeLabels: true, // whether labels should be included in determining the space used by a node 135 | // positioning options 136 | avoidOverlap: true, // if true, prevents overlap of node bounding boxes 137 | } 138 | } 139 | 140 | export class AVSDFGlobalLayout implements LayoutSettings { 141 | constructor(options?: LayoutOptions) { 142 | this.options = Object.assign({}, AVSDFGlobalLayout.DEFAULT, options); 143 | } 144 | startLayout(collection: Collection): Layouts { 145 | return collection.layout( this.options).start(); 146 | } 147 | options: cytoscape.LayoutOptions; 148 | static DEFAULT: LayoutOptions = { 149 | name: 'avsdf', 150 | // @ts-ignore 151 | animate: 'end', // whether to show the layout as it's running (should this be end? 152 | animationDuration: DISCRETE_LAYOUT_ANIMATION_TIME, 153 | // animationEasing // Should probably add something here 154 | fit: false, // on every layout reposition of nodes, fit the viewport 155 | padding: 30, // padding around the simulation 156 | nodeDimensionsIncludeLabels: true, // whether labels should be included in determining the space used by a node 157 | // positioning options 158 | avoidOverlap: true, // if true, prevents overlap of node bounding boxes 159 | } 160 | } 161 | 162 | 163 | export class ConcentricLayout implements LayoutSettings { 164 | constructor(options?: LayoutOptions) { 165 | this.options = Object.assign({}, ConcentricLayout.DEFAULT, options); 166 | } 167 | startLayout(collection: Collection): Layouts { 168 | return collection.layout(Object.assign(this.options, {concentric: (n: NodeSingular) =>{ 169 | // @ts-ignore 170 | if (n.hasClass(CLASS_ACTIVE_NODE)) { 171 | return 1000; 172 | } 173 | // @ts-ignore 174 | if (n.hasClass(CLASS_EXPANDED)) { 175 | return 100; 176 | } 177 | return 1; 178 | }})).start(); 179 | } 180 | options: cytoscape.LayoutOptions; 181 | static DEFAULT: LayoutOptions = { 182 | name: 'concentric', 183 | // @ts-ignore 184 | animate: 'end', // whether to show the layout as it's running (should this be end? 185 | animationDuration: DISCRETE_LAYOUT_ANIMATION_TIME, 186 | // animationEasing // Should probably add something here 187 | fit: false, // on every layout reposition of nodes, fit the viewport 188 | padding: 30, // padding around the simulation 189 | nodeDimensionsIncludeLabels: true, // whether labels should be included in determining the space used by a node 190 | // positioning options 191 | avoidOverlap: true, // if true, prevents overlap of node bounding boxes 192 | } 193 | } 194 | 195 | 196 | export const getLayoutSetting = function(layoutType: AllLayouts, settings?: IJugglSettings, options?: LayoutOptions) { 197 | switch (layoutType) { 198 | case 'circle': 199 | case 'concentric': return new ConcentricLayout(options); 200 | case 'force-directed': if (settings && settings.fdgdLayout === 'd3-force') { 201 | return new D3GlobalLayout(options, settings.animateLayout); 202 | } else { 203 | return new ColaGlobalLayout(options, settings.animateLayout); 204 | } 205 | case 'hierarchy': 206 | case 'dagre': 207 | return new DagreGlobalLayout(options); 208 | case 'grid': return new GridGlobalLayout(options); 209 | case 'cola': return new ColaGlobalLayout(options, settings.animateLayout); 210 | case 'd3-force': return new D3GlobalLayout(options, settings.animateLayout); 211 | } 212 | }; 213 | 214 | export const parseLayoutSettings = function(settings: IJugglSettings) { 215 | if (typeof settings.layout === 'string' || settings.layout instanceof String) { 216 | return getLayoutSetting(settings.layout as JugglLayouts, settings ); 217 | } else { 218 | return getLayoutSetting(settings.layout.name as JugglLayouts, settings, settings.layout); 219 | } 220 | }; 221 | -------------------------------------------------------------------------------- /src/viz/local-mode.ts: -------------------------------------------------------------------------------- 1 | import type {IAGMode} from 'juggl-api'; 2 | import type {EventNames, EventObject, NodeSingular} from 'cytoscape'; 3 | import type {Juggl} from './visualization'; 4 | import type {NodeCollection} from 'cytoscape'; 5 | import type {Menu} from 'obsidian'; 6 | import ToolbarLocal from '../ui/toolbar/ToolbarLocal.svelte'; 7 | import {Component, TFile} from 'obsidian'; 8 | import {VizId} from 'juggl-api'; 9 | import { 10 | CLASS_ACTIVE_NODE, 11 | CLASS_CONNECTED_ACTIVE_NODE, 12 | CLASS_INACTIVE_NODE, 13 | } from '../constants'; 14 | import type {Core} from 'cytoscape'; 15 | import type {SvelteComponent} from 'svelte'; 16 | import { 17 | getLayoutSetting, 18 | } from './layout-settings'; 19 | 20 | 21 | class EventRec { 22 | eventName: EventNames; 23 | selector: string; 24 | event: any; 25 | } 26 | 27 | export class LocalMode extends Component implements IAGMode { 28 | view; 29 | viz: Core; 30 | events: EventRec[] = []; 31 | windowEvent: any; 32 | toolbar: SvelteComponent; 33 | depth: number = 1; 34 | maxDepthForCurrentFile: number = 1; 35 | 36 | constructor(view: Juggl) { 37 | super(); 38 | this.view = view; 39 | } 40 | 41 | 42 | onload() { 43 | if (this.view.vizReady) { 44 | this._onLoad(); 45 | } else { 46 | this.registerEvent(this.view.on('vizReady', (viz) => { 47 | this._onLoad(); 48 | })); 49 | } 50 | } 51 | 52 | _onLoad() { 53 | this.viz = this.view.viz; 54 | this.registerCyEvent('tap', 'node', async (e: EventObject) => { 55 | const file = await this.view.plugin.openFileFromNode(e.target, e.originalEvent.metaKey); 56 | if (file) { 57 | await this.onOpenFile(file); 58 | } 59 | }); 60 | 61 | // Register on file open event 62 | this.registerEvent(this.view.workspace.on('file-open', async (file) => { 63 | if (file) { 64 | await this.onOpenFile(file); 65 | } 66 | })); 67 | } 68 | 69 | async onOpenFile(file: TFile) { 70 | if (!this.view.settings.autoAddNodes) { 71 | return; 72 | } 73 | const id = new VizId(file.name, 'core'); 74 | let node: NodeSingular; 75 | this.viz.startBatch(); 76 | if (this.viz.$id(id.toId()).length === 0) { 77 | const nodeDef = await this.view.datastores.coreStore.get(id, this.view); 78 | node = this.viz.add(nodeDef); 79 | } else { 80 | node = this.viz.$id(id.toId()); 81 | } 82 | await this.view.expand(node, false); 83 | node.addClass(CLASS_ACTIVE_NODE); 84 | this.viz.nodes() 85 | .difference(node.closedNeighborhood()) 86 | .remove(); 87 | this.view.onGraphChanged(false); 88 | this.updateActiveFile(node as NodeSingular); 89 | this.viz.endBatch(); 90 | } 91 | 92 | changeDepth(depth: number) { 93 | console.log(`changing depth to ${depth}`); 94 | 95 | } 96 | 97 | registerCyEvent(name: EventNames, selector: string, callback: any) { 98 | this.events.push({eventName: name, selector: selector, event: callback}); 99 | if (selector) { 100 | this.viz.on(name, selector, callback); 101 | } else { 102 | this.viz.on(name, callback); 103 | } 104 | } 105 | 106 | onunload(): void { 107 | for (const listener of this.events) { 108 | if (listener.selector) { 109 | this.viz.off(listener.eventName, listener.selector, listener.event); 110 | } else { 111 | this.viz.off(listener.eventName, listener.event); 112 | } 113 | } 114 | this.events = []; 115 | this.toolbar.$destroy(); 116 | } 117 | 118 | getName(): string { 119 | return 'local'; 120 | } 121 | 122 | fillMenu(menu: Menu, nodes: NodeCollection): void { 123 | 124 | } 125 | 126 | createToolbar(element: Element) { 127 | const view = this.view; 128 | this.toolbar = new ToolbarLocal({ 129 | target: element, 130 | props: { 131 | viz: this.viz, 132 | fitClick: this.view.fitView.bind(view), 133 | fdgdClick: () => this.view.setLayout(getLayoutSetting('force-directed', this.view.settings)), 134 | concentricClick: () => this.view.setLayout(getLayoutSetting('circle')), 135 | gridClick: () => this.view.setLayout(getLayoutSetting('grid')), 136 | hierarchyClick: () => this.view.setLayout(getLayoutSetting('hierarchy')), 137 | workspaceModeClick: () => view.setMode('workspace'), 138 | filterInput: (handler: InputEvent) => { 139 | // @ts-ignore 140 | this.view.searchFilter(handler.target.value); 141 | this.view.restartLayout(); 142 | }, 143 | onDepthChange: this.changeDepth, 144 | filterValue: this.view.settings.filter, 145 | workspace: this.view.plugin.app.workspace, 146 | }, 147 | }); 148 | } 149 | 150 | updateActiveFile(node: NodeCollection) { 151 | this.viz.elements() 152 | .removeClass([CLASS_CONNECTED_ACTIVE_NODE, CLASS_ACTIVE_NODE, CLASS_INACTIVE_NODE]) 153 | .difference(node.closedNeighborhood()) 154 | .addClass(CLASS_INACTIVE_NODE); 155 | node.addClass(CLASS_ACTIVE_NODE); 156 | node.connectedEdges() 157 | .addClass(CLASS_CONNECTED_ACTIVE_NODE) 158 | .connectedNodes() 159 | .addClass(CLASS_CONNECTED_ACTIVE_NODE) 160 | .union(node); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/viz/query-builder.ts: -------------------------------------------------------------------------------- 1 | import type {NodeCollection} from 'cytoscape'; 2 | import searchQuery, {ISearchParserDictionary} from 'search-query-parser'; 3 | import cytoscape, {NodeSingular} from 'cytoscape'; 4 | 5 | 6 | const _containsSelector = function(attribute: string, filters: string|string[], op='*='): string[] { 7 | if (typeof(filters) === 'string' || filters instanceof String) { 8 | return [`[${attribute} ${op} '${filters}']`]; 9 | } 10 | return filters.map((s) => `[${attribute} ${op} '${s}']`); 11 | }; 12 | 13 | const _tagSelector = function(tag: string|string[]): string[] { 14 | if (typeof(tag) === 'string' || tag instanceof String) { 15 | if (tag.length > 0 && tag[0] === '#') { 16 | const t = tag.slice(1); 17 | // @ts-ignore 18 | return [`.tag-${tag.slice(1).replaceAll('/', '-')}`]; 19 | } 20 | return []; 21 | } 22 | return tag.map((t) => { 23 | if (t.length > 0 && t[0] === '#') { 24 | // @ts-ignore 25 | return `.tag-${t.slice(1).replaceAll('/', '-')}`; 26 | } 27 | return ''; 28 | }); 29 | }; 30 | 31 | const _classSelector = function(clazz: string|string[]): string[] { 32 | if (typeof(clazz) === 'string' || clazz instanceof String) { 33 | return [`.${clazz}`]; 34 | } 35 | return clazz.map(( c) => `.${c}`); 36 | }; 37 | 38 | 39 | const literal = function(atomicQuery: ISearchParserDictionary, key: string): string[] { 40 | switch (key) { 41 | case 'exclude': return []; 42 | case 'content': 43 | case 'ignore-case': 44 | case 'text': return _containsSelector('content', atomicQuery[key], '@*='); 45 | case 'match-case': 46 | return _containsSelector('content', atomicQuery[key], '*='); 47 | case 'file': return _containsSelector('name', atomicQuery[key]); 48 | case 'name': return _containsSelector('name', atomicQuery[key], '@*='); 49 | case 'tag': return _tagSelector(atomicQuery[key]); 50 | case 'class': return _classSelector(atomicQuery[key]); 51 | case 'raw': return [atomicQuery[key]]; 52 | default: return _containsSelector(key, atomicQuery[key]); 53 | } 54 | }; 55 | 56 | 57 | const _parseAtomicQuery = function(query: string, nodes: NodeCollection): NodeCollection { 58 | const keys:Set = new Set(); 59 | nodes.forEach((node:NodeSingular) => { 60 | Object.keys(node.data()).forEach((key) => keys.add(key)); 61 | }); 62 | const keywords = ['file', 'tag', 'raw', 'match-case', 'ignore-case', 'class']; 63 | keywords.push(...keys); 64 | const options = { 65 | keywords: keywords, 66 | tokenize: true, 67 | offsets: false, 68 | }; 69 | const parsedQuery = searchQuery.parse(query, options) as ISearchParserDictionary; 70 | let selector = 'node'; 71 | for (const key of Object.keys(parsedQuery)) { 72 | selector += literal(parsedQuery, key).join(); 73 | } 74 | let filteredNodes = nodes.filter(selector); 75 | if (parsedQuery.exclude) { 76 | for (const key of Object.keys(parsedQuery.exclude)) { 77 | for (const query of literal(parsedQuery.exclude, key)) { 78 | const selector = 'node' + query; 79 | filteredNodes = filteredNodes.not(selector); 80 | } 81 | } 82 | } 83 | return filteredNodes; 84 | }; 85 | 86 | const _parseConjuncts = function(query: string, toFilter: NodeCollection): NodeCollection { 87 | const conjuncts: string[] = []; 88 | const negated: boolean[] = []; 89 | let nesting = 0; 90 | let startBrace = -1; 91 | let lastEndBrace = -1; 92 | for (let i = 0; i < query.length; i++) { 93 | if (query[i] === '(') { 94 | if (nesting === 0) { 95 | startBrace = i; 96 | const betweenBraces = query.slice(lastEndBrace+1, i > 0 ? (query[i-1] === '-' ? i-1 : i):i); 97 | if (betweenBraces.trim().length > 0) { 98 | negated.push(false); 99 | conjuncts.push(betweenBraces); 100 | } 101 | } 102 | nesting += 1; 103 | } else if (query[i] === ')') { 104 | nesting -= 1; 105 | if (nesting === 0) { 106 | lastEndBrace = i; 107 | negated.push(startBrace > 0 ? query[startBrace-1] === '-' : false); 108 | conjuncts.push(query.slice(startBrace+1, lastEndBrace)); 109 | } 110 | } else if (i === query.length-1) { 111 | const betweenBraces = query.slice(lastEndBrace+1); 112 | if (betweenBraces.trim().length > 0) { 113 | negated.push(false); 114 | conjuncts.push(betweenBraces); 115 | } 116 | } 117 | } 118 | let coll = toFilter; 119 | for (let i=0; i < conjuncts.length; i++) { 120 | const recFiltered = _parseDisjuncts(conjuncts[i], coll, query); 121 | if (negated[i]) { 122 | coll = coll.difference(recFiltered); 123 | } else { 124 | coll = coll.intersection(recFiltered); 125 | } 126 | } 127 | return coll; 128 | }; 129 | 130 | const _parseDisjuncts = function(query: string, toFilter: NodeCollection, lastDisjunct: string): NodeCollection { 131 | if (lastDisjunct === query) { 132 | return _parseAtomicQuery(query, toFilter); 133 | } 134 | const disjuncts: string[] = []; 135 | let lastEnd = 0; 136 | let nesting = 0; 137 | for (let i = 0; i < query.length; i++) { 138 | if (query[i] === '(') { 139 | nesting += 1; 140 | } else if (query[i] === ')') { 141 | nesting -= 1; 142 | } else if (nesting === 0 && query[i] === 'O' && 143 | i+1 < query.length && query[i+1] === 'R') { 144 | disjuncts.push(query.slice(lastEnd, i)); 145 | lastEnd = i + 2; 146 | } 147 | } 148 | if (lastEnd < query.length) { 149 | disjuncts.push(query.slice(lastEnd)); 150 | } 151 | return disjuncts.reduce((acc, s) => 152 | // Performance optimization: Use toFilter - acc to not consider elements that are already matched 153 | acc.union(_parseConjuncts(s, toFilter.difference(acc))), cytoscape().collection()); 154 | }; 155 | 156 | export const filter = function(query: string, nodes: NodeCollection): NodeCollection { 157 | return _parseDisjuncts(query, nodes, ''); 158 | }; 159 | -------------------------------------------------------------------------------- /src/viz/stylesheet.ts: -------------------------------------------------------------------------------- 1 | import type {FileSystemAdapter} from 'obsidian'; 2 | // import {promises as fs} from 'fs'; 3 | import type {Juggl} from './visualization'; 4 | import {MAX_FONT_SIZE, MAX_NODE_SIZE, MAX_TEXT_WIDTH, MIN_FONT_SIZE, MIN_NODE_SIZE, MIN_TEXT_WIDTH} from '../constants'; 5 | import type {Vault} from 'obsidian'; 6 | import type {IJugglPlugin, StyleGroup} from 'juggl-api'; 7 | 8 | export const STYLESHEET_PATH = function(vault: Vault) { 9 | return `${vault.configDir}/plugins/juggl/graph.css`; 10 | }; 11 | export const SHAPES = ['ellipse', 12 | 'rectangle', 13 | 'triangle', 14 | 'diamond', 15 | 'pentagon', 16 | 'hexagon', 17 | 'tag', 18 | 'rhomboid', 19 | 'star', 20 | 'vee', 21 | 'round-rectangle', 22 | 'round-triangle', 23 | 'round-diamond', 24 | 'round-pentagon', 25 | 'round-hexagon', 26 | 'round-tag', 27 | 28 | ]; 29 | 30 | export const DEFAULT_USER_SHEET = ` 31 | /* For a full overview of styling options, see https://js.cytoscape.org/#style */ 32 | `; 33 | 34 | const YAML_MODIFY_SHEET = ` 35 | 36 | 37 | node[title] { 38 | label: data(title); 39 | } 40 | 41 | node[color] { 42 | background-color: data(color); 43 | } 44 | 45 | node[shape] { 46 | shape: data(shape); 47 | } 48 | 49 | node[width] { 50 | width: data(width); 51 | } 52 | 53 | node[height] { 54 | height: data(height); 55 | } 56 | 57 | node[image] { 58 | background-image: data(image); 59 | } 60 | `; 61 | 62 | export const getGraphColor = function(clazz: string): string { 63 | // Hacky way to get style properties set for Obsidians graph view 64 | const graphDiv = activeDocument.createElement('div'); 65 | graphDiv.addClass('graph-view', clazz); 66 | activeDocument.body.appendChild(graphDiv); 67 | const computedColor = getComputedStyle(graphDiv).getPropertyValue('color'); 68 | graphDiv.detach(); 69 | return computedColor; 70 | }; 71 | 72 | 73 | /* 74 | defaultSheet comes before graph.css, yamlModifySheet comes after. 75 | */ 76 | export class GraphStyleSheet { 77 | defaultSheet: string; 78 | yamlModifySheet: string; 79 | plugin: IJugglPlugin; 80 | constructor(plugin: IJugglPlugin) { 81 | this.defaultSheet = this.getDefaultStylesheet(); 82 | this.yamlModifySheet = YAML_MODIFY_SHEET; 83 | this.plugin = plugin; 84 | } 85 | 86 | async getStylesheet(viz: Juggl): Promise { 87 | const adapter = this.plugin.vault.adapter as FileSystemAdapter; 88 | const file = STYLESHEET_PATH(this.plugin.vault); 89 | // const customSheet = ''; 90 | let customSheet = ''; 91 | try { 92 | customSheet = await adapter.read(file) 93 | .catch(async (err) => { 94 | if (err.code === 'ENOENT') { 95 | const cstmSheet = DEFAULT_USER_SHEET; 96 | await adapter.write(file, cstmSheet); 97 | return cstmSheet; 98 | } else { 99 | throw err; 100 | } 101 | }); 102 | } catch (e) { 103 | console.log('Couldn\'t load user stylesheet. This is probably because we are on mobile'); 104 | console.log(e); 105 | } 106 | // TODO: Ordering: If people specify some new YAML property to take into account, style groups will override this! 107 | 108 | let globalGroups = ''; 109 | if ('settings' in this.plugin) { 110 | // @ts-ignore 111 | globalGroups = this.styleGroupsToSheet(this.plugin.settings.globalStyleGroups, 'global'); 112 | } 113 | const localGroups = this.styleGroupsToSheet(viz.settings.styleGroups, 'local'); 114 | return this.defaultSheet + globalGroups + customSheet + localGroups + this.yamlModifySheet; 115 | } 116 | 117 | 118 | colorToRGBA(col: string): string { 119 | const canvas = activeDocument.createElement('canvas'); 120 | canvas.width = canvas.height = 1; 121 | const ctx = canvas.getContext('2d'); 122 | 123 | 124 | ctx.clearRect(0, 0, 1, 1); 125 | // In order to detect invalid values, 126 | // we can't rely on col being in the same format as what fillStyle is computed as, 127 | // but we can ask it to implicitly compute a normalized value twice and compare. 128 | ctx.fillStyle = '#000'; 129 | ctx.fillStyle = col; 130 | const computed = ctx.fillStyle; 131 | ctx.fillStyle = '#fff'; 132 | ctx.fillStyle = col; 133 | if (computed !== ctx.fillStyle) { 134 | return; // invalid color 135 | } 136 | ctx.fillRect(0, 0, 1, 1); 137 | const rgba = [...ctx.getImageData(0, 0, 1, 1).data]; 138 | return `rgb(${rgba[0]}, ${rgba[1]}, ${rgba[2]})`; 139 | } 140 | 141 | 142 | styleGroupsToSheet(groups: StyleGroup[], groupPrefix: string): string { 143 | let sheet = ''; 144 | const parser = new DOMParser; 145 | for (const [index, val] of groups.entries()) { 146 | if (val.show) { 147 | let icon = ''; 148 | if (val.icon && val.icon.path) { 149 | const svg = '' + 150 | '' + 151 | '' + 152 | `` + 153 | ''; 154 | const html = parser.parseFromString(svg, 'text/xml').documentElement.outerHTML; 155 | icon = `background-image: url('data:image/svg+xml,${encodeURIComponent(html)}');`; 156 | } 157 | // Until size = 1, let text size linearly scale with node, then scale the square root. 158 | const textSizeModifier = Math.max(Math.min(val.size, 1), Math.sqrt(val.size)); 159 | sheet += ` 160 | node.${groupPrefix}-${index} { 161 | background-color: ${val.color}; 162 | shape: ${val.shape}; 163 | background-fit: contain; 164 | ${icon} 165 | width: mapData(degree, 0, 60, ${MIN_NODE_SIZE*val.size}, ${MAX_NODE_SIZE*val.size}); 166 | height: mapData(degree, 0, 60, ${MIN_NODE_SIZE*val.size}, ${MAX_NODE_SIZE*val.size}); 167 | font-size: mapData(degree, 0, 60, ${MIN_FONT_SIZE*textSizeModifier}, ${MAX_FONT_SIZE*textSizeModifier}); 168 | text-max-width: mapData(degree, 0, 60, ${Math.round(MIN_TEXT_WIDTH*textSizeModifier)}px, ${Math.round(MAX_TEXT_WIDTH*textSizeModifier)}px); 169 | } 170 | `; 171 | } else { 172 | sheet += ` 173 | node.${groupPrefix}-${index} { 174 | display: none; 175 | } 176 | `; 177 | } 178 | } 179 | return sheet; 180 | } 181 | 182 | getDefaultStylesheet(): string { 183 | const style = getComputedStyle(activeDocument.body); 184 | let font = style.getPropertyValue('--text'); 185 | font = font.replace('BlinkMacSystemFont,', ''); // This crashes electron for some reason. 186 | if (font.length === 0) { 187 | font = 'Helvetica Neue'; 188 | } 189 | const fillColor = getGraphColor('color-fill'); 190 | const fillHighlightColor = getGraphColor('color-fill-highlight'); 191 | const accentBorderColor = getGraphColor('color-circle'); 192 | const lineColor = getGraphColor('color-line'); 193 | const lineHighlightColor = getGraphColor('color-line-highlight'); 194 | const textColor = getGraphColor('color-text'); 195 | const danglingColor = getGraphColor('color-fill-unresolved'); 196 | return ` 197 | node { 198 | background-color: ${fillColor}; 199 | color: ${textColor}; 200 | font-family: ${font}; 201 | text-valign: bottom; 202 | shape: ellipse; 203 | border-width: 0; 204 | text-wrap: wrap; 205 | min-zoomed-font-size: 8; 206 | } 207 | 208 | node[name] { 209 | label: data(name); 210 | } 211 | node[degree] { 212 | width: mapData(degree, 0, 60, ${MIN_NODE_SIZE}, ${MAX_NODE_SIZE}); 213 | height: mapData(degree, 0, 60, ${MIN_NODE_SIZE}, ${MAX_NODE_SIZE}); 214 | font-size: mapData(degree, 0, 60, ${MIN_FONT_SIZE}, ${MAX_FONT_SIZE}); 215 | text-opacity: mapData(degree, 0, 60, 0.7, 1); 216 | text-max-width: mapData(degree, 0, 60, ${MIN_TEXT_WIDTH}px, ${MAX_TEXT_WIDTH}px); 217 | } 218 | 219 | node:selected { 220 | background-blacken: 0.3; 221 | font-weight: bold; 222 | 223 | } 224 | node:selected[degree] { 225 | border-width: mapData(degree, 0, 60, 1, 3); 226 | } 227 | 228 | .dangling { 229 | background-color: ${danglingColor}; 230 | } 231 | 232 | .image { 233 | shape: round-rectangle; 234 | width: 50; 235 | height: 50; 236 | background-opacity: 0; 237 | background-image: data(resource_url); 238 | background-image-crossorigin: anonymous; 239 | background-image-opacity: 1; 240 | background-fit: contain; 241 | font-size: 0; 242 | background-clip: node; 243 | } 244 | 245 | .image.note { 246 | font-size: mapData(degree, 0, 60, 5, 11); 247 | } 248 | 249 | edge { 250 | line-color: ${lineColor}; 251 | loop-sweep: -50deg; 252 | loop-direction: -45deg; 253 | width: 0.70; 254 | 255 | target-arrow-shape: vee; 256 | target-arrow-fill: filled; 257 | target-arrow-color: ${lineColor}; 258 | 259 | arrow-scale: 0.55; 260 | 261 | font-size: 6; 262 | font-family: ${font}; 263 | color: ${textColor}; 264 | curve-style: straight; 265 | 266 | } 267 | 268 | edge[edgeCount] { 269 | width: mapData(edgeCount, 1, 50, 0.55, 3); 270 | arrow-scale: mapData(edgeCount, 1, 50, 0.35, 1.5); 271 | } 272 | 273 | edge:selected { 274 | width: 0.7; 275 | font-weight: bold; 276 | line-color: ${lineHighlightColor}; 277 | } 278 | 279 | :loop { 280 | display: none; 281 | } 282 | 283 | edge[type] { 284 | label: data(type); 285 | } 286 | .inactive-node, 287 | .unhover { 288 | opacity: 0.3; 289 | } 290 | node.active-node, 291 | node.hover { 292 | background-color: ${fillHighlightColor}; 293 | font-weight: bold; 294 | border-width: 0.4; 295 | border-color: ${accentBorderColor}; 296 | opacity: 1; 297 | } 298 | edge.hover, 299 | edge.connected-active-node, 300 | edge.connected-hover { 301 | width: 1; 302 | opacity: 1; 303 | } 304 | edge.hover, 305 | edge.connected-hover { 306 | font-weight: bold; 307 | line-color: ${lineHighlightColor}; 308 | target-arrow-color: ${lineHighlightColor}; 309 | } 310 | 311 | node.pinned { 312 | border-style: dotted; 313 | border-width: 2; 314 | } 315 | 316 | node.hard-filtered, 317 | node.filtered { 318 | display: none; 319 | } 320 | `; 321 | 322 | // old 323 | // node.protected { 324 | // ghost: yes; 325 | // ghost-offset-x: 1px; 326 | // ghost-offset-y: 1px; 327 | // ghost-opacity: 0.5; 328 | // } 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/viz/workspaces/workspace-manager.ts: -------------------------------------------------------------------------------- 1 | import {Component, DataAdapter} from 'obsidian'; 2 | import type JugglPlugin from '../../main'; 3 | import type {Juggl} from '../visualization'; 4 | import {DATA_FOLDER} from '../../constants'; 5 | import {VizId} from 'juggl-api'; 6 | 7 | export class WorkspaceManager extends Component { 8 | plugin: JugglPlugin; 9 | adapter: DataAdapter; 10 | graphs: string[] = []; 11 | constructor(plugin: JugglPlugin) { 12 | super(); 13 | this.plugin = plugin; 14 | this.adapter = this.plugin.app.vault.adapter; 15 | } 16 | 17 | async onload() { 18 | super.onload(); 19 | try { 20 | await this.adapter.mkdir(DATA_FOLDER(this.plugin.vault)); 21 | const path = require('path'); 22 | this.graphs = (await this.adapter.list(DATA_FOLDER(this.plugin.vault))).folders.map((s) => path.basename(s)); 23 | } catch (e) { 24 | console.log(e); 25 | } 26 | } 27 | 28 | async saveGraph(name: string, viz: Juggl) { 29 | try { 30 | const folder = DATA_FOLDER(viz.vault) + name; 31 | await this.adapter.mkdir(folder); 32 | const graphJson = viz.viz.json(); 33 | await this.adapter.write(folder + '/graph.json', JSON.stringify(graphJson)); 34 | const settings = viz.settings; 35 | await this.adapter.write(folder + '/settings.json', JSON.stringify(settings)); 36 | if (!this.graphs.contains(name)) { 37 | this.graphs.push(name); 38 | } 39 | } catch (e) { 40 | console.log(e); 41 | } 42 | } 43 | 44 | async loadGraph(name: string, viz: Juggl) { 45 | try { 46 | const folder = DATA_FOLDER(viz.vault) + name; 47 | const graph = JSON.parse(await this.adapter.read(folder + '/graph.json')); 48 | const settings = JSON.parse(await this.adapter.read( folder + '/settings.json')); 49 | viz.viz.json(graph); 50 | viz.settings = settings; 51 | 52 | // After loading in the graph, we have to validate with the datastores that the data is still up-to-date: 53 | // This could create race-condition conflicts possibly when a node updates in the meantime. 54 | const nodes = viz.viz.nodes(); 55 | for (let i=1; i < nodes.length; i++ ) { 56 | if (!nodes[i]) { 57 | continue; 58 | } 59 | const vId = VizId.fromNode(nodes[i]); 60 | 61 | for (const store of viz.datastores.dataStores) { 62 | if (store.storeId() === vId.storeId) { 63 | await store.refreshNode(vId, viz); 64 | break; 65 | } 66 | } 67 | } 68 | } catch (e) { 69 | console.log(e); 70 | } 71 | }; 72 | 73 | async deleteGraph(name: string, view: Juggl) { 74 | try { 75 | await this.adapter.rmdir(DATA_FOLDER + name, true); 76 | this.graphs.remove(name); 77 | } catch (e) { 78 | console.log(e); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | div.neovis_setting { 2 | width: content-box; 3 | } 4 | 5 | div.cxtmenu-item { 6 | opacity: 0.8; 7 | } 8 | 9 | /*.cytoscape-navigatorView,*/ 10 | div.cy-navigator { 11 | width: 150px; 12 | height: 150px; 13 | position: fixed; 14 | z-index: 3; 15 | bottom: 10px; 16 | right: 27px; 17 | border: #828282 1px solid; 18 | border-radius: 2px; 19 | background-color: rgba(130, 130, 130, 0.1); 20 | cursor: default; 21 | overflow: hidden; 22 | } 23 | 24 | div.juggl-error { 25 | background-color: red; 26 | } 27 | 28 | 29 | .cy-navigator > img{ 30 | position: relative; 31 | top: 0; 32 | left: 0; 33 | width: 100%; 34 | height: 100%; 35 | z-index: 0; 36 | opacity: 0.8; 37 | 38 | } 39 | .cytoscape-navigatorView{ 40 | position: relative; 41 | top: 0; 42 | left: 0; 43 | cursor: move; 44 | background: #828282; 45 | -moz-opacity: 0.20; 46 | opacity: 0.20; 47 | width: 50%; 48 | height: 50%; 49 | z-index: 0; 50 | } 51 | 52 | .cytoscape-navigatorOverlay{ 53 | position: relative; 54 | top: 0; 55 | left: 0; 56 | z-index: 103; 57 | width: 100%; 58 | height: 100%; 59 | } 60 | 61 | .juggl-hover.is-loaded.hover-popover.popover { 62 | opacity: 0.9; 63 | height: max-content; 64 | } 65 | 66 | .juggl-preview-edge { 67 | height: 140px !important; 68 | } 69 | 70 | .cy-content { 71 | padding: 0 !important; 72 | } 73 | 74 | .cy-toolbar { 75 | position: relative; 76 | left: 8px; 77 | top: 8px; 78 | width: fit-content; 79 | max-width: inherit; 80 | height: 0; 81 | margin: 2px; 82 | margin-block-start: 0; 83 | margin-block-end: 0; 84 | z-index: 1000; 85 | background-color: rgba(0, 0, 0, 0); 86 | } 87 | 88 | .cy-toolbar-section { 89 | width: fit-content; 90 | text-align: center; 91 | background-color: var(--background-primary); 92 | display: inline-block; 93 | margin: 1px; 94 | padding: 1px; 95 | border-color: var(--background-modifier-border); 96 | border-radius: 4px; 97 | border-width: 1px; 98 | border-style: solid; 99 | opacity: 1; 100 | } 101 | .cy-toolbar > input[type='text'] { 102 | font-size: var(--font-small); 103 | background-color: var(--background-secondary); 104 | height: 30px; 105 | padding: 5px 7px; 106 | } 107 | 108 | 109 | button.juggl-button { 110 | width: 27px; 111 | height: 27px; 112 | text-align: center; 113 | background-color: var(--background-secondary); 114 | padding: 0 !important; 115 | margin: 1px; 116 | } 117 | 118 | 119 | .cy-toolbar-section > button:disabled { 120 | background: var(--background-primary); 121 | cursor: not-allowed; 122 | } 123 | 124 | button.juggl-button > svg > path { 125 | fill: var(--text-muted); 126 | opacity: 0.6; 127 | } 128 | 129 | button.juggl-button:hover > svg > path { 130 | opacity: 1; 131 | } 132 | 133 | button.juggl-button:disabled > svg > path { 134 | opacity: 0.3; 135 | } 136 | 137 | button.juggl-button-pane { 138 | background: none; 139 | margin: 0; 140 | padding: 0; 141 | width: 15px; 142 | height: 15px; 143 | } 144 | 145 | 146 | div.juggl-list-text { 147 | font-size: var(--font-small); 148 | word-break: break-word; 149 | } 150 | 151 | div.juggl-style-group { 152 | position: relative; 153 | display: flex; 154 | flex-wrap: wrap; 155 | align-items: center; 156 | padding-bottom: 6px; 157 | padding-top: 6px; 158 | border-bottom: 1px solid var(--background-modifier-border-focus); 159 | } 160 | 161 | div.juggl-style-group-hidden { 162 | width: auto; 163 | display: inline; 164 | } 165 | 166 | div.juggl-nodes-pane, 167 | div.juggl-style-pane { 168 | overflow-y: auto; 169 | padding: 0 10px; 170 | font-size: 14px; 171 | } 172 | 173 | .break { 174 | flex-basis: 100%; 175 | height: 3px; 176 | } 177 | 178 | /* Use a collapsed column to break to a new column */ 179 | .break-column { 180 | flex-basis: 100%; 181 | width: 0; 182 | } 183 | .react-icon { 184 | display: inline-flex; 185 | width: 24px; 186 | box-sizing: content-box; 187 | height: 24px; 188 | stroke-width: 0; 189 | } 190 | .react-icon > svg { 191 | vertical-align: top; 192 | line-height: 0; 193 | font-size: 0; 194 | margin-bottom: 3px; 195 | } 196 | 197 | .juggl-icon-picker > .suggestion-item { 198 | height: auto; 199 | min-height: 24px; 200 | } 201 | 202 | button.juggl-icon-button { 203 | height: 30px; 204 | } 205 | 206 | .juggl-style-pane-left { 207 | display: inline-flex; 208 | width: 50px; 209 | justify-content: center; 210 | } 211 | 212 | .juggl-inline-group { 213 | max-width: 9rem; 214 | display: flex; 215 | align-items: center; 216 | /*padding: .5rem;*/ 217 | } 218 | 219 | .juggl-inline-group .form-control { 220 | text-align: right; 221 | } 222 | 223 | .form-control[type="number"]::-webkit-inner-spin-button, 224 | .form-control[type="number"]::-webkit-outer-spin-button { 225 | -webkit-appearance: none; 226 | margin: 0; 227 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "module": "ESNext", 6 | "target": "es2020", 7 | "sourceMap": true, 8 | "moduleResolution": "node", 9 | "noImplicitAny": true, 10 | "importHelpers": true, 11 | "lib": [ 12 | "dom", 13 | "es5", 14 | "scripthost", 15 | "es2020", 16 | "dom.iterable" 17 | ], 18 | "esModuleInterop": true, 19 | "types": ["node", "svelte"], 20 | "paths": {"src": ["*"]}, 21 | "typeRoots": [ 22 | "node_modules/@types/" 23 | ] 24 | }, 25 | "include": [ 26 | "./**/*" 27 | ], 28 | "exclude": ["node_modules/*"] 29 | } 30 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.5.0": "0.15.0", 3 | "1.0.1": "0.9.12", 4 | "1.0.0": "0.9.7" 5 | } 6 | --------------------------------------------------------------------------------