├── .eslintrc.json ├── .githooks └── pre-commit │ └── filter.sh ├── .github └── workflows │ └── codeql.yml ├── .gitignore ├── .nycrc.json ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── docs ├── edge.graphic.txt ├── en.md └── icons.md ├── example ├── example.browser.html ├── example.destroy.js ├── example.easy.js ├── example.fully.js ├── example.image.browser.html ├── example.image.js ├── example.jsx ├── example.marker.js └── example.summary.js ├── package-lock.json ├── package.json ├── rollup.config.ts ├── scripts ├── browserify.js ├── icons.js └── marker.js ├── src ├── abstracts │ ├── marker.abstract.ts │ ├── note.abstract.ts │ ├── summary.abstract.ts │ ├── topic.abstract.ts │ └── workbook.abstract.ts ├── browser.ts ├── common │ ├── constants │ │ ├── index.ts │ │ └── marker.ts │ ├── model.ts │ ├── templates │ │ └── content.xml │ └── themes │ │ ├── business.json │ │ ├── robust.json │ │ └── snowbrush.json ├── core │ ├── base.ts │ ├── marker.ts │ ├── note.ts │ ├── summary.ts │ ├── theme.ts │ ├── topic.ts │ └── workbook.ts ├── index.ts └── utils │ ├── common.ts │ ├── dumper.ts │ └── zipper.ts ├── test ├── browser │ └── index.html ├── fixtures │ ├── 19442.png │ ├── logo.png │ └── utils.ts ├── functional │ └── index.test.ts ├── mocha.opts └── units │ ├── common.test.ts │ ├── dumper.test.ts │ ├── marker.test.ts │ ├── theme.test.ts │ ├── topic.test.ts │ ├── workbook.test.ts │ └── zip.test.ts ├── tsconfig.json └── tslint.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "jsx": false, 8 | "modules": true 9 | }, 10 | "useJSXTextNode": true, 11 | "project": "./tsconfig.json", 12 | "tsconfigRootDir": ".", 13 | "extraFileExtensions": [".ts"] 14 | }, 15 | "plugins": [ 16 | "@typescript-eslint/tslint" 17 | ], 18 | "rules": { 19 | "@typescript-eslint/tslint/config": ["error", { 20 | "lintFile": "./tslint.json", 21 | "rulesDirectory": [] 22 | }] 23 | } 24 | } -------------------------------------------------------------------------------- /.githooks/pre-commit/filter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | EMAIL=$(git config user.email) 5 | 6 | npm run lint 7 | 8 | if [[ $? != 0 ]]; then 9 | echo "lint error." 10 | exit 1; 11 | fi 12 | 13 | if [[ ${EMAIL} == *"xmind"* ]]; then 14 | echo "Do not use *@xmind.net email to commit codes."; 15 | exit 1; 16 | else 17 | exit 0 18 | fi 19 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '15 23 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | dist 3 | 4 | # Mac os 5 | .DS_Store 6 | 7 | # webstorm 8 | .idea 9 | 10 | docs/typedocs 11 | 12 | # vscode 13 | .vscode 14 | 15 | ### Node template 16 | # Logs 17 | logs 18 | *.log 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # Runtime data 24 | pids 25 | *.pid 26 | *.seed 27 | *.pid.lock 28 | 29 | # Directory for instrumented libs generated by jscoverage/JSCover 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | coverage 34 | 35 | # nyc test coverage 36 | .nyc_output 37 | 38 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 39 | .grunt 40 | 41 | # Bower dependency directory (https://bower.io/) 42 | bower_components 43 | 44 | # node-waf configuration 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules/ 52 | jspm_packages/ 53 | 54 | # TypeScript v1 declaration files 55 | typings/ 56 | 57 | # Optional npm cache directory 58 | .npm 59 | 60 | # Optional eslint cache 61 | .eslintcache 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | 75 | # next.js build output 76 | .next 77 | 78 | .idea/encodings.xml 79 | .idea/misc.xml 80 | .idea/modules.xml 81 | .idea/workspace.xml 82 | .idea/xmind-sdk.iml 83 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@istanbuljs/nyc-config-typescript", 3 | "all": true, 4 | "check-coverage": false, 5 | "exclude": [ 6 | "src/abstracts", 7 | "src/common", 8 | "src/index.ts", 9 | "src/browser.ts", 10 | "test/", 11 | "example/", 12 | "dist/", 13 | "scripts/", 14 | "**/*.d.ts", 15 | "docs", 16 | "coverage", 17 | "rollup.config.ts" 18 | ], 19 | "extension": [".ts"], 20 | "reporter": ["html", "text", "lcov"], 21 | "require": ["ts-node/register"] 22 | } 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | - "10" 6 | - "12" 7 | 8 | install: 9 | - npm i 10 | - npm i ts-node # --require ts-node/register 11 | 12 | before_script: 13 | - npm run lint 14 | - npm run build 15 | 16 | script: 17 | - npm run test 18 | - npm run cov 19 | - npm run clean 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 - 2023 Xmind Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📦 📦 📦 ![](https://assets.xmind.net/www/assets/images/xmind-logo-dark-7a5ac2ec22.svg) 2 | 3 | [![CodeQL](https://github.com/xmindltd/xmind-sdk-js/actions/workflows/codeql.yml/badge.svg?branch=master)](https://github.com/xmindltd/xmind-sdk-js/actions/workflows/codeql.yml) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/qll0sp4ny7bl7yo0/branch/master?svg=true)](https://ci.appveyor.com/project/danielsss/xmind-sdk-js/branch/master) 5 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/a0cef4ab9b024a97b4ef2970fec29158)](https://www.codacy.com/gh/xmindltd/xmind-sdk-js/dashboard?utm_source=github.com&utm_medium=referral&utm_content=xmindltd/xmind-sdk-js&utm_campaign=Badge_Grade) 6 | ![npm](https://img.shields.io/npm/v/xmind) 7 | ![GitHub](https://img.shields.io/github/license/xmindltd/xmind-sdk-js.svg) 8 | [![npm (scoped)](https://img.shields.io/badge/XMind-ZEN-red.svg)](https://xmind.app) 9 | 10 | This project is a lightweight official software development kit for JavaScript/Typescript which is available for browsers and Node.js. 11 | 12 | This library implements various functions which are similar to our UI applications, and You might know the basic concepts of this library if you've used the application before. 13 | 14 | In order to use it conveniently, an essential concept you should know is that everything is a component and each one of them has a unique component ID. You can add a child node under the components, however, the Markers and Notes can only be attached to the components. 15 | 16 | Eventually, Our UI apps could be used to open the `*.xmind` file generated by this tool. 17 | 18 | 19 | ## Recommendations 20 | 21 | * [Xmind AI](https://xmind.works) - It's a lightweight online `Mind-Map` tool comes with AI features which helps you to build everything you wanted. 22 | * [Xmind-generator](https://github.com/xmindltd/xmind-generator) — If you are looking for a tool specifically designed for `Mind-Map` generation, `Xmind Generator` is an official package that prioritizes this functionality, featuring a modern and lightweight API. 23 | 24 | 25 | ## Supported Platforms 26 | 27 | * Linux 28 | * Win32 29 | * Browser (Not Fully Supported) 30 | 31 | ## Usage and Getting Started 32 | 33 | ### Node.js 34 | 35 | ```shell 36 | $ npm i --save xmind 37 | ``` 38 | 39 | > NOTICE: The `xmind-sdk` is renamed to `xmind` from the version: 2.0.0 40 | > 41 | > Please, use `npm i --save xmind` to replace with it if you were using the `xmind-sdk`. 42 | 43 | 44 | ```js 45 | const { Workbook, Topic, Marker } = require('xmind'); 46 | ``` 47 | 48 | ### Browser or Vue.js 49 | 50 | ```jsx harmony 51 | import { Workbook, Topic, Marker } from 'xmind'; 52 | ``` 53 | 54 | ```html 55 | // HTML 56 | // Latest version 57 | 58 | // Specify version 59 | 60 | 61 | 64 | ``` 65 | 66 | 67 | ### More Examples 68 | 69 | 70 | See [example directory](./example). 71 | 72 | ```js 73 | const { Workbook, Topic, Marker, Zipper } = require('xmind'); 74 | 75 | const [ workbook, marker ] = [new Workbook(), new Marker()]; 76 | 77 | const topic = new Topic({sheet: workbook.createSheet('sheet title', 'Central Topic')}); 78 | const zipper = new Zipper({path: '/tmp', workbook, filename: 'MyFirstMap'}); 79 | 80 | // topic.on() default: `central topic` 81 | topic.add({title: 'main topic 1'}); 82 | 83 | topic 84 | .on(topic.cid(/*In default, the componentId is last element*/)) 85 | 86 | // add subtopics under `main topic 1` 87 | .add({title: 'subtopic 1'}) 88 | .add({title: 'subtopic 2'}) 89 | 90 | // attach text note to `main topic 1` 91 | .note('This is a note attached on main topic 1') 92 | 93 | // attach a marker flag to `subtopic 1` 94 | .on(topic.cid('subtopic 1')) 95 | .marker(marker.week('fri')) 96 | 97 | // add a component of the summary that contains two sub topics 98 | .summary({title: 'subtopic summary', edge: topic.cid('subtopic 2')}) 99 | 100 | zipper.save().then(status => status && console.log('Saved /tmp/MyFirstMap.xmind')); 101 | ``` 102 | 103 | 104 | ## Workbook 105 | 106 | The workbook is a temporary storage where all the data are written. 107 | 108 | ### Methods 109 | 110 | #### .createSheet(sheetTitle, topicTitle?) => `Sheet` 111 | 112 | Once the workbook is created, then there's a way to build a sheet containing a `root topic`. 113 | In addition, you can customize their titles by parameters. 114 | 115 | 116 | | Name | Type | Default | Required | 117 | |:---- |:----:|:-------:|:--------:| 118 | | sheetTitle | String | `-` | Y | 119 | | topicTitle | String | `Central Topic` | N | 120 | 121 | #### .createSheets(options: Object[]) => `Object[]` 122 | 123 | You can use this method to create sheets in bulk. 124 | 125 | 126 | | Name | Type | Default | Required | 127 | |:---- |:----:|:-------:|:--------:| 128 | | sheetTitle | String | `-` | Y | 129 | | topicTitle | String | `Central Topic` | N | 130 | 131 | It returns an object of sheet identifier([Click here to check how it uses](./example/example.fully.js)). 132 | 133 | ```typescript 134 | const sheets = workbook.createSheets([ 135 | {s: 'SheetTitle1', t: 'RootTopicTitle1'}, 136 | {s: 'SheetTitle2', t: 'RootTopicTitle2'} 137 | ]); 138 | console.info(sheets); 139 | // [ 140 | // { id: string, title: string }, 141 | // { id: string, title: string } 142 | // ... 143 | // ] 144 | ``` 145 | 146 | #### .getSheets() => `Object[]` 147 | 148 | It allows you to get back the identifier of the sheet anytime and anywhere. 149 | 150 | #### .getSheet(id: string) => `Sheet` 151 | 152 | You can get an instance of the sheet with an existed sheet ID. 153 | 154 | #### .theme(sheetTitle, themeName) => Boolean 155 | 156 | The `UI client` has many theme styles and this library also offers some of them, such as `robust / snowbrush / business`. 157 | 158 | | Name | Type | Default | Required | 159 | |:---- |:----:|:-------:|:--------:| 160 | | sheetTitle | String | null | Y | 161 | | themeName | String | null | Y | 162 | 163 | #### .toJSON() 164 | 165 | Get component's data from the workbook in the form of `JSON`. 166 | 167 | #### .toString() 168 | 169 | Get component's data from the workbook in the form of `STRING`. 170 | 171 | #### .validate() => `{status: Boolean, errors: Array | null}` 172 | 173 | This is the way to prove that all data are available and complete. 174 | 175 | The `status` indicates the result of validation which is `true` if it's correct, otherwise `false` returns. 176 | 177 | ## Topic 178 | 179 | The `Topic` is an important constructor function that implements most of the methods. 180 | And you're going to depend on it during most operations. 181 | 182 | ### Topic Options 183 | 184 | * options.sheet <= `workbook.createSheet(...)` 185 | 186 | You may wonder why we need to offer the `options.sheet` manually? The reason is that `Topic` is implemented independently and most of the methods depend on the instance of the sheet. 187 | 188 | In the UI client, you also need to draw the mind map on the sheet. 189 | 190 | > usage: 191 | > 192 | > ```js 193 | > const {Topic, Workbook} = require('xmind'); 194 | > const wb = new Workbook(); 195 | > 196 | > new Topic({sheet: wb.createSheet('Sheet-1', 'topic-1')}); 197 | > ``` 198 | 199 | ### Methods 200 | 201 | #### .on(componentId?) => Topic 202 | 203 | Set the component to be parent node. If there isn't component ID, the `Central Topic` will become as parent node. 204 | 205 | #### .cid(title?, options?: { customId?: string, parentId?: string }) => String 206 | 207 | Use this method to get componentId. 208 | 209 | > You should use `customId` or `parentId` for getting the `componentId` if there are some duplicated topic titles. 210 | 211 | If you don't specify the title in the period of calling `.cid()`, 212 | the last `componentId` that you've added would be returned. 213 | 214 | #### .cids() => {$cid: $title} 215 | 216 | It will return all the `key/value`s in once. 217 | 218 | #### .add(options) => Topic 219 | 220 | Add a topic component under parent node. 221 | 222 | | Name | Type | Default | Required | 223 | |:----:|:----:|:---------------------------------------------------:|:--------:| 224 | | options.title | String | null | Y | 225 | | options.parentId | String | The previous topic that you've operated on | N | 226 | | options.customId | String | It would be useful if you have the same topic title | N | 227 | 228 | 229 | #### .note(text, del?) => Topic 230 | 231 | Attach a text to parent node. 232 | 233 | | Name | Type | Default | Required | Description | 234 | |:----:|:----:|:-------:|:--------:|:------------| 235 | | text | String | null | Y | text message | 236 | | del | Boolean | false | N | detach the note from current parent node if the `del` is true | 237 | 238 | #### .addLabel(text) => Topic 239 | 240 | Add label text to the component, also you can add label to the same component many times. 241 | 242 | | Name | Type | Default | Required | Description | 243 | |:----:|:----:|:-------:|:--------:|:------------| 244 | | text | String | null | Y | label text string | 245 | 246 | 247 | #### .removeLabels(componentId?) => Topic 248 | 249 | Remove all the labels from the component. 250 | 251 | > If you don't give the componentId, then remove labels from the currently component. 252 | 253 | | Name | Type | Default | Required | Description | 254 | |:----:|:----:|:-------:|:--------:|:------------| 255 | | componentId | String | null | N | - | 256 | 257 | #### .marker(object) => Topic 258 | 259 | Attach a marker flag to the parent node. 260 | Moreover, you can detach a marker flag from the parent node by setting `object.del` as `true`. 261 | Default: `false` 262 | 263 | Example: 264 | 265 | ```js 266 | const {Marker} = require('xmind'); 267 | const marker = new Marker(); 268 | // add 269 | topic.marker(marker.smiley('cry')); 270 | // del 271 | topic.marker(Object.assign({}, marker.smiley('cry'), {del: true})); 272 | ``` 273 | 274 | > [Use `Marker Object` to generate the object](#marker-flags) 275 | 276 | 277 | #### .image() => key 278 | 279 | You can use `.image()` to get `image key` back. 280 | 281 | However, you need to write image into manifest by `zip.updateManifestMetadata()` or `dumper.updateManifestMetadata()`. 282 | 283 | > [See image example](./example/example.image.js) 284 | > [See image in browser example](./example/example.image.browser.html) 285 | 286 | #### .summary(options) => Topic 287 | 288 | Attach a summary component to parent node including all children. In the meantime, the `edge` can be used to set the scope of summary component. 289 | > **Important** 290 | > 291 | > The summary doesn't allow to be added under `Central Topic` 292 | > 293 | > The `edge` must parallel to parent node 294 | 295 | | Name | Type | Default | Required | 296 | |:---- |:----:|:-------:|:--------:| 297 | | options.title | String | null | Y | 298 | | options.edge | String | null | N | 299 | 300 | 301 | > [About `edge`](./docs/edge.graphic.txt) 302 | 303 | 304 | #### .destroy(componentId) => Topic 305 | 306 | Destroy a component from the map tree. 307 | 308 | > **Important** 309 | > 310 | > All children would be destroyed along with it 311 | 312 | 313 | ## Marker flags 314 | 315 | We provide an instance of `Marker` that includes all the markers. Such as: 316 | 317 | ###### .priority(name: `string`) 318 | 319 | ###### .smiley(name: `string`) 320 | 321 | ###### .task(name: `string`) 322 | 323 | ###### .flag(name: `string`) 324 | 325 | ###### .star(name: `string`) 326 | 327 | ###### .people(name: `string`) 328 | 329 | ###### .arrow(name: `string`) 330 | 331 | ###### .symbol(name: `string`) 332 | 333 | ###### .month(name: `string`) 334 | 335 | ###### .week(name: `string`) 336 | 337 | ###### .half(name: `string`) 338 | 339 | ###### .other(name: `string`) 340 | 341 | > **The `name` of marker is available [!here](./docs/icons.md)** 342 | > 343 | > You can also use the **Marker.groups** and **Marker.names** to find out available names of Marker. 344 | 345 | 346 | ### Static methods 347 | 348 | #### Marker.groups() => Array\ 349 | 350 | List available group names. 351 | 352 | #### Marker.names(groupName) => Array\ 353 | 354 | * Get the flag names by `groupName`. 355 | 356 | 357 | ## Zipper 358 | 359 | The module of `Zipper` only works under backend. 360 | 361 | > [!See `Dumper` in browser environment](#dumper) 362 | 363 | ### Zipper Options 364 | 365 | | Name | Type | Default | Required | Description | 366 | |:---- |:----:|:-------:|:--------:|:------------| 367 | | options.path | String | `-` | Y | The path is where to save the `.xmind` file | 368 | | options.workbook | Workbook | `-` | Y | The instance of Workbook | 369 | | options.filename | String | default | N | `default.xmind` | 370 | 371 | #### .updateManifestMetadata(key, content) => Zipper 372 | 373 | Update manifest for image insertion. 374 | 375 | | Name | Type | Default | Required | Description | 376 | |:---- |:----:|:-------:|:--------:|:------------| 377 | | key | String | null | Y | The key only can get by topic.image() | 378 | | content | Buffer | null | Y | The buffer data of image | 379 | 380 | #### .removeManifestMetadata(key) => Zipper 381 | 382 | Remove a pair of key / value from manifest. 383 | 384 | #### .save() => Promise\ 385 | 386 | Save components to the logic disk in the form of zip. 387 | 388 | ## Dumper 389 | 390 | The module of `Dumper` only works under browser. 391 | 392 | ### Dumper methods 393 | 394 | #### .dumping() => Array<{filename: string, value: string}> 395 | 396 | Return an array of objects composed of file content. 397 | In order to open it in the official software, 398 | you need to compress these files in the form of zip with the suffix `.xmind`. 399 | 400 | > **Important** 401 | > 402 | > Don't include top level folders, otherwise the software can't extract files 403 | 404 | #### .updateManifestMetadata(key, content, creator) => Promise\ 405 | 406 | Update manifest for image insertion. 407 | 408 | | Name | Type | Default | Required | Description | 409 | |:---- |:----:|:-------:|:--------:|:------------| 410 | | key | string | null | Y | The key only can get by topic.image() | 411 | | content | File \| Blob \| ArrayBuffer | null | Y | The data of image | 412 | | creator | FileCreator | | Y | To specify how to save the file | 413 | 414 | where `FileCreator` is 415 | 416 | ```typescript 417 | interface FileCreator { 418 | /** 419 | * Hint that should create a folder-like structure, enter the folder if exists 420 | * @param name - Folder name 421 | */ 422 | folder(name: string): Promise 423 | 424 | /** 425 | * Hint that should create a file-like object with `content`, update the file if exists 426 | * @param name Filename 427 | */ 428 | file(name: string, content: File | Blob | ArrayBuffer): Promise 429 | } 430 | ``` 431 | 432 | ## Contributing 433 | Thank you for being interested in the SDK. 434 | 435 | If you have any problems or suggestions, please let's know. 🙂 436 | 437 | We also welcome you to submit a pull request for any big or small issues. 438 | 439 | ## License 440 | 441 | See the [MIT License](LICENSE). 442 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '10' 4 | - nodejs_version: '12' 5 | 6 | install: 7 | 8 | # Get the latest stable version of Node.js or io.js 9 | - ps: Install-Product node $env:nodejs_version 10 | 11 | # install modules 12 | - npm install 13 | - npm i ts-node 14 | 15 | test_script: 16 | - node --version 17 | - npm --version 18 | - npm run test 19 | 20 | # Don't actually build. 21 | build: off 22 | -------------------------------------------------------------------------------- /docs/edge.graphic.txt: -------------------------------------------------------------------------------- 1 | `edge` Graphic Example: 2 | 3 | |--------------------------------------------------------------------------------------| 4 | | | 5 | | | 6 | | main topic 1 - ... subtopics \ | 7 | | \ | 8 | | main topic 2 - ... subtopics \ | 9 | | Summary Description | 10 | | `subtopic 1` / | 11 | | / / | 12 | | main topic 3 - `subtopic 2` / | 13 | | | 14 | | | 15 | |--------------------------------------------------------------------------------------| 16 | 17 | 18 | Implementation code: 19 | 20 | topic.on('main topic 1'); 21 | topic.summary({edge: 'main topic 3'}); -------------------------------------------------------------------------------- /docs/en.md: -------------------------------------------------------------------------------- 1 | # xmind-sdk-js 2 | 3 | [![Build Status](https://travis-ci.org/xmindltd/xmind-sdk-js.svg?branch=master)](https://travis-ci.org/xmindltd/xmind-sdk-js) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/qll0sp4ny7bl7yo0/branch/master?svg=true)](https://ci.appveyor.com/project/danielsss/xmind-sdk-js/branch/master) 5 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/36420399770547e4825f0657eb29118b)](https://www.codacy.com/app/danielsss/xmind-sdk-js?utm_source=github.com&utm_medium=referral&utm_content=xmindltd/xmind-sdk-js&utm_campaign=Badge_Grade) 6 | [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/36420399770547e4825f0657eb29118b)](https://www.codacy.com/app/danielsss/xmind-sdk-js?utm_source=github.com&utm_medium=referral&utm_content=xmindltd/xmind-sdk-js&utm_campaign=Badge_Coverage) 7 | ![npm](https://img.shields.io/npm/v/xmind-sdk.svg?color=red&label=version) 8 | ![GitHub](https://img.shields.io/github/license/xmindltd/xmind-sdk-js.svg) 9 | 10 | This [project](https://github.com/xmindltd/xmind-sdk-js) is an official library that implements various functions which is similar to UI client. If you had access to UI client, you could have already known how to use this library. 11 | 12 | In order to use conveniently, an essential concept you should know is that everything is component and each one of them has a unique component ID. You can add child nodes under the component, however, the `Marker` and `Note` can only be attached to the component. 13 | 14 | ## Getting started 15 | 16 | ### Usage in Node.js 17 | 18 | ```shell 19 | $ npm i --save xmind-sdk 20 | ``` 21 | 22 | ```js 23 | const {Workbook, Topic, Marker} = require('xmind-sdk'); 24 | ``` 25 | 26 | ### Usage in Browser 27 | 28 | ```jsx harmony 29 | // JSX 30 | import {Workbook, Topic, Marker} from 'xmind-sdk'; 31 | ``` 32 | 33 | ```html 34 | // HTML 35 | // Latest version 36 | 37 | // Specify version 38 | 39 | 40 | 43 | 44 | ``` 45 | 46 | ## Simple Usage 47 | 48 | 49 | ```js 50 | const { Workbook, Topic, Marker, Zipper } = require('xmind-sdk'); 51 | 52 | const [workbook, marker] = [new Workbook(), new Marker()]; 53 | 54 | const topic = new Topic({sheet: workbook.createSheet('sheet title', 'Central Topic')}); 55 | const zipper = new Zipper({path: '/tmp', workbook, filename: 'MyFirstMap'}); 56 | 57 | // topic.on() default: `central topic` 58 | topic.add({title: 'main topic 1'}); 59 | 60 | topic 61 | .on(topic.cid(/*In default, the componentId is last element*/)) 62 | 63 | // add subtopics under `main topic 1` 64 | .add({title: 'subtopic 1'}) 65 | .add({title: 'subtopic 2'}) 66 | 67 | // attach text note to `main topic 1` 68 | .note('This is a note attached on main topic 1') 69 | 70 | // attach a marker flag to `subtopic 1` 71 | .on(topic.cid('subtopic 1')) 72 | .marker(marker.week('fri')) 73 | 74 | // add a component of the summary that contains two sub topics 75 | .summary({title: 'subtopic summary', edge: topic.cid('subtopic 2')}) 76 | 77 | zipper.save().then(status => status && console.log('Saved /tmp/MyFirstMap.xmind')); 78 | ``` 79 | 80 | ## More Examples 81 | 82 | [Go to example directory](../example) 83 | 84 | 85 | ## Workbook 86 | 87 | The workbook is a temporary storage where all the data are written. 88 | 89 | ### Methods 90 | 91 | #### .createSheet(sheetTitle, topicTitle?) => `Sheet` 92 | 93 | Once the workbook is created, then there's a way to build a sheet containing a `root topic`. In addition, you can custom their titles by parameters. 94 | 95 | 96 | | Name | Type | Default | Required | 97 | |:---- |:----:|:-------:|:--------:| 98 | | sheetTitle | String | `-` | Y | 99 | | topicTitle | String | `Central Topic` | N | 100 | 101 | 102 | #### .theme(sheetTitle, themeName) => Boolean 103 | 104 | The `UI client` has many theme styles and this library also offers some of them, such as `robust / snowbrush / business`. 105 | 106 | | Name | Type | Default | Required | 107 | |:---- |:----:|:-------:|:--------:| 108 | | sheetTitle | String | null | Y | 109 | | themeName | String | null | Y | 110 | 111 | #### .toJSON() 112 | 113 | Get component's data from the workbook in the form of `JSON`. 114 | 115 | #### .toString() 116 | 117 | Get component's data from the workbook in the form of `STRING`. 118 | 119 | #### .validate() => `{status: Boolean, errors: Array | null}` 120 | 121 | This is proof that all data are available and complete. 122 | 123 | The `status` indicates the result of validation which is `true` if it's correct, othewise `false` returns. 124 | 125 | ## Topic 126 | 127 | The `Topic` is an important constructor function that implements most of the methods. And you are going to depend on it during most operations. 128 | 129 | ### Topic Options 130 | 131 | * options.sheet <= `workbook.createSheet(...)` 132 | 133 | You may wonder why we need to offer the `options.sheet` manually? The reason is that `Topic` is implemented independently and most of the methods depend on the instance of the sheet. 134 | 135 | In the UI client, you also need to draw the mind map on sheet. 136 | 137 | > usage: 138 | > 139 | > ```js 140 | > const {Topic, Workbook} = require('xmind-sdk'); 141 | > const wb = new Workbook(); 142 | > 143 | > new Topic({sheet: wb.createSheet('Sheet-1', 'topic-1')}); 144 | > ``` 145 | 146 | ### Methods 147 | 148 | #### .on(componentId?) => Topic 149 | 150 | Set the component to be parent node. If there isn't component ID, the `Central Topic` will become as parent node. 151 | 152 | #### .cid(title?) => String 153 | 154 | Use .cid to get component ID corresponding to the `title`. 155 | > _!!! NOTE THAT:_ You should avoid duplicating the component `title` if use `title` to search the component ID. 156 | 157 | If none of the components has been added, at least `Central Topic`'ID could be returned. 158 | 159 | If you don't specify title in the period of calling .cid, the last added component ID would be returned. 160 | 161 | #### .cids() => {$cid: $title} 162 | 163 | That will return all added components. 164 | 165 | #### .add(options) => Topic 166 | 167 | Add a topic component under parent node. 168 | 169 | | Name | Type | Default | Required | 170 | |:----:|:----:|:-------:|:--------:| 171 | | options.title | String | null | Y | 172 | 173 | 174 | #### .note(text, del?) => Topic 175 | 176 | Attach a text to parent node. 177 | 178 | | Name | Type | Default | Required | Description | 179 | |:----:|:----:|:-------:|:--------:|:------------| 180 | | text | String | null | Y | text message | 181 | | del | Boolean | false | N | detach the note from current parent node if the `del` is true | 182 | 183 | 184 | #### .marker(object) => Topic 185 | 186 | Attach a marker flag to the parent node. Moreover, you can detach a marker flag from the parent node by setting `object.del` as `true`. default: `false` 187 | 188 | Example: 189 | 190 | ```js 191 | const {Marker} = require('xmind-sdk'); 192 | const marker = new Marker(); 193 | // add 194 | topic.marker(marker.smiley('cry')); 195 | // del 196 | topic.marker(Object.assign({}, marker.smiley('cry'), {del: true})); 197 | ``` 198 | 199 | > [Use `Marker Object` to generate the object](#marker-flags) 200 | 201 | 202 | #### .summary(options) => Topic 203 | 204 | Attach a summary component to parent node including all children. In the meantime, the `edge` can be used to set the scope of summary component. 205 | > _!!! NOTE THAT_ 206 | > 207 | > The summary does not allow to be added under `Central Topic` 208 | > 209 | > The `edge` must parallel to parent node 210 | 211 | | Name | Type | Default | Required | 212 | |:---- |:----:|:-------:|:--------:| 213 | | options.title | String | null | Y | 214 | | options.edge | String | null | N | 215 | 216 | 217 | > [!`edge` graphic](./edge.graphic.txt) 218 | 219 | 220 | #### .destroy(componentId) => Topic 221 | 222 | Destroy a component from the map tree. 223 | 224 | > _!!! NOTE THAT_ 225 | > 226 | > All children would be destroyed along with it 227 | 228 | 229 | ## Marker flags 230 | 231 | We provide an instance of `Marker` that includes all the markers. Such as: 232 | 233 | ###### .priority(name: `string`) 234 | 235 | ###### .smiley(name: `string`) 236 | 237 | ###### .task(name: `string`) 238 | 239 | ###### .flag(name: `string`) 240 | 241 | ###### .star(name: `string`) 242 | 243 | ###### .people(name: `string`) 244 | 245 | ###### .arrow(name: `string`) 246 | 247 | ###### .symbol(name: `string`) 248 | 249 | ###### .month(name: `string`) 250 | 251 | ###### .week(name: `string`) 252 | 253 | ###### .half(name: `string`) 254 | 255 | ###### .other(name: `string`) 256 | 257 | > **The `name` of marker is available [!here](icons.md)** 258 | > 259 | > You also can use **Marker.groups** and **Marker.names** to find out available names 260 | 261 | 262 | ### Static methods 263 | 264 | #### Marker.groups() => Array\ 265 | 266 | List available group names. 267 | 268 | #### Marker.names(groupName) => Array\ 269 | 270 | * Get the flag names by `groupName`. 271 | 272 | 273 | ## Zipper 274 | 275 | The module of `Zipper` only works under backend. 276 | 277 | > [!See `Dumper` in browser environment](#dumper) 278 | 279 | ### Zipper Options 280 | 281 | | Name | Type | Default | Required | Description | 282 | |:---- |:----:|:-------:|:--------:|:------------| 283 | | options.path | String | `-` | Y | The path is where to save the `.xmind` file | 284 | | options.workbook | Workbook | `-` | Y | The instance of Workbook | 285 | | options.filename | String | default | N | `default.xmind` | 286 | 287 | 288 | #### .updateManifestMetadata(key, content) => Zipper 289 | 290 | Update manifest for image insertion. 291 | 292 | | Name | Type | Default | Required | Description | 293 | |:---- |:----:|:-------:|:--------:|:------------| 294 | | key | String | null | Y | The key only can be get by topic.image() | 295 | | content | Buffer | null | Y | The buffer data of image | 296 | 297 | #### .removeManifestMetadata(key) => Zipper 298 | 299 | 300 | #### .save() => Promise\ 301 | 302 | Save components to the logic disk in the form of zip. 303 | 304 | ## Dumper 305 | 306 | The module of `Dumper` only works under browser. 307 | 308 | #### .dumping() => Array<{filename: string, value: string}> 309 | 310 | Return an array of the object composed of file content. In order to open it in the official software, you need to compress these files in the form of zip with end of `.xmind`. 311 | 312 | > **Important** 313 | > 314 | > Do not include top level folders, otherwise the software can't extract files 315 | -------------------------------------------------------------------------------- /docs/icons.md: -------------------------------------------------------------------------------- 1 | # Markers 2 | 3 | ## Usage 4 | ```js 5 | > const { Marker } = require("xmind-sdk"); 6 | 7 | > const marker = new Marker(); 8 | > const valueObject = marker.other("bomb"); 9 | > console.info(valueObject); 10 | {groupId: ..., markerId: ...} 11 | ``` 12 | 13 | ## Groups 14 | 15 | ### .priority 16 | 17 | * - Name: `1` 18 | * - Name: `2` 19 | * - Name: `3` 20 | * - Name: `4` 21 | * - Name: `5` 22 | * - Name: `6` 23 | * - Name: `7` 24 | * - Name: `8` 25 | * - Name: `9` 26 | 27 | ### .smiley 28 | 29 | * - Name: `laugh` 30 | * - Name: `smile` 31 | * - Name: `cry` 32 | * - Name: `surprise` 33 | * - Name: `boring` 34 | * - Name: `angry` 35 | * - Name: `embarrass` 36 | 37 | ### .task 38 | 39 | * - Name: `start` 40 | * - Name: `oct` 41 | * - Name: `quarter` 42 | * - Name: `3oct` 43 | * - Name: `half` 44 | * - Name: `5oct` 45 | * - Name: `3quar` 46 | * - Name: `7oct` 47 | * - Name: `done` 48 | * - Name: `pause` 49 | 50 | ### .flag 51 | 52 | * - Name: `red` 53 | * - Name: `orange` 54 | * - Name: `yellow` 55 | * - Name: `dark-blue` 56 | * - Name: `purple` 57 | * - Name: `green` 58 | * - Name: `blue` 59 | * - Name: `gray` 60 | * - Name: `dark-green` 61 | * - Name: `dark-gray` 62 | 63 | ### .star 64 | 65 | * - Name: `red` 66 | * - Name: `orange` 67 | * - Name: `dark-blue` 68 | * - Name: `purple` 69 | * - Name: `green` 70 | * - Name: `blue` 71 | * - Name: `gray` 72 | * - Name: `yellow` 73 | * - Name: `dark-green` 74 | * - Name: `dark-gray` 75 | 76 | ### .people 77 | 78 | * - Name: `red` 79 | * - Name: `orange` 80 | * - Name: `yellow` 81 | * - Name: `dark-blue` 82 | * - Name: `purple` 83 | * - Name: `green` 84 | * - Name: `blue` 85 | * - Name: `gray` 86 | * - Name: `dark-green` 87 | * - Name: `dark-gray` 88 | 89 | ### .arrow 90 | 91 | * - Name: `left` 92 | * - Name: `right` 93 | * - Name: `up` 94 | * - Name: `down` 95 | * - Name: `left-right` 96 | * - Name: `up-down` 97 | * - Name: `refresh` 98 | * - Name: `up-right` 99 | * - Name: `down-right` 100 | * - Name: `down-left` 101 | * - Name: `up-left` 102 | 103 | ### .symbol 104 | 105 | * - Name: `question` 106 | * - Name: `attention` 107 | * - Name: `wrong` 108 | * - Name: `pause` 109 | * - Name: `no-entry` 110 | * - Name: `plus` 111 | * - Name: `minus` 112 | * - Name: `info` 113 | * - Name: `divide` 114 | * - Name: `equality` 115 | * - Name: `right` 116 | * - Name: `code` 117 | * - Name: `image` 118 | * - Name: `pin` 119 | * - Name: `exclam` 120 | 121 | ### .month 122 | 123 | * - Name: `jan` 124 | * - Name: `feb` 125 | * - Name: `mar` 126 | * - Name: `apr` 127 | * - Name: `may` 128 | * - Name: `jun` 129 | * - Name: `jul` 130 | * - Name: `aug` 131 | * - Name: `sep` 132 | * - Name: `oct` 133 | * - Name: `nov` 134 | * - Name: `dec` 135 | 136 | ### .week 137 | 138 | * - Name: `sun` 139 | * - Name: `mon` 140 | * - Name: `tue` 141 | * - Name: `wed` 142 | * - Name: `thu` 143 | * - Name: `fri` 144 | * - Name: `sat` 145 | 146 | ### .half 147 | 148 | * - Name: `star-green` 149 | * - Name: `star-red` 150 | * - Name: `star-yellow` 151 | * - Name: `star-purple` 152 | * - Name: `star-blue` 153 | * - Name: `star-gray` 154 | 155 | ### .other 156 | 157 | * - Name: `calendar` 158 | * - Name: `email` 159 | * - Name: `phone` 160 | * - Name: `phone2` 161 | * - Name: `fax` 162 | * - Name: `people` 163 | * - Name: `people2` 164 | * - Name: `clock` 165 | * - Name: `coffee-cup` 166 | * - Name: `question` 167 | * - Name: `exclam` 168 | * - Name: `lightbulb` 169 | * - Name: `businesscard` 170 | * - Name: `social` 171 | * - Name: `chat` 172 | * - Name: `note` 173 | * - Name: `lock` 174 | * - Name: `unlock` 175 | * - Name: `yes` 176 | * - Name: `no` 177 | * - Name: `bomb` 178 | 179 | -------------------------------------------------------------------------------- /example/example.browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example browser 6 | 7 | 8 | 9 | 10 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/example.destroy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { Workbook, Topic, Zipper, Marker } = require('xmind'); 4 | 5 | const workbook = new Workbook(); 6 | const topic = new Topic({sheet: workbook.createSheet('sheet-1', 'central topic')}); 7 | 8 | const marker = new Marker(); 9 | const zip = new Zipper({path: '/tmp', workbook}); 10 | 11 | const mainTopic1 = topic.add({title: 'main topic 1'}).cid(); 12 | const mainTopic2 = topic.add({title: 'main topic 2'}).cid(); 13 | const mainTopic3 = topic.add({title: 'repeated'}).cid(); 14 | const mainTopic4 = topic.add({title: 'repeated'}).cid(); 15 | 16 | 17 | topic 18 | .on(mainTopic1) 19 | .add({title: 'subtopic 1'}) 20 | .add({title: 'subtopic 2'}); 21 | 22 | const subtopic1 = topic.on(mainTopic3).add({title: 'subtopic 1'}).cid(); 23 | topic.on(subtopic1) 24 | .note('this is a note text') 25 | .marker(marker.smiley('cry')) 26 | // destroy marker from current component 27 | .marker(Object.assign({}, marker.smiley('cry'), {del: true})); 28 | 29 | const summaryId = topic.summary({title: 'Summary'}).cid(); 30 | 31 | // The destroyed operation does not depends on the parent node 32 | topic 33 | .destroy(topic.cid('subtopic 2')) 34 | .destroy(mainTopic4) 35 | .destroy(summaryId) 36 | .destroy(mainTopic2); 37 | 38 | zip.save().then(status => status && console.log('Saved')); 39 | 40 | -------------------------------------------------------------------------------- /example/example.easy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Example.easy - Easy usage 5 | */ 6 | 7 | const { Workbook, Topic, Zipper } = require('xmind'); 8 | 9 | const wb = new Workbook(); 10 | const topic = new Topic({sheet: wb.createSheet('sheet-1', 'central topic')}); 11 | 12 | const zip = new Zipper({path: '/tmp', workbook: wb}); 13 | 14 | topic 15 | .on() 16 | .add({title: 'main topic 1'}) 17 | .add({title: 'main topic 2'}) 18 | .add({title: 'main topic 2.2'}) 19 | .add({title: 'main topic 3'}) 20 | 21 | .on(topic.cid('main topic 1')) 22 | .add({title: 'subtopic 1 on main topic 1'}) 23 | 24 | .on(topic.cid('main topic 2')) 25 | .add({title: 'subtopic 1 on main topic 2'}) 26 | 27 | .on(topic.cid('subtopic 1 on main topic 2')) 28 | .add({title: 'test node'}); 29 | 30 | 31 | zip.save().then(status => status && console.log('Saved.')); 32 | -------------------------------------------------------------------------------- /example/example.fully.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Example.fully - Topic fully usage 6 | */ 7 | 8 | 9 | const { Topic, Workbook, Zipper, Marker } = require('../dist'); 10 | 11 | const workbook = new Workbook(); 12 | const topicOnSheet1 = workbook.createSheet('sheet-1', 'Computer science - 1'); 13 | const topic = new Topic({ sheet: topicOnSheet1 }); 14 | 15 | 16 | const workbook2 = new Workbook(); 17 | const createdSheetList = workbook2.createSheets([ 18 | {s: 'sheetName1', t: 'rootTopicName1'}, 19 | {s: 'sheetName2', t: 'rootTopicName2'}, 20 | {s: 'sheetName3', t: 'rootTopicName3'} 21 | ]); 22 | 23 | // console.info(createdSheetList); 24 | // console.info('sheetList:', workbook2.getSheets()); 25 | // console.info('sheet1', workbook2.getSheet(createdSheetList[0].id)); 26 | 27 | // const zip2 = new Zipper({path: '/tmp', workbook: workbook2}); 28 | 29 | // Set theme 30 | workbook.theme('sheet-1', 'robust'); 31 | const zip = new Zipper({path: '/tmp', workbook}); 32 | const marker = new Marker(); 33 | 34 | topic 35 | .add({title: 'Programming Language'}) 36 | .add({title: 'Software Name'}) 37 | .add({title: 'Network device'}) 38 | .add({title: 'Computer Brand'}) 39 | .marker(marker.smiley('smile')) 40 | 41 | 42 | .on(topic.cid('Programming Language')) 43 | .add({title: 'dynamic'}) 44 | .add({title: 'static'}) 45 | 46 | .on(topic.cid()/* Also the topic.cid('static') is working */) 47 | .add({title: 'C'}) 48 | .add({title: 'C++'}) 49 | .add({title: '中文测试'}) 50 | .add({title: 'にほんご/にっぽんご'}) 51 | .add({title: 'mixed123中文ぽんご😋'}) 52 | .add({title: 'Java'}) 53 | .on(topic.cid('C')) 54 | .summary({title: 'Low level that is hard to learning', edge: topic.cid('C++')}) 55 | 56 | .on(topic.cid('dynamic')) 57 | .note('The static languages are fast more than dynamic language') 58 | .add({title: 'Node.js'}) 59 | .add({title: 'Python'}) 60 | .add({title: 'Ruby'}) 61 | .on(topic.cid('dynamic')) 62 | .summary({title: 'In popular'}) 63 | 64 | 65 | // on Software 66 | .on(topic.cid('Software')) 67 | .add({title: 'jetBrains'}) 68 | .add({title: 'Microsoft'}) 69 | 70 | .on(topic.cid('jetBrains')) 71 | .marker(marker.smiley('smile')) 72 | .add({title: 'WebStorm'}) 73 | .add({title: 'Pycharm'}) 74 | .add({title: 'CLion'}) 75 | .add({title: 'IntelliJ Idea'}) 76 | .add({title: 'etc.'}) 77 | .summary({title: 'all of the productions are belonging to jetbrains'}) 78 | 79 | .on(topic.cid('Microsoft')) 80 | .marker(marker.smiley('cry')) 81 | .add({title: 'vs code'}); 82 | 83 | // console.info(workbook.toJSON()); 84 | 85 | zip.save().then(status => { 86 | status && console.log('%s saved', zip.target()); 87 | }); 88 | // zip2.save().then(status => status && console.log('zip2 saved')); 89 | -------------------------------------------------------------------------------- /example/example.image.browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Example browser 7 | 8 | 9 | 10 | 14 | 15 | 58 | 59 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /example/example.image.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Example.image - Insert image on topic 6 | */ 7 | 8 | 9 | const {Topic, Workbook, Zipper} = require('xmind'); 10 | const fs = require('fs'); 11 | 12 | const workbook = new Workbook(); 13 | const sheet = workbook.createSheet('sheet-1', 'Computer science'); 14 | 15 | const topic = new Topic({sheet}); 16 | const zip = new Zipper({path: '/tmp', workbook}); 17 | 18 | 19 | topic 20 | .add({title: 'main topic 1'}) 21 | .add({title: 'main topic 2'}); 22 | 23 | // Insert image on `main topic 1` 24 | const imageKey = topic.on(topic.cid('main topic 1')).image(); 25 | const ctx = fs.readFileSync(join(__dirname, '../fixtures/19442.png')); 26 | zip.updateManifestMetadata(imageKey, ctx); 27 | // zip.save().then(); -------------------------------------------------------------------------------- /example/example.jsx: -------------------------------------------------------------------------------- 1 | import { Workbook, Topic, Marker, Zipper } from 'xmind'; 2 | 3 | const wb = new Workbook(); 4 | wb.createSheet('sheet-1','Root Topic'); 5 | const topic = new Topic({sheet: wb.sheet}) 6 | const marker = new Marker(); 7 | 8 | topic 9 | .on() 10 | .add({title: 'main topic 1'}) 11 | .on(topic.cid()) 12 | .add({title: 'subtopic 1'}) 13 | .add({title: 'subtopic 2'}) 14 | .add({title: 'subtopic 3'}) 15 | .add({title: 'subtopic 4'}) 16 | .on(topic.cid('subtopic 2')) 17 | .note('this is a note record') 18 | .on(topic.cid('subtopic 3')) 19 | .note('this is a note record attached on subtopic 3') 20 | .marker(marker.smiley('cry')) 21 | .on(topic.rootTopicId) 22 | .add({title: 'main topic 1'}) 23 | .add({title: 'main topic 2'}) 24 | .add({title: 'main topic 2.2'}) 25 | .add({title: 'main topic 2.1'}) 26 | 27 | console.info('Main topic Id:', topic.cid('main topic 1')); 28 | console.info(topic.cids()); 29 | 30 | const zip = new Zipper({path: '/tmp', workbook: wb}); 31 | zip.save().then(status => {console.info(status)}); 32 | -------------------------------------------------------------------------------- /example/example.marker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Example.marker - Marker usage 5 | */ 6 | 7 | 8 | const { Workbook, Topic, Zipper, Marker } = require('xmind'); 9 | 10 | const wb = new Workbook(); 11 | const topic = new Topic({sheet: wb.createSheet('sheet-1', 'central topic')}); 12 | 13 | const zip = new Zipper({path: '/tmp', workbook: wb}); 14 | 15 | const marker = new Marker(); 16 | 17 | topic 18 | .add({title: 'main topic 1'}) 19 | 20 | // attach markers to main topic 1 21 | .on(topic.cid()) 22 | .marker(marker.smiley('cry')) 23 | .marker(marker.week('fri')) 24 | .marker(marker.smiley('laugh')) 25 | 26 | .on(topic.rootTopicId) 27 | .add({title: 'main topic 2'}) 28 | 29 | // detach marker from main topic 1 30 | .on(topic.cid('main topic 1')) 31 | .marker(Object.assign({}, marker.smiley('cry'), {del: true})); 32 | 33 | zip.save().then(status => process.exit(status ? 0 : 1)); 34 | -------------------------------------------------------------------------------- /example/example.summary.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Example.summary - Summary usage 5 | */ 6 | 7 | const { Workbook, Topic, Zipper } = require('xmind'); 8 | 9 | const wb = new Workbook(); 10 | const topic = new Topic({sheet: wb.createSheet('sheet-1', 'central topic')}); 11 | 12 | 13 | /** Range Graph 14 | ------------------------------------------ 15 | | subtopic 1 | 16 | | / | 17 | Central topic -| 'main topic 4' - subtopic 2 | ------- Summary title 18 | | \ | 19 | | subtopic 3 | 20 | ------------------------------------------ 21 | */ 22 | 23 | topic 24 | .on() 25 | // adding main topic 4 on central topic 26 | .add({title: 'main topic 4'}) 27 | 28 | // In default, The cid() returns id of `main topic 4` 29 | // Also topic.cid('main topic 4') is working 30 | .on(topic.cid()) 31 | 32 | // adding some subtopics on `main topic 4` 33 | .add({title: 'subtopic 1'}) 34 | .add({title: 'subtopic 2'}) 35 | .add({title: 'subtopic 3'}) 36 | 37 | // The summary will be attach on 'main topic 4' and contains all the sub topics 38 | .summary({title: 'Summary title 1'}); 39 | 40 | 41 | // You can do as below codes if just want the summary to contains subtopic 1 - 3 42 | 43 | topic 44 | .on() 45 | .add({title: 'main topic 2'}) 46 | .on(topic.cid('main topic 2')) 47 | .add({title: 'subtopic 1 on main topic 2'}) 48 | .add({title: 'subtopic 2 on main topic 2'}) 49 | .add({title: 'subtopic 3 on main topic 2'}) 50 | .add({title: 'subtopic 4 on main topic 2'}) 51 | .on(topic.cid('subtopic 1 on main topic 2')) 52 | .summary({title: 'contains subtopic 1 - 3', edge: topic.cid('subtopic 3 on main topic 2')}) 53 | 54 | 55 | const zip = new Zipper({path: '/tmp', workbook: wb}); 56 | zip.save().then(status => status && console.info('Saved')); 57 | 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xmind", 3 | "version": "2.2.33", 4 | "description": "The SDK of XMind Ltd.", 5 | "main": "dist/index.js", 6 | "browser": "dist/xmind.esm.js", 7 | "scripts": { 8 | "test": "npm run lint && nyc mocha", 9 | "build": "npm run test && shx rm -rf ./dist && cross-env node_modules/.bin/tsc && npm run cp && npm run browserify && npm run rollup", 10 | "lint": "cross-env node_modules/.bin/tslint --config tslint.json --project .", 11 | "fix": "cross-env node_modules/.bin/tslint --config tslint.json --project . --fix", 12 | "build-doc": "cross-env BUILD_DOCS=true node scripts/icons.js", 13 | "browserify": "node scripts/browserify.js", 14 | "cov": "shx cat ./coverage/lcov.info | codacy-coverage --language typescript", 15 | "clean": "shx rm -rf ./coverage ./.nyc_output", 16 | "cp": "shx cp -r ./src/common/templates ./src/common/themes ./dist/common/", 17 | "patch": "npm run build && npm version patch && npm publish --access public", 18 | "minor": "npm run build && npm version minor && npm publish --access public", 19 | "major": "npm run build && npm version major && npm publish --access public", 20 | "rollup": "rollup --config rollup.config.ts --configPlugin typescript --bundleConfigAsCjs" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/xmindltd/xmind-sdk-js.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/xmindltd/xmind-sdk-js/issues" 28 | }, 29 | "keywords": [ 30 | "xmind-sdk", 31 | "xmindsdk", 32 | "xmind", 33 | "xmind-sdk-js", 34 | "xmind-sdk-ts", 35 | "xmind-package" 36 | ], 37 | "files": [ 38 | "dist" 39 | ], 40 | "dependencies": { 41 | "ajv": "^6.10.0", 42 | "debug": "^4.1.1", 43 | "iconv-lite": "^0.6.3", 44 | "jszip": "^3.2.1", 45 | "tree-model": "^1.0.7", 46 | "uuid": "^3.3.2", 47 | "xmind-model": "^1.1.12" 48 | }, 49 | "devDependencies": { 50 | "@babel/core": "^7.19.6", 51 | "@babel/preset-env": "^7.19.4", 52 | "@babel/preset-react": "^7.18.6", 53 | "@istanbuljs/nyc-config-typescript": "^0.1.3", 54 | "@rollup/plugin-alias": "^4.0.2", 55 | "@rollup/plugin-commonjs": "^23.0.2", 56 | "@rollup/plugin-json": "^5.0.1", 57 | "@rollup/plugin-node-resolve": "^15.0.1", 58 | "@rollup/plugin-typescript": "^9.0.2", 59 | "@types/ajv": "^1.0.0", 60 | "@types/chai": "^4.1.7", 61 | "@types/debug": "^4.1.4", 62 | "@types/jszip": "^3.1.5", 63 | "@types/mocha": "^5.2.6", 64 | "@types/node": "^12.0.0", 65 | "@typescript-eslint/eslint-plugin": "^1.7.0", 66 | "@typescript-eslint/eslint-plugin-tslint": "^1.7.0", 67 | "@typescript-eslint/parser": "^1.7.0", 68 | "babelify": "^10.0.0", 69 | "browserify": "^17.0.0", 70 | "chai": "^4.2.0", 71 | "codacy-coverage": "^3.4.0", 72 | "cross-env": "^7.0.3", 73 | "istanbul": "^0.4.5", 74 | "minify-stream": "^1.2.0", 75 | "mocha": "^6.1.4", 76 | "mocha-appveyor-reporter": "^0.4.2", 77 | "mocha-lcov-reporter": "^1.3.0", 78 | "nyc": "^14.1.1", 79 | "rollup": "^3.2.3", 80 | "rollup-plugin-minification": "^0.2.0", 81 | "shx": "^0.3.4", 82 | "sinon": "^7.3.2", 83 | "source-map-support": "^0.5.12", 84 | "tsify": "^5.0.4", 85 | "tslint": "^5.16.0", 86 | "typescript": "^3.4.5", 87 | "uglify-js": "^3.6.0", 88 | "uglifyify": "^5.0.1", 89 | "unassertify": "^2.1.1" 90 | }, 91 | "author": "daniels", 92 | "license": "MIT" 93 | } 94 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import json from '@rollup/plugin-json'; 3 | import typescript from '@rollup/plugin-typescript'; 4 | // @ts-ignore 5 | import { terser } from 'rollup-plugin-minification'; 6 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 7 | 8 | const pkg = require('./package.json'); 9 | const format = 'esm'; 10 | const entry = 'src/browser.ts'; 11 | 12 | export default [ 13 | { 14 | input: entry, 15 | output: { 16 | file: `dist/${pkg.name}.${format}.js`, 17 | format, sourcemap: true 18 | }, 19 | plugins: [ 20 | commonjs({ ignoreTryCatch: false, include: 'node_modules/**' }), 21 | typescript({ compilerOptions: { module: 'CommonJS'} }), 22 | json(), 23 | nodeResolve({ preferBuiltins: true }), 24 | terser() 25 | ] 26 | } 27 | ]; -------------------------------------------------------------------------------- /scripts/browserify.js: -------------------------------------------------------------------------------- 1 | #!/bin/env node 2 | 3 | 'use strict'; 4 | 5 | const browserify = require('browserify'); 6 | const tsify = require('tsify'); 7 | const path = require('path'); 8 | const fs = require('fs'); 9 | 10 | const PATH = path.join(__dirname, '../dist/xmind-sdk.bundle.js'); 11 | const PATH_WITHOUT_SDK = path.join(__dirname, '../dist/xmind.min.js'); 12 | 13 | if (fs.existsSync(PATH)) fs.unlinkSync(PATH); 14 | 15 | browserify() 16 | .add(path.join(__dirname, '../src/browser.ts')) 17 | .plugin(tsify, { noImplicitAny: false, target: 'es6' }) 18 | .transform('babelify', { 19 | presets: ['@babel/preset-env', '@babel/preset-react'], 20 | extensions: ['.ts', '.js'] 21 | }) 22 | .bundle() 23 | .on('error', function (error) { console.error(error.toString()); }) 24 | .pipe(require('minify-stream')({ sourceMap: false })) 25 | .pipe(fs.createWriteStream(PATH).on('finish', () => { 26 | console.info(PATH); 27 | fs.createReadStream(PATH) 28 | .pipe(fs.createWriteStream(PATH_WITHOUT_SDK)) 29 | .on('finish', () => { 30 | console.info(PATH_WITHOUT_SDK); 31 | }) 32 | })); 33 | -------------------------------------------------------------------------------- /scripts/icons.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin env 2 | 3 | 'use strict'; 4 | 5 | const markers = require('./marker'); 6 | const { writeFileSync } = require('fs'); 7 | const path = require('path'); 8 | 9 | const header = '# Markers\n'; 10 | const usage = '\n## Usage\n\ 11 | \`\`\`js \ 12 | \n> const { Marker } = require("xmind-sdk"); \ 13 | \n \ 14 | \n> const marker = new Marker(); \ 15 | \n> const valueObject = marker.other("bomb"); \ 16 | \n> console.info(valueObject); \ 17 | \n{groupId: ..., markerId: ...} \ 18 | \n\`\`\`\n\ 19 | '; 20 | 21 | const groups = '\n## Groups\n'; 22 | 23 | let contents = '\n'; 24 | 25 | const height = '32px'; 26 | const width = '32px'; 27 | 28 | for (const key in markers) { 29 | contents += `### .${key}\n\n` 30 | for (const name in markers[key]) { 31 | if (!markers[key].hasOwnProperty(name)) continue; 32 | contents += `* - Name: \`${name}\` \n`; 33 | } 34 | contents += '\n'; 35 | } 36 | 37 | writeFileSync(path.join(__dirname, '../docs/icons.md'), `${header}${usage}${groups}${contents}`, {encoding: 'utf8'}); 38 | 39 | -------------------------------------------------------------------------------- /src/abstracts/marker.abstract.ts: -------------------------------------------------------------------------------- 1 | export interface Marker { 2 | groupId: string; 3 | markerId: string; 4 | } 5 | 6 | /** 7 | * @description Marker abstract class 8 | * should to override all the methods 9 | */ 10 | export class AbstractMarker { 11 | /** 12 | * @description The icon of group `priority` 13 | * @param {String} name 14 | * @return {Marker} 15 | */ 16 | priority(name: string): Marker { return null; } 17 | 18 | /** 19 | * @description The icon of group `smiley` 20 | * @param {String} name 21 | * @return {Marker} 22 | */ 23 | smiley(name: string): Marker { return null; } 24 | 25 | /** 26 | * @description The icon of group `task` 27 | * @param {String} name 28 | * @return {Marker} 29 | */ 30 | task(name: string): Marker { return null; } 31 | 32 | /** 33 | * @description The icon of group `flag` 34 | * @param {String} name 35 | * @return {Marker} 36 | */ 37 | flag(name: string): Marker { return null; } 38 | 39 | /** 40 | * @description The icon of group `star` 41 | * @param {String} name 42 | * @return {Marker} 43 | */ 44 | star(name: string): Marker { return null; } 45 | 46 | /** 47 | * @description The icon of group `people` 48 | * @param {String} name 49 | * @return {Marker} 50 | */ 51 | people(name: string): Marker { return null; } 52 | 53 | /** 54 | * @description The icon of group `arrow` 55 | * @param {String} name 56 | * @return {Marker} 57 | */ 58 | arrow(name: string): Marker { return null; } 59 | 60 | /** 61 | * @description The icon of group `symbol` 62 | * @param {String} name 63 | * @return {Marker} 64 | */ 65 | symbol(name: string): Marker { return null; } 66 | 67 | /** 68 | * @description The icon of group `month` 69 | * @param {String} name 70 | * @return {Marker} 71 | */ 72 | month(name: string): Marker { return null; } 73 | 74 | /** 75 | * @description The icon of group `week` 76 | * @param {String} name 77 | * @return {Marker} 78 | */ 79 | week(name: string): Marker { return null; } 80 | 81 | /** 82 | * @description The icon of group `half` 83 | * @param {String} name 84 | * @return {Marker} 85 | */ 86 | half(name: string): Marker { return null; } 87 | 88 | /** 89 | * @description The icon of group `other` 90 | * @param {String} name 91 | * @return {Marker} 92 | */ 93 | other(name: string): Marker { return null; } 94 | } 95 | -------------------------------------------------------------------------------- /src/abstracts/note.abstract.ts: -------------------------------------------------------------------------------- 1 | import * as Model from '../common/model'; 2 | 3 | /** 4 | * @description Note abstract 5 | */ 6 | export interface AbstractNote { 7 | /** 8 | * @description Formatting content as JSON 9 | * @return {Object} 10 | */ 11 | toJSON(): Model.Notes; 12 | 13 | /** 14 | * @description Formatting content as String 15 | * @return {String} 16 | */ 17 | toString(): string; 18 | } 19 | -------------------------------------------------------------------------------- /src/abstracts/summary.abstract.ts: -------------------------------------------------------------------------------- 1 | import { Summary } from '../core/summary'; 2 | import * as Core from 'xmind-model'; 3 | 4 | export interface SummaryOptions { 5 | title?: string; 6 | edge?: string; 7 | } 8 | 9 | export interface RangeOptions { 10 | children: Array; 11 | condition: Array; 12 | } 13 | 14 | export interface SummaryDataStructure { 15 | id: string; 16 | range: string; 17 | topicId: string; 18 | } 19 | 20 | export interface AbstractSummary { 21 | 22 | /** 23 | * @description Set a range scope on the instance of Summary 24 | * @param {RangeOptions} options 25 | * @param {Array} options.children - An array of topic 26 | * @param {Array} options.condition - An array of topicId that contains position of start and end 27 | * @return {String} 28 | */ 29 | range(options: RangeOptions): Summary; 30 | 31 | /** 32 | * @description Get the full data structure 33 | * @return {SummaryDataStructure} 34 | */ 35 | toJSON(): SummaryDataStructure; 36 | } 37 | -------------------------------------------------------------------------------- /src/abstracts/topic.abstract.ts: -------------------------------------------------------------------------------- 1 | import { Topic } from '..'; 2 | import { SummaryOptions } from './summary.abstract'; 3 | import * as Model from '../common/model'; 4 | import * as Core from 'xmind-model'; 5 | 6 | export interface TopicOptions { 7 | sheet: Core.Sheet; 8 | } 9 | 10 | export interface MarkerOptions { 11 | groupId: string; 12 | markerId: string; 13 | del?: boolean; 14 | } 15 | 16 | export interface ImageOptions { 17 | width?: string; 18 | height?: string; 19 | } 20 | 21 | export interface AbstractTopic { 22 | 23 | /** 24 | * @description Set topic as the parent node 25 | * @param {String} [componentId] - The root topic will be used as the parent node if you don't given 26 | * @return {Topic} 27 | */ 28 | on(componentId?: string): Topic; 29 | 30 | /** 31 | * @description Add a topic on the parent node 32 | * @param {Model.Topic} topic - The topic data model 33 | * @param {Object} options 34 | * @param {String} options.title - The title of topic 35 | * @param {Number} [options.index] - The position where the element in map tree 36 | * @return {Topic} 37 | */ 38 | add(topic: Model.Topic, options?: {index: number}): Topic; 39 | 40 | 41 | /** 42 | * @description Add an image on the current topic 43 | * @param {Object} options 44 | * @param {String} [options.width] - Image width 45 | * @param {String} [options.height] - image height 46 | * @return {String} a file key that can be used for `Zipper.updateManifestMetadata` and `Dumper.updateManifestMetadata` 47 | */ 48 | image(options?: ImageOptions): string; 49 | 50 | /** 51 | * @description Get topic by topicId 52 | * @param {String} componentId 53 | * @return {Core.Topic} 54 | */ 55 | find(componentId: string): Core.Topic; 56 | 57 | 58 | /** 59 | * @description Attach a text note to topic 60 | * @param {String} text - note body 61 | * @param {Boolean} del - a boolean flag for text note deletion 62 | * @return {Topic} 63 | */ 64 | note(text: string, del?: boolean): Topic; 65 | 66 | /** 67 | * @description Destroy a component from map tree 68 | * @param {String} componentId 69 | * @return {Topic} 70 | */ 71 | destroy(componentId: string): Topic; 72 | 73 | /** 74 | * @description Add info of summary to topic and range topics 75 | * @param {SummaryOptions} [options] 76 | * @param {String} options.title - The title of summary 77 | * @param {String} options.edge - A topicId for the range of summary 78 | * @return {Topic} 79 | */ 80 | summary(options: SummaryOptions): Topic; 81 | 82 | /** 83 | * @description Add marker to topic 84 | * @param {MarkerOptions} options - The groupId & markerId 85 | * @param {String} options.groupId 86 | * @param {String} options.markerId 87 | * @param {Boolean} options.del - The marker object will be removed if del set as true 88 | * @return {Topic} 89 | */ 90 | marker(options: MarkerOptions): Topic; 91 | 92 | /** 93 | * @description Get the topicId that you have added. 94 | * * In default, It's going to return the last topicId 95 | * @param { String } title - Find out topicId by `Title`. 96 | * @return { String | Null } 97 | */ 98 | cid(title?: string): string | null; 99 | 100 | 101 | /** 102 | * @description Get an object that contains pairs of $topicId and $title 103 | * @return { Record } 104 | */ 105 | cids(): Record; 106 | } 107 | -------------------------------------------------------------------------------- /src/abstracts/workbook.abstract.ts: -------------------------------------------------------------------------------- 1 | import * as Core from 'xmind-model'; 2 | 3 | export interface CreateSheetsOptions { 4 | s: string; 5 | t: string; 6 | } 7 | 8 | export interface ResponseOfSheets { 9 | id: string; 10 | title: string; 11 | } 12 | 13 | 14 | export interface AbstractWorkbook { 15 | 16 | /** 17 | * @description Create a instance of Sheet 18 | * @param {String} sheetTitle 19 | * @param {String} [centralTopicTitle] 20 | */ 21 | createSheet(sheetTitle: string, centralTopicTitle: string): Core.Sheet; 22 | 23 | 24 | /** 25 | * To create sheet in batch mode 26 | * @param {Object[{ 27 | * s: string, 28 | * t: string 29 | * }]} options 30 | */ 31 | createSheets(options: CreateSheetsOptions[]): ResponseOfSheets[]; 32 | 33 | /** 34 | * Get sheet back 35 | * @param {String} id 36 | */ 37 | getSheet(id: string): Core.Sheet; 38 | 39 | /** 40 | * Get all sheet information that you created 41 | */ 42 | getSheets(): ResponseOfSheets[]; 43 | 44 | /** 45 | * @description Set theme color 46 | * @param {String} sheetTitle 47 | * @param {String} themeName 48 | */ 49 | theme(sheetTitle:string, themeName: string): boolean; 50 | 51 | /** 52 | * @description Formatting Mind-map data as String 53 | * @return {String} 54 | */ 55 | toString(): string; 56 | 57 | /** 58 | * @description Formatting Mind-map data as JSON 59 | * @return {Object} 60 | */ 61 | toJSON(): object; 62 | 63 | 64 | /** 65 | * @description Validate Mind-map data 66 | * @return {status: boolean, errors: Array} The `status` indicates result and you also can get errors 67 | */ 68 | validate(): {status: boolean, errors: Array}; 69 | } 70 | -------------------------------------------------------------------------------- /src/browser.ts: -------------------------------------------------------------------------------- 1 | import { Workbook } from './core/workbook'; 2 | import { Topic } from './core/topic'; 3 | import { Marker } from './core/marker'; 4 | import { Dumper } from './utils/dumper'; 5 | 6 | export { Workbook, Topic, Marker, Dumper }; 7 | // In browser, window === global 8 | Object.assign(global, { Workbook, Topic, Marker, Dumper }); 9 | -------------------------------------------------------------------------------- /src/common/constants/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Zip files 3 | */ 4 | const PACKAGE_MAP = { 5 | MANIFEST: { NAME: 'manifest.json', TYPE: 'json' }, 6 | CONTENT_JSON: { NAME: 'content.json', TYPE: 'json'}, 7 | CONTENT_XML: { NAME: 'content.xml', TYPE: 'xml'}, 8 | METADATA: { NAME: 'metadata.json', TYPE: 'json'}, 9 | THUMBNAILS: {NAME: 'Thumbnails', TYPE: 'directory'}, 10 | RESOURCES: { NAME: 'resources', TYPE: 'directory'} 11 | }; 12 | 13 | export { PACKAGE_MAP }; 14 | -------------------------------------------------------------------------------- /src/common/constants/marker.ts: -------------------------------------------------------------------------------- 1 | const marker = { 2 | 'priorityMarkers': { 3 | 'id': 'priorityMarkers', 4 | 'markersMap': { 5 | 'priority-1': { 6 | 'id': 'priority-1', 7 | 'name': '%priority1', 8 | 'resource': 'priority_1@32.png', 9 | 'svg': 'priority_1@16.svg', 10 | 'index': 0 11 | }, 12 | 'priority-2': { 13 | 'id': 'priority-2', 14 | 'name': '%priority2', 15 | 'resource': 'priority_2@32.png', 16 | 'svg': 'priority_2@16.svg', 17 | 'index': 1 18 | }, 19 | 'priority-3': { 20 | 'id': 'priority-3', 21 | 'name': '%priority3', 22 | 'resource': 'priority_3@32.png', 23 | 'svg': 'priority_3@16.svg', 24 | 'index': 2 25 | }, 26 | 'priority-4': { 27 | 'id': 'priority-4', 28 | 'name': '%priority4', 29 | 'resource': 'priority_4@32.png', 30 | 'svg': 'priority_4@16.svg', 31 | 'index': 3 32 | }, 33 | 'priority-5': { 34 | 'id': 'priority-5', 35 | 'name': '%priority5', 36 | 'resource': 'priority_5@32.png', 37 | 'svg': 'priority_5@16.svg', 38 | 'index': 4 39 | }, 40 | 'priority-6': { 41 | 'id': 'priority-6', 42 | 'name': '%priority6', 43 | 'resource': 'priority_6@32.png', 44 | 'svg': 'priority_6@16.svg', 45 | 'index': 5 46 | }, 47 | 'priority-7': { 48 | 'id': 'priority-7', 49 | 'name': '%priority7', 50 | 'resource': 'priority_7@32.png', 51 | 'svg': 'priority_7@16.svg', 52 | 'index': 6 53 | }, 54 | 'priority-8': { 55 | 'id': 'priority-8', 56 | 'name': '%priority8', 57 | 'resource': 'priority_8@32.png', 58 | 'svg': 'priority_8@16.svg', 59 | 'hidden': 'true', 60 | 'index': 7 61 | }, 62 | 'priority-9': { 63 | 'id': 'priority-9', 64 | 'name': '%priority9', 65 | 'resource': 'priority_9@32.png', 66 | 'svg': 'priority_9@16.svg', 67 | 'hidden': 'true', 68 | 'index': 8 69 | } 70 | } 71 | }, 72 | 'smileyMarkers': { 73 | 'id': 'smileyMarkers', 74 | 'markersMap': { 75 | 'smiley-laugh': { 76 | 'id': 'smiley-laugh', 77 | 'name': '%smileyLaugh', 78 | 'resource': 'emotion_laugh@32.png', 79 | 'svg': 'emotion_laugh@16.svg', 80 | 'index': 9 81 | }, 82 | 'smiley-smile': { 83 | 'id': 'smiley-smile', 84 | 'name': '%smileySmile', 85 | 'resource': 'emotion_smile@32.png', 86 | 'svg': 'emotion_smile@16.svg', 87 | 'index': 10 88 | }, 89 | 'smiley-cry': { 90 | 'id': 'smiley-cry', 91 | 'name': '%smileyCry', 92 | 'resource': 'emotion_cry@32.png', 93 | 'svg': 'emotion_cry@16.svg', 94 | 'index': 11 95 | }, 96 | 'smiley-surprise': { 97 | 'id': 'smiley-surprise', 98 | 'name': '%smileySurprise', 99 | 'resource': 'emotion_surprise@32.png', 100 | 'svg': 'emotion_surprise@16.svg', 101 | 'index': 12 102 | }, 103 | 'smiley-boring': { 104 | 'id': 'smiley-boring', 105 | 'name': '%smileyBoring', 106 | 'resource': 'emotion_boring@32.png', 107 | 'svg': 'emotion_boring@16.svg', 108 | 'index': 13 109 | }, 110 | 'smiley-angry': { 111 | 'id': 'smiley-angry', 112 | 'name': '%smileyAngry', 113 | 'resource': 'emotion_angry@32.png', 114 | 'svg': 'emotion_angry@16.svg', 115 | 'index': 14 116 | }, 117 | 'smiley-embarrass': { 118 | 'id': 'smiley-embarrass', 119 | 'name': '%smileyEmbarrass', 120 | 'resource': 'emotion_embarrass@16.png', 121 | 'svg': 'emotion_embarrass@16.svg', 122 | 'index': 15 123 | } 124 | } 125 | }, 126 | 'taskMakers': { 127 | 'id': 'taskMakers', 128 | 'markersMap': { 129 | 'task-start': { 130 | 'id': 'task-start', 131 | 'name': '%taskStart', 132 | 'resource': 'progress_start@32.png', 133 | 'svg': 'progress_start@16.svg', 134 | 'index': 16 135 | }, 136 | 'task-oct': { 137 | 'id': 'task-oct', 138 | 'name': '%taskOct', 139 | 'resource': 'progress_1o@32.png', 140 | 'svg': 'progress_1o@16.svg', 141 | 'index': 17 142 | }, 143 | 'task-quarter': { 144 | 'id': 'task-quarter', 145 | 'name': '%taskQuarter', 146 | 'resource': 'progress_1q@32.png', 147 | 'svg': 'progress_1q@16.svg', 148 | 'hidden': 'true', 149 | 'index': 18 150 | }, 151 | 'task-3oct': { 152 | 'id': 'task-3oct', 153 | 'name': '%task3Oct', 154 | 'resource': 'progress_3o@32.png', 155 | 'svg': 'progress_3o@16.svg', 156 | 'index': 19 157 | }, 158 | 'task-half': { 159 | 'id': 'task-half', 160 | 'name': '%taskHalf', 161 | 'resource': 'progress_half@32.png', 162 | 'svg': 'progress_half@16.svg', 163 | 'index': 20 164 | }, 165 | 'task-5oct': { 166 | 'id': 'task-5oct', 167 | 'name': '%task5Oct', 168 | 'resource': 'progress_5o@32.png', 169 | 'svg': 'progress_5o@16.svg', 170 | 'index': 21 171 | }, 172 | 'task-3quar': { 173 | 'id': 'task-3quar', 174 | 'name': '%task3Quarters', 175 | 'resource': 'progress_3q@32.png', 176 | 'svg': 'progress_3q@16.svg', 177 | 'hidden': 'true', 178 | 'index': 22 179 | }, 180 | 'task-7oct': { 181 | 'id': 'task-7oct', 182 | 'name': '%task7Oct', 183 | 'resource': 'progress_7o@32.png', 184 | 'svg': 'progress_7o@16.svg', 185 | 'index': 23 186 | }, 187 | 'task-done': { 188 | 'id': 'task-done', 189 | 'name': '%taskDone', 190 | 'resource': 'progress_done@32.png', 191 | 'svg': 'progress_done@16.svg', 192 | 'index': 24 193 | }, 194 | 'task-pause': { 195 | 'id': 'task-pause', 196 | 'name': '%taskPause', 197 | 'resource': 'progress_pause@32.png', 198 | 'hidden': 'true', 199 | 'index': 25 200 | } 201 | } 202 | }, 203 | 'flagMakers': { 204 | 'id': 'flagMakers', 205 | 'markersMap': { 206 | 'flag-red': { 207 | 'id': 'flag-red', 208 | 'name': '%colorRed', 209 | 'resource': 'flag_red@32.png', 210 | 'svg': 'flag_red@16.svg', 211 | 'index': 26 212 | }, 213 | 'flag-orange': { 214 | 'id': 'flag-orange', 215 | 'name': '%colorOrange', 216 | 'resource': 'flag_orange@32.png', 217 | 'svg': 'flag_orange@16.svg', 218 | 'index': 27 219 | }, 220 | 'flag-yellow': { 221 | 'id': 'flag-yellow', 222 | 'name': '%colorYellow', 223 | 'resource': 'flag_yellow@32.png', 224 | 'svg': 'flag_yellow@16.svg', 225 | 'hidden': 'true', 226 | 'index': 28 227 | }, 228 | 'flag-dark-blue': { 229 | 'id': 'flag-dark-blue', 230 | 'name': '%colorDarkBlue', 231 | 'resource': 'flag_dark_blue@32.png', 232 | 'svg': 'flag_dark_blue@16.svg', 233 | 'index': 29 234 | }, 235 | 'flag-purple': { 236 | 'id': 'flag-purple', 237 | 'name': '%colorPurple', 238 | 'resource': 'flag_purple@32.png', 239 | 'svg': 'flag_purple@16.svg', 240 | 'index': 30 241 | }, 242 | 'flag-green': { 243 | 'id': 'flag-green', 244 | 'name': '%colorGreen', 245 | 'resource': 'flag_green@32.png', 246 | 'svg': 'flag_green@16.svg', 247 | 'index': 31 248 | }, 249 | 'flag-blue': { 250 | 'id': 'flag-blue', 251 | 'name': '%colorBlue', 252 | 'resource': 'flag_blue@32.png', 253 | 'svg': 'flag_blue@16.svg', 254 | 'index': 32 255 | }, 256 | 'flag-gray': { 257 | 'id': 'flag-gray', 258 | 'name': '%colorGray', 259 | 'resource': 'flag_gray@32.png', 260 | 'svg': 'flag_gray@16.svg', 261 | 'index': 33 262 | }, 263 | 'flag-dark-green': { 264 | 'id': 'flag-dark-green', 265 | 'name': '%colorDarkGreen', 266 | 'resource': 'flag_dark_green@16.png', 267 | 'svg': 'flag_dark_green@16.svg', 268 | 'hidden': 'true', 269 | 'index': 34 270 | }, 271 | 'flag-dark-gray': { 272 | 'id': 'flag-dark-gray', 273 | 'name': '%colorDarkGray', 274 | 'resource': 'flag_dark_gray@16.png', 275 | 'svg': 'flag_dark_gray@16.svg', 276 | 'hidden': 'true', 277 | 'index': 35 278 | } 279 | } 280 | }, 281 | 'starMakers': { 282 | 'id': 'starMakers', 283 | 'markersMap': { 284 | 'star-red': { 285 | 'id': 'star-red', 286 | 'name': '%colorRed', 287 | 'resource': 'star_red@32.png', 288 | 'svg': 'star_red@16.svg', 289 | 'index': 36 290 | }, 291 | 'star-orange': { 292 | 'id': 'star-orange', 293 | 'name': '%colorOrange', 294 | 'resource': 'star_orange@32.png', 295 | 'svg': 'star_orange@16.svg', 296 | 'index': 37 297 | }, 298 | 'star-dark-blue': { 299 | 'id': 'star-dark-blue', 300 | 'name': '%colorDarkBlue', 301 | 'resource': 'star_dark_blue@32.png', 302 | 'svg': 'star_dark_blue@16.svg', 303 | 'index': 38 304 | }, 305 | 'star-purple': { 306 | 'id': 'star-purple', 307 | 'name': '%colorPurple', 308 | 'resource': 'star_purple@32.png', 309 | 'svg': 'star_purple@16.svg', 310 | 'index': 39 311 | }, 312 | 'star-green': { 313 | 'id': 'star-green', 314 | 'name': '%colorGreen', 315 | 'resource': 'star_green@32.png', 316 | 'svg': 'star_green@16.svg', 317 | 'index': 40 318 | }, 319 | 'star-blue': { 320 | 'id': 'star-blue', 321 | 'name': '%colorBlue', 322 | 'resource': 'star_blue@32.png', 323 | 'svg': 'star_blue@16.svg', 324 | 'index': 41 325 | }, 326 | 'star-gray': { 327 | 'id': 'star-gray', 328 | 'name': '%colorGray', 329 | 'resource': 'star_gray@32.png', 330 | 'svg': 'star_gray@16.svg', 331 | 'index': 42 332 | }, 333 | 'star-yellow': { 334 | 'id': 'star-yellow', 335 | 'name': '%colorYellow', 336 | 'resource': 'star_yellow@32.png', 337 | 'svg': 'star_yellow@16.svg', 338 | 'hidden': 'true', 339 | 'index': 43 340 | }, 341 | 'star-dark-green': { 342 | 'id': 'star-dark-green', 343 | 'name': '%colorDarkGreen', 344 | 'resource': 'star_dark_green@16.png', 345 | 'svg': 'star_dark_green@16.svg', 346 | 'hidden': 'true', 347 | 'index': 44 348 | }, 349 | 'star-dark-gray': { 350 | 'id': 'star-dark-gray', 351 | 'name': '%colorDarkGray', 352 | 'resource': 'star_dark_gray@16.png', 353 | 'svg': 'star_dark_gray@16.svg', 354 | 'hidden': 'true', 355 | 'index': 45 356 | } 357 | } 358 | }, 359 | 'peopleMakers': { 360 | 'id': 'peopleMakers', 361 | 'markersMap': { 362 | 'people-red': { 363 | 'id': 'people-red', 364 | 'name': '%colorRed', 365 | 'resource': 'people_red@32.png', 366 | 'svg': 'people_red@16.svg', 367 | 'index': 46 368 | }, 369 | 'people-orange': { 370 | 'id': 'people-orange', 371 | 'name': '%colorOrange', 372 | 'resource': 'people_orange@32.png', 373 | 'svg': 'people_orange@16.svg', 374 | 'index': 47 375 | }, 376 | 'people-yellow': { 377 | 'id': 'people-yellow', 378 | 'name': '%colorYellow', 379 | 'resource': 'people_yellow@32.png', 380 | 'svg': 'people_yellow@16.svg', 381 | 'hidden': 'true', 382 | 'index': 48 383 | }, 384 | 'people-dark-blue': { 385 | 'id': 'people-dark-blue', 386 | 'name': '%colorDarkBlue', 387 | 'resource': 'people_dark_blue@32.png', 388 | 'svg': 'people_dark_blue@16.svg', 389 | 'index': 49 390 | }, 391 | 'people-purple': { 392 | 'id': 'people-purple', 393 | 'name': '%colorPurple', 394 | 'resource': 'people_purple@32.png', 395 | 'svg': 'people_purple@16.svg', 396 | 'index': 50 397 | }, 398 | 'people-green': { 399 | 'id': 'people-green', 400 | 'name': '%colorGreen', 401 | 'resource': 'people_green@32.png', 402 | 'svg': 'people_green@16.svg', 403 | 'index': 51 404 | }, 405 | 'people-blue': { 406 | 'id': 'people-blue', 407 | 'name': '%colorBlue', 408 | 'resource': 'people_blue@32.png', 409 | 'svg': 'people_blue@16.svg', 410 | 'index': 52 411 | }, 412 | 'people-gray': { 413 | 'id': 'people-gray', 414 | 'name': '%colorGray', 415 | 'resource': 'people_gray@32.png', 416 | 'svg': 'people_gray@16.svg', 417 | 'index': 53 418 | }, 419 | 'people-dark-green': { 420 | 'id': 'people-dark-green', 421 | 'name': '%colorDarkGreen', 422 | 'resource': 'people_dark_green@16.png', 423 | 'svg': 'people_dark_green@16.svg', 424 | 'hidden': 'true', 425 | 'index': 54 426 | }, 427 | 'people-dark-gray': { 428 | 'id': 'people-dark-gray', 429 | 'name': '%colorDarkGray', 430 | 'resource': 'people_dark_gray@16.png', 431 | 'svg': 'people_dark_gray@16.svg', 432 | 'hidden': 'true', 433 | 'index': 55 434 | } 435 | } 436 | }, 437 | 'arrowMakers': { 438 | 'id': 'arrowMakers', 439 | 'markersMap': { 440 | 'arrow-left': { 441 | 'id': 'arrow-left', 442 | 'name': '%arrowLeft', 443 | 'resource': 'arrow_left@32.png', 444 | 'svg': 'arrow_left@16.svg', 445 | 'index': 56 446 | }, 447 | 'arrow-right': { 448 | 'id': 'arrow-right', 449 | 'name': '%arrowRight', 450 | 'resource': 'arrow_right@32.png', 451 | 'svg': 'arrow_right@16.svg', 452 | 'index': 57 453 | }, 454 | 'arrow-up': { 455 | 'id': 'arrow-up', 456 | 'name': '%arrowUp', 457 | 'resource': 'arrow_up@32.png', 458 | 'svg': 'arrow_up@16.svg', 459 | 'index': 58 460 | }, 461 | 'arrow-down': { 462 | 'id': 'arrow-down', 463 | 'name': '%arrowDown', 464 | 'resource': 'arrow_down@32.png', 465 | 'svg': 'arrow_down@16.svg', 466 | 'index': 59 467 | }, 468 | 'arrow-left-right': { 469 | 'id': 'arrow-left-right', 470 | 'name': '%arrowLeftRight', 471 | 'resource': 'arrow_left_right@32.png', 472 | 'svg': 'arrow_left_right@16.svg', 473 | 'index': 60 474 | }, 475 | 'arrow-up-down': { 476 | 'id': 'arrow-up-down', 477 | 'name': '%arrowUpDown', 478 | 'resource': 'arrow_up_down@32.png', 479 | 'svg': 'arrow_up_down@16.svg', 480 | 'index': 61 481 | }, 482 | 'arrow-refresh': { 483 | 'id': 'arrow-refresh', 484 | 'name': '%arrowRefresh', 485 | 'resource': 'arrow_refresh@32.png', 486 | 'svg': 'arrow_refresh@16.svg', 487 | 'index': 62 488 | }, 489 | 'arrow-up-right': { 490 | 'id': 'arrow-up-right', 491 | 'name': '%arrowUpRight', 492 | 'resource': 'arrow_up_right@32.png', 493 | 'svg': 'arrow_up_right@16.svg', 494 | 'hidden': 'true', 495 | 'index': 63 496 | }, 497 | 'arrow-down-right': { 498 | 'id': 'arrow-down-right', 499 | 'name': '%arrowDownRight', 500 | 'resource': 'arrow_down_right@32.png', 501 | 'svg': 'arrow_down_right@16.svg', 502 | 'hidden': 'true', 503 | 'index': 64 504 | }, 505 | 'arrow-down-left': { 506 | 'id': 'arrow-down-left', 507 | 'name': '%arrowDownLeft', 508 | 'resource': 'arrow_down_left@32.png', 509 | 'svg': 'arrow_down_left@16.svg', 510 | 'hidden': 'true', 511 | 'index': 65 512 | }, 513 | 'arrow-up-left': { 514 | 'id': 'arrow-up-left', 515 | 'name': '%arrowUpLeft', 516 | 'resource': 'arrow_up_left@32.png', 517 | 'svg': 'arrow_up_left@16.svg', 518 | 'hidden': 'true', 519 | 'index': 66 520 | } 521 | } 522 | }, 523 | 'symbolMakers': { 524 | 'id': 'symbolMakers', 525 | 'markersMap': { 526 | 'c_symbol_heart': { 527 | 'id': 'c_symbol_heart', 528 | 'name': '%symbolHeart', 529 | 'resource': 'symbol_heart@16.png', 530 | 'svg': 'symbol_heart.svg', 531 | 'index': 67 532 | }, 533 | 'c_symbol_dislike': { 534 | 'id': 'c_symbol_dislike', 535 | 'name': '%symbolDislike', 536 | 'resource': 'symbol_dislike@16.png', 537 | 'svg': 'symbol_dislike.svg', 538 | 'index': 68 539 | }, 540 | 'c_symbol_like': { 541 | 'id': 'c_symbol_like', 542 | 'name': '%symbolLike', 543 | 'resource': 'symbol_like@16.png', 544 | 'svg': 'symbol_like.svg', 545 | 'index': 69 546 | }, 547 | 'c_symbol_music': { 548 | 'id': 'c_symbol_music', 549 | 'name': '%symbolMusic', 550 | 'resource': 'symbol_music@16.png', 551 | 'svg': 'symbol_music.svg', 552 | 'index': 70 553 | }, 554 | 'c_symbol_lock': { 555 | 'id': 'c_symbol_lock', 556 | 'name': '%symbolLock', 557 | 'svg': 'symbol_lock.svg', 558 | 'index': 71 559 | }, 560 | 'c_symbol_hourglass': { 561 | 'id': 'c_symbol_hourglass', 562 | 'name': '%symbolHourglass', 563 | 'svg': 'symbol_hourglass.svg', 564 | 'index': 72 565 | }, 566 | 'c_symbol_broken_heart': { 567 | 'id': 'c_symbol_broken_heart', 568 | 'name': '%symbolBrokenHeart', 569 | 'svg': 'symbol_broken_heart.svg', 570 | 'index': 73 571 | }, 572 | 'c_symbol_quote': { 573 | 'id': 'c_symbol_quote', 574 | 'name': '%symbolQuote', 575 | 'svg': 'symbol_quote.svg', 576 | 'index': 74 577 | }, 578 | 'c_symbol_apostrophe': { 579 | 'id': 'c_symbol_apostrophe', 580 | 'name': '%symbolApostrophe', 581 | 'svg': 'symbol_apostrophe.svg', 582 | 'index': 75 583 | }, 584 | 'symbol-question': { 585 | 'id': 'symbol-question', 586 | 'name': '%symbolQuestion', 587 | 'resource': 'symbol_question@32.png', 588 | 'svg': 'symbol_question@16.svg', 589 | 'index': 76 590 | }, 591 | 'symbol-attention': { 592 | 'id': 'symbol-attention', 593 | 'name': '%symbolAttention', 594 | 'resource': 'symbol_attention.png', 595 | 'svg': 'symbol_attention@16.svg', 596 | 'index': 77 597 | }, 598 | 'symbol-wrong': { 599 | 'id': 'symbol-wrong', 600 | 'name': '%symbolWrong', 601 | 'resource': 'symbol_wrong@32.png', 602 | 'svg': 'symbol_wrong@16.svg', 603 | 'index': 78 604 | }, 605 | 'symbol-pause': { 606 | 'id': 'symbol-pause', 607 | 'name': '%symbolPause', 608 | 'resource': 'symbol_pause@32.png', 609 | 'svg': 'symbol_pause@16.svg', 610 | 'index': 79 611 | }, 612 | 'symbol-no-entry': { 613 | 'id': 'symbol-no-entry', 614 | 'name': '%symbolNoEntry', 615 | 'svg': 'symbol_no_entry@16.svg', 616 | 'index': 80 617 | }, 618 | 'symbol-plus': { 619 | 'id': 'symbol-plus', 620 | 'name': '%symbolPlus', 621 | 'resource': 'symbol_plus@32.png', 622 | 'svg': 'symbol_plus@16.svg', 623 | 'index': 81 624 | }, 625 | 'symbol-minus': { 626 | 'id': 'symbol-minus', 627 | 'name': '%symbolMinus', 628 | 'resource': 'symbol_minus@32.png', 629 | 'svg': 'symbol_minus@16.svg', 630 | 'index': 82 631 | }, 632 | 'symbol-info': { 633 | 'id': 'symbol-info', 634 | 'name': '%symbolInfo', 635 | 'resource': 'symbol_information@32.png', 636 | 'svg': 'symbol_information@16.svg', 637 | 'index': 83 638 | }, 639 | 'symbol-divide': { 640 | 'id': 'symbol-divide', 641 | 'name': '%symbolDivide', 642 | 'svg': 'symbol_divide@16.svg', 643 | 'index': 84 644 | }, 645 | 'symbol-equality': { 646 | 'id': 'symbol-equality', 647 | 'name': '%symbolEquality', 648 | 'svg': 'symbol_equality@16.svg', 649 | 'index': 85 650 | }, 651 | 'symbol-right': { 652 | 'id': 'symbol-right', 653 | 'name': '%symbolRight', 654 | 'resource': 'symbol_right@32.png', 655 | 'svg': 'symbol_right@16.svg', 656 | 'index': 86 657 | }, 658 | 'symbol-code': { 659 | 'id': 'symbol-code', 660 | 'name': '%symbolCode', 661 | 'svg': 'symbol_code@16.svg', 662 | 'index': 87 663 | }, 664 | 'c_symbol_contact': { 665 | 'id': 'c_symbol_contact', 666 | 'name': '%symbolContact', 667 | 'resource': 'symbol_contact@16.png', 668 | 'svg': 'symbol_contact.svg', 669 | 'index': 88 670 | }, 671 | 'c_symbol_telephone': { 672 | 'id': 'c_symbol_telephone', 673 | 'name': '%symbolTelephone', 674 | 'resource': 'symbol_telephone@16.png', 675 | 'svg': 'symbol_telephone.svg', 676 | 'index': 89 677 | }, 678 | 'c_symbol_pen': { 679 | 'id': 'c_symbol_pen', 680 | 'name': '%symbolPen', 681 | 'resource': 'symbol_pen@16.png', 682 | 'svg': 'symbol_pen.svg', 683 | 'index': 90 684 | }, 685 | 'c_symbol_money': { 686 | 'id': 'c_symbol_money', 687 | 'name': '%symbolMoney', 688 | 'resource': 'symbol_money@16.png', 689 | 'svg': 'symbol_money.svg', 690 | 'index': 91 691 | }, 692 | 'c_symbol_bar_chart': { 693 | 'id': 'c_symbol_bar_chart', 694 | 'name': '%symbolBarChart', 695 | 'resource': 'symbol_bar_chart@16.png', 696 | 'svg': 'symbol_bar_chart.svg', 697 | 'index': 92 698 | }, 699 | 'c_symbol_pie_chart': { 700 | 'id': 'c_symbol_pie_chart', 701 | 'name': '%symbolPieChart', 702 | 'resource': 'symbol_pie_chart@16.png', 703 | 'svg': 'symbol_pie_chart.svg', 704 | 'index': 93 705 | }, 706 | 'c_symbol_line_graph': { 707 | 'id': 'c_symbol_line_graph', 708 | 'name': '%symbolLineGraph', 709 | 'resource': 'symbol_line_graph@16.png', 710 | 'svg': 'symbol_line_graph.svg', 711 | 'index': 94 712 | }, 713 | 'c_symbol_shopping_cart': { 714 | 'id': 'c_symbol_shopping_cart', 715 | 'name': '%symbolShoppingCart', 716 | 'resource': 'symbol_shopping_cart@16.png', 717 | 'svg': 'symbol_shopping_cart.svg', 718 | 'index': 95 719 | }, 720 | 'c_symbol_medals': { 721 | 'id': 'c_symbol_medals', 722 | 'name': '%symbolMedals', 723 | 'resource': 'symbol_medals@16.png', 724 | 'svg': 'symbol_medals.svg', 725 | 'index': 96 726 | }, 727 | 'c_symbol_trophy': { 728 | 'id': 'c_symbol_trophy', 729 | 'name': '%symbolTrophy', 730 | 'resource': 'symbol_trophy@16.png', 731 | 'svg': 'symbol_trophy.svg', 732 | 'index': 97 733 | }, 734 | 'symbol-image': { 735 | 'id': 'symbol-image', 736 | 'name': '%symbolImage', 737 | 'svg': 'symbol_image@16.svg', 738 | 'index': 98 739 | }, 740 | 'c_symbol_exercise': { 741 | 'id': 'c_symbol_exercise', 742 | 'name': '%symbolExercise', 743 | 'resource': 'symbol_exercise@16.png', 744 | 'svg': 'symbol_exercise.svg', 745 | 'index': 99 746 | }, 747 | 'c_symbol_flight': { 748 | 'id': 'c_symbol_flight', 749 | 'name': '%symbolFlight', 750 | 'resource': 'symbol_flight@16.png', 751 | 'svg': 'symbol_flight.svg', 752 | 'index': 100 753 | }, 754 | 'symbol-pin': { 755 | 'id': 'symbol-pin', 756 | 'name': '%symbolPin', 757 | 'svg': 'symbol_pin@16.svg', 758 | 'index': 101 759 | }, 760 | 'symbol-exclam': { 761 | 'id': 'symbol-exclam', 762 | 'name': '%symbolExclam', 763 | 'resource': 'symbol_exclamation@32.png', 764 | 'svg': 'symbol_exclamation@16.svg', 765 | 'index': 102, 766 | 'hidden': 'true' 767 | }, 768 | 'c_simbol-plus': { 769 | 'id': 'c_simbol-plus', 770 | 'name': '%symbolPlus', 771 | 'resource': 'c_simbol_plus@32.png', 772 | 'svg': 'symbol_plus@16.svg', 773 | 'index': 103, 774 | 'hidden': 'true' 775 | }, 776 | 'c_simbol-minus': { 777 | 'id': 'c_simbol-minus', 778 | 'name': '%symbolMinus', 779 | 'resource': 'c_simbol_minus@32.png', 780 | 'svg': 'symbol_minus@16.svg', 781 | 'index': 104, 782 | 'hidden': 'true' 783 | }, 784 | 'c_simbol-question': { 785 | 'id': 'c_simbol-question', 786 | 'name': '%symbolQuestion', 787 | 'resource': 'c_simbol_question@32.png', 788 | 'svg': 'c_simbol_question@16.svg', 789 | 'index': 105, 790 | 'hidden': 'true' 791 | }, 792 | 'c_simbol-exclam': { 793 | 'id': 'c_simbol-exclam', 794 | 'name': '%symbolExclam', 795 | 'resource': 'c_simbol_exclamation@32.png', 796 | 'svg': 'c_simbol_exclamation@16.svg', 797 | 'index': 106, 798 | 'hidden': 'true' 799 | }, 800 | 'c_simbol-info': { 801 | 'id': 'c_simbol-info', 802 | 'name': '%symbolInfo', 803 | 'resource': 'c_simbol_information@32.png', 804 | 'svg': 'symbol_information@16.svg', 805 | 'index': 107, 806 | 'hidden': 'true' 807 | }, 808 | 'c_simbol-wrong': { 809 | 'id': 'c_simbol-wrong', 810 | 'name': '%symbolWrong', 811 | 'resource': 'c_simbol_wrong@32.png', 812 | 'svg': 'c_simbol_wrong@16.svg', 813 | 'index': 108, 814 | 'hidden': 'true' 815 | }, 816 | 'c_simbol-right': { 817 | 'id': 'c_simbol-right', 818 | 'name': '%symbolRight', 819 | 'resource': 'c_simbol_right@32.png', 820 | 'svg': 'symbol_right@16.svg', 821 | 'index': 109, 822 | 'hidden': 'true' 823 | }, 824 | 'c_simbol-pause': { 825 | 'id': 'c_simbol-pause', 826 | 'name': '%symbolPause', 827 | 'resource': 'c_simbol_pause@32.png', 828 | 'svg': 'symbol_pause@16.svg', 829 | 'index': 110, 830 | 'hidden': 'true' 831 | }, 832 | 'c_symbol_thermometer': { 833 | 'id': 'c_symbol_thermometer', 834 | 'name': '%symbolThermometer', 835 | 'resource': 'symbol_thermometer@16.png', 836 | 'svg': 'symbol_thermometer.svg', 837 | 'index': 111, 838 | 'hidden': 'true' 839 | } 840 | } 841 | }, 842 | 'monthMakers': { 843 | 'id': 'monthMakers', 844 | 'markersMap': { 845 | 'month-jan': { 846 | 'id': 'month-jan', 847 | 'name': '%monthJan', 848 | 'resource': 'month_jan@32.png', 849 | 'svg': 'month_jan@16.svg', 850 | 'index': 112 851 | }, 852 | 'month-feb': { 853 | 'id': 'month-feb', 854 | 'name': '%monthFeb', 855 | 'resource': 'month_feb@32.png', 856 | 'svg': 'month_feb@16.svg', 857 | 'index': 113 858 | }, 859 | 'month-mar': { 860 | 'id': 'month-mar', 861 | 'name': '%monthMar', 862 | 'resource': 'month_mar@32.png', 863 | 'svg': 'month_mar@16.svg', 864 | 'index': 114 865 | }, 866 | 'month-apr': { 867 | 'id': 'month-apr', 868 | 'name': '%monthApr', 869 | 'resource': 'month_apr@32.png', 870 | 'svg': 'month_apr@16.svg', 871 | 'index': 115 872 | }, 873 | 'month-may': { 874 | 'id': 'month-may', 875 | 'name': '%monthMay', 876 | 'resource': 'month_may@32.png', 877 | 'svg': 'month_may@16.svg', 878 | 'index': 116 879 | }, 880 | 'month-jun': { 881 | 'id': 'month-jun', 882 | 'name': '%monthJun', 883 | 'resource': 'month_jun@32.png', 884 | 'svg': 'month_jun@16.svg', 885 | 'index': 117 886 | }, 887 | 'month-jul': { 888 | 'id': 'month-jul', 889 | 'name': '%monthJul', 890 | 'resource': 'month_jul@32.png', 891 | 'svg': 'month_jul@16.svg', 892 | 'index': 118 893 | }, 894 | 'month-aug': { 895 | 'id': 'month-aug', 896 | 'name': '%monthAug', 897 | 'resource': 'month_aug@32.png', 898 | 'svg': 'month_aug@16.svg', 899 | 'index': 119 900 | }, 901 | 'month-sep': { 902 | 'id': 'month-sep', 903 | 'name': '%monthSep', 904 | 'resource': 'month_sep@32.png', 905 | 'svg': 'month_sep@16.svg', 906 | 'index': 120 907 | }, 908 | 'month-oct': { 909 | 'id': 'month-oct', 910 | 'name': '%monthOct', 911 | 'resource': 'month_oct@32.png', 912 | 'svg': 'month_oct@16.svg', 913 | 'index': 121 914 | }, 915 | 'month-nov': { 916 | 'id': 'month-nov', 917 | 'name': '%monthNov', 918 | 'resource': 'month_nov@32.png', 919 | 'svg': 'month_nov@16.svg', 920 | 'index': 122 921 | }, 922 | 'month-dec': { 923 | 'id': 'month-dec', 924 | 'name': '%monthDec', 925 | 'resource': 'month_dec@32.png', 926 | 'svg': 'month_dec@16.svg', 927 | 'index': 123 928 | } 929 | } 930 | }, 931 | 'weekMakers': { 932 | 'id': 'weekMakers', 933 | 'markersMap': { 934 | 'week-sun': { 935 | 'id': 'week-sun', 936 | 'name': '%weekSun', 937 | 'resource': 'week_sun@32.png', 938 | 'svg': 'week_sun@16.svg', 939 | 'index': 124 940 | }, 941 | 'week-mon': { 942 | 'id': 'week-mon', 943 | 'name': '%weekMon', 944 | 'resource': 'week_mon@32.png', 945 | 'svg': 'week_mon@16.svg', 946 | 'index': 125 947 | }, 948 | 'week-tue': { 949 | 'id': 'week-tue', 950 | 'name': '%weekTue', 951 | 'resource': 'week_tue@32.png', 952 | 'svg': 'week_tue@16.svg', 953 | 'index': 126 954 | }, 955 | 'week-wed': { 956 | 'id': 'week-wed', 957 | 'name': '%weekWed', 958 | 'resource': 'week_wed@32.png', 959 | 'svg': 'week_wed@16.svg', 960 | 'index': 127 961 | }, 962 | 'week-thu': { 963 | 'id': 'week-thu', 964 | 'name': '%weekThu', 965 | 'resource': 'week_thu@32.png', 966 | 'svg': 'week_thu@16.svg', 967 | 'index': 128 968 | }, 969 | 'week-fri': { 970 | 'id': 'week-fri', 971 | 'name': '%weekFri', 972 | 'resource': 'week_fri@32.png', 973 | 'svg': 'week_fri@16.svg', 974 | 'index': 129 975 | }, 976 | 'week-sat': { 977 | 'id': 'week-sat', 978 | 'name': '%weekSat', 979 | 'resource': 'week_sat@32.png', 980 | 'svg': 'week_sat@16.svg', 981 | 'index': 130 982 | } 983 | } 984 | }, 985 | 'halfStarMarkers': { 986 | 'id': 'halfStarMarkers', 987 | 'hidden': 'true', 988 | 'markersMap': { 989 | 'half-star-green': { 990 | 'id': 'half-star-green', 991 | 'name': '%colorGreen', 992 | 'resource': 'star_green_half.png', 993 | 'index': 131 994 | }, 995 | 'half-star-red': { 996 | 'id': 'half-star-red', 997 | 'name': '%colorRed', 998 | 'resource': 'star_red_half.png', 999 | 'index': 132 1000 | }, 1001 | 'half-star-yellow': { 1002 | 'id': 'half-star-yellow', 1003 | 'name': '%colorYellow', 1004 | 'resource': 'star_yellow_half.png', 1005 | 'index': 133 1006 | }, 1007 | 'half-star-purple': { 1008 | 'id': 'half-star-purple', 1009 | 'name': '%colorPurple', 1010 | 'resource': 'star_purple_half.png', 1011 | 'index': 134 1012 | }, 1013 | 'half-star-blue': { 1014 | 'id': 'half-star-blue', 1015 | 'name': '%colorBlue', 1016 | 'resource': 'star_blue_half.png', 1017 | 'index': 135 1018 | }, 1019 | 'half-star-gray': { 1020 | 'id': 'half-star-gray', 1021 | 'name': '%colorGray', 1022 | 'resource': 'star_gray_half.png', 1023 | 'index': 136 1024 | } 1025 | } 1026 | }, 1027 | 'otherMarkers': { 1028 | 'id': 'otherMarkers', 1029 | 'hidden': 'true', 1030 | 'markersMap': { 1031 | 'other-calendar': { 1032 | 'id': 'other-calendar', 1033 | 'name': '%otherCalendar', 1034 | 'resource': 'other_calendar.png', 1035 | 'index': 137 1036 | }, 1037 | 'other-email': { 1038 | 'id': 'other-email', 1039 | 'name': '%otherEmail', 1040 | 'resource': 'other_email.png', 1041 | 'index': 138 1042 | }, 1043 | 'other-phone': { 1044 | 'id': 'other-phone', 1045 | 'name': '%otherPhone', 1046 | 'resource': 'other_phone.png', 1047 | 'index': 139 1048 | }, 1049 | 'other-phone2': { 1050 | 'id': 'other-phone2', 1051 | 'name': '%otherPhone', 1052 | 'resource': 'other_phone2.png', 1053 | 'index': 140 1054 | }, 1055 | 'other-fax': { 1056 | 'id': 'other-fax', 1057 | 'name': '%otherFax', 1058 | 'resource': 'other_print.png', 1059 | 'index': 141 1060 | }, 1061 | 'other-people': { 1062 | 'id': 'other-people', 1063 | 'name': '%otherPeople', 1064 | 'resource': 'people_green@32.png', 1065 | 'index': 142 1066 | }, 1067 | 'other-people2': { 1068 | 'id': 'other-people2', 1069 | 'name': '%otherPeople', 1070 | 'resource': 'people_blue@32.png', 1071 | 'index': 143 1072 | }, 1073 | 'other-clock': { 1074 | 'id': 'other-clock', 1075 | 'name': '%otherClock', 1076 | 'resource': 'other_clock.png', 1077 | 'index': 144 1078 | }, 1079 | 'other-coffee-cup': { 1080 | 'id': 'other-coffee-cup', 1081 | 'name': '%otherCoffeeCup', 1082 | 'resource': 'other_coffee.png', 1083 | 'index': 145 1084 | }, 1085 | 'other-question': { 1086 | 'id': 'other-question', 1087 | 'name': '%otherQuestion', 1088 | 'resource': 'symbol_question@32.png', 1089 | 'svg': 'symbol_question@16.svg', 1090 | 'index': 146 1091 | }, 1092 | 'other-exclam': { 1093 | 'id': 'other-exclam', 1094 | 'name': '%otherExclam', 1095 | 'resource': 'symbol_exclamation@32.png', 1096 | 'svg': 'symbol_exclamation@16.svg', 1097 | 'index': 147 1098 | }, 1099 | 'other-lightbulb': { 1100 | 'id': 'other-lightbulb', 1101 | 'name': '%otherLightBulb', 1102 | 'resource': 'other_buble.png', 1103 | 'index': 148 1104 | }, 1105 | 'other-businesscard': { 1106 | 'id': 'other-businesscard', 1107 | 'name': '%otherBusinessCard', 1108 | 'resource': 'other_businesscard.png', 1109 | 'index': 149 1110 | }, 1111 | 'other-social': { 1112 | 'id': 'other-social', 1113 | 'name': '%otherSocial', 1114 | 'resource': 'other_social.png', 1115 | 'index': 150 1116 | }, 1117 | 'other-chat': { 1118 | 'id': 'other-chat', 1119 | 'name': '%otherChat', 1120 | 'resource': 'other_chat.png', 1121 | 'index': 151 1122 | }, 1123 | 'other-note': { 1124 | 'id': 'other-note', 1125 | 'name': '%otherNote', 1126 | 'resource': 'other_note.png', 1127 | 'index': 152 1128 | }, 1129 | 'other-lock': { 1130 | 'id': 'other-lock', 1131 | 'name': '%otherLock', 1132 | 'resource': 'other_lock.png', 1133 | 'index': 153 1134 | }, 1135 | 'other-unlock': { 1136 | 'id': 'other-unlock', 1137 | 'name': '%otherUnlock', 1138 | 'resource': 'other_unlock.png', 1139 | 'index': 154 1140 | }, 1141 | 'other-yes': { 1142 | 'id': 'other-yes', 1143 | 'name': '%otherYes', 1144 | 'resource': 'symbol_right@32.png', 1145 | 'svg': 'symbol_right@16.svg', 1146 | 'index': 155 1147 | }, 1148 | 'other-no': { 1149 | 'id': 'other-no', 1150 | 'name': '%otherNo', 1151 | 'resource': 'symbol_wrong@32.png', 1152 | 'svg': 'symbol_wrong@16.svg', 1153 | 'index': 156 1154 | }, 1155 | 'other-bomb': { 1156 | 'id': 'other-bomb', 1157 | 'name': '%otherBomb', 1158 | 'resource': 'other_bomb.png', 1159 | 'index': 157 1160 | } 1161 | } 1162 | } 1163 | }; 1164 | 1165 | const icons = {}; 1166 | const iterable = {}; 1167 | 1168 | for(const key in marker) { 1169 | const realGroupId = key; 1170 | const map = marker[key].markersMap; 1171 | 1172 | for(const markerId in map) { 1173 | if (!map.hasOwnProperty(markerId)) continue; 1174 | if (markerId.startsWith('c_')) continue; 1175 | const arr = markerId.split('-'); 1176 | const collection = arr[0]; 1177 | const name = arr.length > 2 ? arr.slice(1, arr.length).join('-') : arr[1]; 1178 | 1179 | if (!iterable[collection]) { 1180 | iterable[collection] = []; 1181 | } 1182 | 1183 | if (!icons[collection]) { 1184 | icons[collection] = {}; 1185 | } 1186 | 1187 | iterable[collection].push(name); 1188 | icons[collection][name] = { groupId: realGroupId, markerId: markerId }; 1189 | } 1190 | } 1191 | 1192 | export { icons, iterable }; -------------------------------------------------------------------------------- /src/common/model.ts: -------------------------------------------------------------------------------- 1 | /* Basic type alias */ 2 | type Extension = ExtensionTextNode | ExtensionChildNode; 3 | type ItemInformation = Array<{type: string, mode: string}>; 4 | type TabColor = Array<{rgb: string}>; 5 | type Markers = {'': {name: string, resource: string}}; 6 | type Angle = {angle: number, amount: number}; 7 | type None = 'none'; 8 | type Plain = { content: string }; 9 | type NoteSpan = TextSpan | ImageSpan | HyperlinkSpan; 10 | 11 | // @ts-ignore 12 | type Model = 13 | Sheet | 14 | SheetSetting | 15 | Relationship | 16 | Legend | 17 | Theme | 18 | Style | 19 | Topic | 20 | Image | 21 | Numbering | 22 | Notes | 23 | Extension; 24 | 25 | export type Whatever = Model; 26 | 27 | export enum VISIBILITY { 28 | HIDDEN = 'hidden', 29 | VISIBLE = 'visible' 30 | } 31 | 32 | export interface TextSpan { 33 | 'style': Style; 34 | 'text': string; 35 | 'class': string; 36 | } 37 | 38 | export interface ImageSpan { 39 | 'style': Style; 40 | 'class': string; 41 | 'image': string; 42 | } 43 | 44 | export interface HyperlinkSpan { 45 | 'style': Style; 46 | 'class': string; 47 | 'href': string; 48 | 'spans': Array; 49 | } 50 | 51 | export interface HTML { 52 | content: { 53 | paragraphs: Array<{ 54 | style: Style; 55 | spans: Array; 56 | }> 57 | } 58 | } 59 | 60 | export interface ControlPoints { 61 | '0': Axis; 62 | '1': Angle; 63 | } 64 | 65 | export interface Axis { 66 | x: number; 67 | y: number; 68 | } 69 | 70 | /* The end of type alias */ 71 | 72 | 73 | /* Model definitions */ 74 | /* Sheet model */ 75 | export interface Sheet { 76 | 'id': string; 77 | 'title': string; 78 | 'rootTopic': Topic; 79 | 'style': Style; 80 | 'topicPositioning': string; 81 | 'topicOverlapping': string; 82 | 'theme': Theme; 83 | 'relationships': Array; 84 | 'legend': Legend; 85 | 'settings': SheetSetting; 86 | } 87 | 88 | 89 | /* Sheet settings model */ 90 | export interface SheetSetting { 91 | 'infoItems/infoItem': ItemInformation; 92 | 'tab-color': TabColor; 93 | } 94 | 95 | /* Theme model */ 96 | export interface Theme { 97 | 'id': string; 98 | 'title': string; 99 | 'map'?: Style; 100 | 'centralTopic'?: Style; 101 | 'mainTopic'?: Style; 102 | 'subTopic'?: Style; 103 | 'floatingTopic'?: Style; 104 | 'centralFloatingTopic'?: Style; 105 | 'boundary'?: Style; 106 | 'relationship'?: Style; 107 | 'summaryTopic'?: Style; 108 | 'summary'?: Style; 109 | } 110 | 111 | /** 112 | * Legend model 113 | */ 114 | export interface Legend { 115 | 'visibility': VISIBILITY.HIDDEN | VISIBILITY.VISIBLE; 116 | 'position': Axis; 117 | 'markers': Markers; 118 | 'groups': Markers; 119 | } 120 | 121 | export interface Topic { 122 | id?: string; 123 | title: string; 124 | style?: Style; 125 | class?: string; 126 | position?: Axis; 127 | structureClass?: string; 128 | branch?: string; 129 | width?: number; 130 | labels?: string; 131 | numbering?: Numbering, 132 | href?: string; 133 | notes?: Notes, 134 | image?: Image, 135 | children?: { [index: string]: Array }; 136 | markers?: Array; 137 | boundaries?: Array; 138 | summaries?: Array; 139 | extensions?: Array; 140 | } 141 | 142 | /** 143 | * Marker model 144 | */ 145 | export interface Marker { 146 | markerId: string; 147 | groupId: string; 148 | } 149 | 150 | /** 151 | * Boundary model 152 | */ 153 | export interface Boundary { 154 | id: string; 155 | title: string; 156 | style: Style; 157 | class: string; 158 | range: string; 159 | } 160 | 161 | 162 | /** 163 | * Summary model 164 | */ 165 | export interface Summary { 166 | id: string; 167 | style: Style; 168 | class: string; 169 | range: string; 170 | topicId: string; 171 | } 172 | 173 | /** 174 | * Relationship model 175 | */ 176 | export interface Relationship { 177 | 'id': string; 178 | 'title': string; 179 | 'style': Style; 180 | 'class': string; 181 | 'end1Id': string; 182 | 'end2Id': string; 183 | 'controlPoints': ControlPoints; 184 | } 185 | 186 | /* Text extension model */ 187 | export interface ExtensionTextNode { 188 | provider: string; 189 | content: string; 190 | } 191 | 192 | /* Child extension model */ 193 | export interface ExtensionChildNode { 194 | provider: string; 195 | content: Array; 196 | resourceRefs: Array; 197 | } 198 | 199 | /** 200 | * Style model 201 | */ 202 | export interface Style { 203 | 'id': string; 204 | 'type': string; 205 | 'properties': Object; 206 | } 207 | 208 | /** 209 | * Image model 210 | */ 211 | export interface Image { 212 | src: string; 213 | width: number; 214 | height: number; 215 | align: string; 216 | } 217 | 218 | /** 219 | * Numbering model 220 | */ 221 | export interface Numbering { 222 | numberFormat: string; 223 | prefix: string; 224 | suffix: string; 225 | prependingNumbers: None | undefined; 226 | } 227 | 228 | /** 229 | * Notes model 230 | */ 231 | export interface Notes { 232 | plain: Plain; 233 | html: HTML; 234 | } 235 | -------------------------------------------------------------------------------- /src/common/templates/content.xml: -------------------------------------------------------------------------------- 1 | Warning 2 | 警告 3 | Attention 4 | Warnung 5 | 경고This file can not be opened normally, please do not modify and save, otherwise the contents will be permanently lost!You can try using XMind 8 Update 3 or later version to open该文件无法正常打开,请勿修改并保存,否则文件内容将会永久性丢失!你可以尝试使用 XMind 8 Update 3 或更新版本打开該文件無法正常打開,請勿修改並保存,否則文件內容將會永久性丟失!你可以嘗試使用 XMind 8 Update 3 或更新版本打開この文書は正常に開かないので、修正して保存しないようにしてください。そうでないと、書類の内容が永久に失われます。!XMind 8 Update 3 や更新版を使って開くこともできますDatei kann nicht richtig geöffnet werden. Bitte ändern Sie diese Datei nicht und speichern Sie sie, sonst wird die Datei endgültig gelöscht werden.Bitte versuchen Sie, diese Datei mit XMind 8 Update 3 oder später zu öffnen.Ce fichier ne peut pas ouvert normalement, veuillez le rédiger et sauvegarder, sinon le fichier sera perdu en permanence. Vous pouvez essayer d'ouvrir avec XMind 8 Update 3 ou avec une version plus récente.파일을 정상적으로 열 수 없으며, 수정 및 저장하지 마십시오. 그렇지 않으면 파일의 내용이 영구적으로 손실됩니다!XMind 8 Update 3 또는 이후 버전을 사용하여-1Sheet 1 -------------------------------------------------------------------------------- /src/common/themes/business.json: -------------------------------------------------------------------------------- 1 | { 2 | "importantTopic": { 3 | "type": "topic", 4 | "properties": { 5 | "fo:font-weight": "bold", 6 | "fo:color": "#434B54", 7 | "svg:fill": "#FF8D3E" 8 | } 9 | }, 10 | "minorTopic": { 11 | "type": "topic", 12 | "properties": { 13 | "fo:font-weight": "bold", 14 | "fo:color": "#434B54", 15 | "svg:fill": "#FBD400" 16 | } 17 | }, 18 | "expiredTopic": { 19 | "type": "topic", 20 | "properties": { 21 | "fo:font-style": "italic", 22 | "fo:text-decoration": " line-through" 23 | } 24 | }, 25 | "centralTopic": { 26 | "properties": { 27 | "fo:font-family": "Open Sans", 28 | "fo:font-weight": "600", 29 | "svg:fill": "#5A729A", 30 | "line-color": "#8793A5", 31 | "fo:font-size": "20pt", 32 | "fo:font-style": "normal", 33 | "line-width": "1pt", 34 | "line-class": "org.xmind.branchConnection.roundedElbow", 35 | "border-line-width": "0" 36 | }, 37 | "styleId": "01b8c70bdbfb816bfd381985a4", 38 | "type": "topic" 39 | }, 40 | "boundary": { 41 | "properties": { 42 | "line-color": "#F78A4A", 43 | "svg:fill": "#FCE7D9", 44 | "fo:color": "#FFFFFF" 45 | }, 46 | "styleId": "7058052c47184b687957ca63a9", 47 | "type": "boundary" 48 | }, 49 | "floatingTopic": { 50 | "properties": { 51 | "svg:fill": "#F78A4A", 52 | "border-line-width": "0", 53 | "fo:font-family": "Open Sans", 54 | "fo:font-weight": "normal", 55 | "line-color": "#F78A4A" 56 | }, 57 | "styleId": "8b98e3ef368a20335ab639d05f", 58 | "type": "topic" 59 | }, 60 | "subTopic": { 61 | "properties": { 62 | "fo:font-weight": "normal", 63 | "fo:color": "#434B54", 64 | "fo:font-family": "Open Sans", 65 | "fo:text-align": "left", 66 | "fo:font-size": "11pt" 67 | }, 68 | "styleId": "1f2ab177cd1a075412f11b1ff0", 69 | "type": "topic" 70 | }, 71 | "mainTopic": { 72 | "properties": { 73 | "fo:font-weight": "normal", 74 | "fo:color": "#434B54", 75 | "fo:font-family": "Open Sans", 76 | "fo:font-size": "14pt", 77 | "svg:fill": "#96D2E7", 78 | "border-line-width": "0" 79 | }, 80 | "styleId": "a1db67020fc89378558ba84152", 81 | "type": "topic" 82 | }, 83 | "calloutTopic": { 84 | "properties": { 85 | "svg:fill": "#F78A4A", 86 | "border-line-width": "0", 87 | "fo:font-family": "Open Sans", 88 | "fo:font-weight": "normal", 89 | "fo:font-style": "normal", 90 | "fo:font-size": "11pt" 91 | }, 92 | "styleId": "2ba8e499f408e34e871ff9ef31", 93 | "type": "topic" 94 | }, 95 | "summary": { 96 | "properties": { 97 | "shape-class": "org.xmind.summaryShape.round", 98 | "line-color": "#F78A4A" 99 | }, 100 | "styleId": "77dbc8b8661ae51aaf517be0dd", 101 | "type": "summary" 102 | }, 103 | "summaryTopic": { 104 | "properties": { 105 | "svg:fill": "#F78A4A", 106 | "border-line-width": "0", 107 | "fo:font-family": "Open Sans", 108 | "fo:font-weight": "normal", 109 | "fo:font-style": "normal", 110 | "line-color": "#F78A4A" 111 | }, 112 | "styleId": "306914919609fa5dc883697a02", 113 | "type": "topic" 114 | }, 115 | "relationship": { 116 | "properties": { 117 | "fo:color": "#434B54", 118 | "fo:font-family": "Open Sans", 119 | "fo:font-weight": "normal", 120 | "line-pattern": "solid", 121 | "line-width": "2pt", 122 | "line-color": "#F78A4A" 123 | }, 124 | "styleId": "fc1aacff7bba7fa363b4fbc2dd", 125 | "type": "relationship" 126 | }, 127 | "map": { 128 | "properties": { 129 | "line-tapered": "tapered", 130 | "svg:fill": "#F5FAFF" 131 | }, 132 | "styleId": "f98b99abe58b508e4d0f5a584c", 133 | "type": "map" 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/common/themes/robust.json: -------------------------------------------------------------------------------- 1 | { 2 | "importantTopic": { 3 | "type": "topic", 4 | "properties": { 5 | "fo:font-weight": "bold", 6 | "fo:color": "#312935", 7 | "svg:fill": "#FEFF00" 8 | } 9 | }, 10 | "minorTopic": { 11 | "type": "topic", 12 | "properties": { 13 | "fo:font-weight": "bold", 14 | "fo:color": "#312935", 15 | "svg:fill": "#00EEFF" 16 | } 17 | }, 18 | "expiredTopic": { 19 | "type": "topic", 20 | "properties": { 21 | "fo:font-style": "italic", 22 | "fo:text-decoration": " line-through" 23 | } 24 | }, 25 | "centralTopic": { 26 | "properties": { 27 | "shape-class": "org.xmind.topicShape.ellipserect", 28 | "svg:fill": "#D1F786", 29 | "fo:color": "#302934", 30 | "line-color": "#30D8C4", 31 | "line-class": "org.xmind.branchConnection.bight", 32 | "fo:font-weight": "500", 33 | "fo:font-family": "Montserrat", 34 | "fo:font-style": "normal", 35 | "fo:text-transform": "uppercase", 36 | "fo:font-size": "30pt", 37 | "line-width": "3pt", 38 | "border-line-width": "0" 39 | }, 40 | "styleId": "566b3aea3f7b2e0a0cbb89ca83", 41 | "type": "topic" 42 | }, 43 | "boundary": { 44 | "properties": { 45 | "line-color": "#FFFFFF", 46 | "svg:fill": "#FFFFFF", 47 | "fo:color": "#302934", 48 | "fo:font-family": "Montserrat", 49 | "fo:font-weight": "normal" 50 | }, 51 | "styleId": "809a90d25f7974588bed59c06b", 52 | "type": "boundary" 53 | }, 54 | "floatingTopic": { 55 | "properties": { 56 | "shape-class": "org.xmind.topicShape.ellipserect", 57 | "svg:fill": "#FFFFFF", 58 | "border-line-color": "#FFFFFF", 59 | "border-line-width": "2pt", 60 | "fo:font-family": "Montserrat", 61 | "fo:font-weight": "normal", 62 | "fo:font-size": "18pt", 63 | "fo:color": "#302934", 64 | "line-class": "org.xmind.branchConnection.bight", 65 | "line-color": "#30D8C4", 66 | "line-width": "3pt" 67 | }, 68 | "styleId": "b9588c8bfc6ad4c872b9fa082f", 69 | "type": "topic" 70 | }, 71 | "subTopic": { 72 | "properties": { 73 | "fo:font-weight": "normal", 74 | "fo:color": "#FFFFFF", 75 | "fo:font-family": "Montserrat", 76 | "fo:text-align": "left", 77 | "line-class": "org.xmind.branchConnection.bight" 78 | }, 79 | "styleId": "3138d4845e2b6e60dfb0eb5dbb", 80 | "type": "topic" 81 | }, 82 | "mainTopic": { 83 | "properties": { 84 | "fo:font-weight": "normal", 85 | "fo:color": "#FFFFFF", 86 | "fo:font-family": "Montserrat", 87 | "fo:font-size": "24pt", 88 | "shape-class": "org.xmind.topicShape.underline", 89 | "svg:fill": "none", 90 | "line-class": "org.xmind.branchConnection.bight", 91 | "line-width": "3pt" 92 | }, 93 | "styleId": "ae3a94584179df1806911bd775", 94 | "type": "topic" 95 | }, 96 | "calloutTopic": { 97 | "properties": { 98 | "svg:fill": "#FFFFFF", 99 | "border-line-width": "0", 100 | "fo:font-family": "Montserrat", 101 | "fo:font-weight": "400", 102 | "fo:color": "#302934", 103 | "fo:font-style": "normal" 104 | }, 105 | "styleId": "cf597269c17beb4f39c6daae3f", 106 | "type": "topic" 107 | }, 108 | "summary": { 109 | "properties": { 110 | "line-color": "#FFFFFF" 111 | }, 112 | "styleId": "7129e73481eb7a1897b2c8094a", 113 | "type": "summary" 114 | }, 115 | "summaryTopic": { 116 | "properties": { 117 | "svg:fill": "#FFFFFF", 118 | "border-line-color": "#FFFFFF", 119 | "fo:font-family": "Montserrat", 120 | "fo:font-weight": "400", 121 | "border-line-width": "1pt", 122 | "fo:color": "#302934", 123 | "fo:font-style": "normal", 124 | "fo:font-size": "18pt", 125 | "line-class": "org.xmind.branchConnection.bight", 126 | "line-width": "3pt" 127 | }, 128 | "styleId": "28db31b676f948c81ce478dc31", 129 | "type": "topic" 130 | }, 131 | "relationship": { 132 | "properties": { 133 | "line-width": "3pt", 134 | "line-pattern": "solid", 135 | "line-color": "#FFFFFF", 136 | "fo:color": "#FFFFFF", 137 | "fo:font-family": "Montserrat", 138 | "fo:font-weight": "normal" 139 | }, 140 | "styleId": "ea0990886e2ba2b4b3c69ab34d", 141 | "type": "relationship" 142 | }, 143 | "map": { 144 | "properties": { 145 | "line-tapered": "tapered", 146 | "svg:fill": "#302934" 147 | }, 148 | "styleId": "6659012d15a89de3df86e84e92", 149 | "type": "map" 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/common/themes/snowbrush.json: -------------------------------------------------------------------------------- 1 | { 2 | "importantTopic": { 3 | "type": "topic", 4 | "properties": { 5 | "fo:font-weight": "bold", 6 | "fo:color": "#F04137", 7 | "svg:fill": "none" 8 | } 9 | }, 10 | "minorTopic": { 11 | "type": "topic", 12 | "properties": { 13 | "svg:fill": "none", 14 | "fo:font-weight": "bold", 15 | "fo:color": "#06ABD0" 16 | } 17 | }, 18 | "expiredTopic": { 19 | "type": "topic", 20 | "properties": { 21 | "fo:font-style": "italic", 22 | "fo:text-decoration": " line-through" 23 | } 24 | }, 25 | "centralTopic": { 26 | "properties": { 27 | "fo:color": "#000000", 28 | "fo:font-family": "Avenir Next", 29 | "fo:font-weight": "600", 30 | "fo:font-size": "24pt", 31 | "fo:font-style": "normal", 32 | "border-line-width": "0", 33 | "border-line-color": "#374C75", 34 | "line-color": "#374C75", 35 | "line-width": "3pt", 36 | "svg:fill": "#FFFFFF" 37 | }, 38 | "styleId": "39687c955b998eedc81008bf3f", 39 | "type": "topic" 40 | }, 41 | "boundary": { 42 | "properties": { 43 | "fo:font-style": "normal", 44 | "fo:color": "#FFFFFF", 45 | "fo:font-family": "Avenir Next", 46 | "fo:font-weight": "600", 47 | "fo:font-size": "14pt", 48 | "svg:fill": "#E9E9E8", 49 | "line-color": "#5E5E5E", 50 | "line-pattern": "dot" 51 | }, 52 | "styleId": "29f9b72a43c95e2d17c1ebd6c8", 53 | "type": "boundary" 54 | }, 55 | "floatingTopic": { 56 | "properties": { 57 | "svg:fill": "#EBEBEB", 58 | "shape-class": "org.xmind.topicShape.roundedRect", 59 | "border-line-width": "0pt", 60 | "border-line-color": "#374C75", 61 | "fo:color": "#332F2E", 62 | "fo:font-family": "Avenir Next", 63 | "fo:font-size": "14pt", 64 | "fo:font-weight": "600", 65 | "fo:font-style": "normal", 66 | "line-class": "org.xmind.branchConnection.bight", 67 | "line-color": "#5E5E5E", 68 | "line-width": "2pt" 69 | }, 70 | "styleId": "8edb0655eed84223023988f896", 71 | "type": "topic" 72 | }, 73 | "subTopic": { 74 | "type": "topic", 75 | "properties": { 76 | "line-class": "org.xmind.branchConnection.bight", 77 | "line-width": "1pt", 78 | "fo:font-family": "Avenir Next", 79 | "fo:font-size": "14pt", 80 | "fo:color": "#091C34", 81 | "fo:text-align": "left" 82 | } 83 | }, 84 | "mainTopic": { 85 | "type": "topic", 86 | "properties": { 87 | "border-line-width": "2pt", 88 | "line-class": "org.xmind.branchConnection.bight", 89 | "line-width": "2pt", 90 | "svg:fill": "none", 91 | "shape-class": "org.xmind.topicShape.underline", 92 | "fo:color": "#091C34", 93 | "fo:font-size": "16pt", 94 | "fo:font-family": "Avenir Next", 95 | "fo:font-weight": "500" 96 | } 97 | }, 98 | "calloutTopic": { 99 | "properties": { 100 | "fo:font-weight": "600", 101 | "fo:font-style": "normal", 102 | "fo:font-size": "14pt", 103 | "fo:font-family": "Avenir Next", 104 | "border-line-width": "0", 105 | "svg:fill": "#5E5E5E", 106 | "fo:color": "#FFFFFF" 107 | }, 108 | "styleId": "4379160bdc98a456dd60a8721d", 109 | "type": "topic" 110 | }, 111 | "summary": { 112 | "properties": { 113 | "shape-class": "org.xmind.summaryShape.square", 114 | "line-width": "2pt", 115 | "line-color": "#5E5E5E" 116 | }, 117 | "styleId": "14394c4b1a5b6b534182699edf", 118 | "type": "summary" 119 | }, 120 | "summaryTopic": { 121 | "properties": { 122 | "line-class": "org.xmind.branchConnection.bight", 123 | "line-width": "1pt", 124 | "line-color": "#5E5E5E", 125 | "shape-class": "org.xmind.topicShape.roundedRect", 126 | "svg:fill": "#EBEBEB", 127 | "fo:color": "#091C34", 128 | "fo:font-weight": "600", 129 | "fo:font-style": "normal", 130 | "fo:font-family": "Avenir Next", 131 | "fo:font-size": "14pt", 132 | "border-line-width": "0pt", 133 | "border-line-color": "#5E5E5E" 134 | }, 135 | "styleId": "963bfcbd450931f641aef94ec5", 136 | "type": "topic" 137 | }, 138 | "relationship": { 139 | "properties": { 140 | "line-width": "2pt", 141 | "line-pattern": "dot", 142 | "line-color": "#5E5E5E", 143 | "fo:font-weight": "600", 144 | "fo:font-style": "normal", 145 | "fo:color": "#5E5E5E", 146 | "fo:font-family": "Avenir Next", 147 | "fo:font-size": "14pt", 148 | "arrow-begin-class": "org.xmind.arrowShape.attached" 149 | }, 150 | "styleId": "67596f401d995d448791686b97", 151 | "type": "relationship" 152 | }, 153 | "map": { 154 | "properties": { 155 | "multi-line-colors": "#F04137 #F8932E #FEC938 #A0C347 #06ABD0 #832A96", 156 | "line-tapered": "tapered", 157 | "svg:fill": "#F5F5FA" 158 | }, 159 | "styleId": "c9c904312f6968e676f4fafd22", 160 | "type": "map" 161 | }, 162 | "level3": { 163 | "type": "topic", 164 | "properties": { 165 | "line-class": "org.xmind.branchConnection.bight", 166 | "line-width": "1pt", 167 | "border-line-width": "2pt", 168 | "fo:font-family": "Avenir Next", 169 | "fo:font-size": "14pt", 170 | "fo:color": "#091C34", 171 | "fo:text-align": "left" 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/core/base.ts: -------------------------------------------------------------------------------- 1 | import * as TreeModel from 'tree-model'; 2 | import { Node } from 'tree-model'; 3 | 4 | import { isString } from '../utils/common'; 5 | 6 | const v4 = require('uuid/v4'); 7 | const Debug = require('debug'); 8 | 9 | export interface BaseOptions { 10 | debug?: string; 11 | instance?: any; 12 | } 13 | 14 | export interface TreeNodeOptions { 15 | id: string; 16 | title: string; 17 | customId?: number | string | null; 18 | parentId?: number | string | null; 19 | } 20 | 21 | export interface ConflictedOnDifferentBranchOptions extends Pick {} 22 | 23 | export interface ConflictedOnSameBranchOptions extends Pick {} 24 | 25 | const DEFAULT_DEBUG_SCOPE = 'xmind-sdk'; 26 | 27 | export default class Base { 28 | private readonly _debug; 29 | 30 | protected tree = new TreeModel(); 31 | 32 | protected rootNode: Node; 33 | 34 | /* istanbul ignore next */ 35 | constructor(protected options: BaseOptions = {}) { 36 | this.options = options; 37 | this._debug = Debug(this.options.debug || DEFAULT_DEBUG_SCOPE); 38 | } 39 | 40 | protected isValidComponentId(componentId: string): boolean { 41 | if (!componentId || typeof componentId !== 'string') { 42 | return false; 43 | } 44 | const node = this.rootNode.first((node: Node) => node.model.id === componentId); 45 | return !!node; 46 | } 47 | 48 | protected setRoot(options: TreeNodeOptions) { 49 | this.rootNode = this.tree.parse(Object.assign(options, { children: [] })); 50 | return this; 51 | } 52 | 53 | protected destroyNode(options: Pick): boolean { 54 | const node = this.rootNode.first((node: Node) => { 55 | return node.model.id === options.id; 56 | }); 57 | node.drop(); 58 | return true; 59 | } 60 | 61 | protected addChildNode(options: TreeNodeOptions) { 62 | const node = this.rootNode.first((node: Node) => { 63 | return node.model.id === options.parentId; 64 | }); 65 | node.addChild(this.tree.parse(options)); 66 | } 67 | 68 | protected exist(componentId: string): boolean { 69 | const n = this.rootNode.first((node: Node) => { 70 | return node.model.id === componentId; 71 | }); 72 | return !!n; 73 | 74 | } 75 | 76 | protected findComponentIdBy(title: string): string | null { 77 | const n = this.rootNode.first((node: Node) => { 78 | return node.model.title === title; 79 | }); 80 | if (!n) return null; 81 | return n.model.id; 82 | } 83 | 84 | protected all() { 85 | const nodes = this.rootNode.all(() => true); 86 | const map = {}; 87 | nodes.forEach(node => { 88 | map[String(node.model.id)] = node.model.title; 89 | }); 90 | return map; 91 | } 92 | 93 | /** 94 | * 95 | * @param { ConflictedOnDifferentBranchOptions | ConflictedOnSameBranchOptions } options 96 | * @return { String | Null } 97 | */ 98 | protected getConflictedComponentId( 99 | options: ConflictedOnDifferentBranchOptions & ConflictedOnSameBranchOptions 100 | ): string | null { 101 | const validString = isString(options.title); 102 | if (validString && options.parentId) { 103 | return this.different(options); 104 | } 105 | 106 | if (validString && options.customId) { 107 | return this.identical(options); 108 | } 109 | 110 | return null; 111 | } 112 | 113 | /** 114 | * @description Print debug information 115 | * @param {Array} args - the rest arguments 116 | */ 117 | public debug(...args) { 118 | this._debug(...args); 119 | } 120 | 121 | /** 122 | * @description uuid/v4 123 | */ 124 | get id(): string { 125 | return v4(); 126 | } 127 | 128 | private different(options: ConflictedOnDifferentBranchOptions): string | null { 129 | const finder = (node: Node) => { 130 | return node.model.title === options.title; 131 | }; 132 | for (const node of this.rootNode.all(finder)) { 133 | if (node.parent.model.id === options.parentId) { 134 | return node.model.id; 135 | } 136 | } 137 | 138 | return null; 139 | } 140 | 141 | private identical(options: ConflictedOnSameBranchOptions) { 142 | const finder = (node: Node) => { 143 | return node.model.title === options.title && 144 | node.model.customId === options.customId; 145 | }; 146 | 147 | const nodes = this.rootNode.all(finder); 148 | if (nodes.length <= 0) { 149 | return null; 150 | } 151 | 152 | return nodes[0].model.id; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/core/marker.ts: -------------------------------------------------------------------------------- 1 | import { AbstractMarker } from '../abstracts/marker.abstract'; 2 | import { icons, iterable } from '../common/constants/marker'; 3 | 4 | const debug = require('debug')('xmind-sdk:marker'); 5 | 6 | Object.defineProperty(icons, 'iterable', { 7 | value: iterable, 8 | enumerable: false, 9 | configurable: false, 10 | writable: false 11 | }); 12 | 13 | 14 | export class Marker extends AbstractMarker { 15 | constructor() { 16 | super(); 17 | this.init(); 18 | } 19 | 20 | private init() { 21 | for (const property in icons) { 22 | this[property] = function(name: string) { 23 | if (!name) { 24 | return null; 25 | } 26 | const normalized = (typeof name === 'string') ? name : String(name); 27 | if (name && !icons[property].hasOwnProperty(normalized)) { 28 | debug('W - Invalid name string %s', name); 29 | return null; 30 | } 31 | return icons[property][normalized]; 32 | }; 33 | } 34 | } 35 | 36 | /** 37 | * @description Get names by group name 38 | * @param {String} groupName 39 | * @return {Array} 40 | * @static 41 | */ 42 | static names(groupName: string) { 43 | return icons['iterable'][String(groupName)]; 44 | } 45 | 46 | /** 47 | * @description Get group names 48 | * @return {Array} 49 | * @static 50 | */ 51 | static groups() { 52 | return Object.keys(icons['iterable']); 53 | } 54 | } -------------------------------------------------------------------------------- /src/core/note.ts: -------------------------------------------------------------------------------- 1 | import { AbstractNote } from '../abstracts/note.abstract'; 2 | 3 | /** 4 | * @description Note class and XMind ZEN is supported 5 | * @implements AbstractNote 6 | * @property {*} html 7 | * @property {*} plain 8 | * @property {*} ops 9 | */ 10 | export class Note implements AbstractNote { 11 | public html: any; 12 | public plain: any; 13 | public ops: any; 14 | 15 | constructor() { 16 | this.html = {content: {paragraphs: []}}; 17 | this.ops = {ops: []}; 18 | this.plain = {}; 19 | } 20 | 21 | /** 22 | * @description Format value 23 | * @param {any} value 24 | * @setter 25 | */ 26 | set text(value) { 27 | this.plain.content = value; 28 | this.html.content.paragraphs.push({spans: [{text: value}]}); 29 | this.ops.ops.push({insert: value}); 30 | } 31 | 32 | public toJSON() { 33 | return {html: this.html, plain: this.plain, ops: this.ops}; 34 | } 35 | 36 | /* istanbul ignore next */ 37 | public toString() { 38 | return JSON.stringify(this.toJSON()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/core/summary.ts: -------------------------------------------------------------------------------- 1 | import { AbstractSummary, RangeOptions } from '../abstracts/summary.abstract'; 2 | import Base from './base'; 3 | 4 | export class Summary extends Base implements AbstractSummary { 5 | private _range: string; 6 | 7 | public topicId: string; 8 | 9 | constructor() { 10 | super({debug: 'xmind-sdk:summary'}); 11 | } 12 | 13 | public range(options: RangeOptions) { 14 | const children = options.children; 15 | const condition = options.condition; 16 | 17 | if (condition[0] === condition[1]) { 18 | for (let i = 0, len = children.length; i < len; i++) { 19 | if (children[i].getId() === condition[0]) { 20 | this._range = `(${i},${i})`; 21 | } 22 | } 23 | } else { 24 | let s, e = 0; 25 | for (let i = 0, len = children.length; i < len; i++) { 26 | if (children[i].getId() === condition[0]) { 27 | s = i; 28 | } 29 | 30 | if (children[i].getId() === condition[1]) { 31 | e = i; 32 | } 33 | } 34 | this._range = s > e ? `(${s},${s})`: `(${s},${e})`; 35 | } 36 | 37 | return this; 38 | } 39 | 40 | public toJSON() { 41 | return { id: this.id, range: this._range, topicId: this.topicId }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/core/theme.ts: -------------------------------------------------------------------------------- 1 | import * as Model from '../common/model'; 2 | 3 | const Robust = require('../common/themes/robust.json'); 4 | const Snowbrush = require('../common/themes/snowbrush.json'); 5 | const Business = require('../common/themes/business.json'); 6 | 7 | const v4 = require('uuid/v4'); 8 | const debug = require('debug')('xmind-sdk:theme'); 9 | const ALLOWED_THEMES = ['robust', 'snowbrush', 'business']; 10 | 11 | interface ThemeOptions { 12 | themeName: string; 13 | } 14 | 15 | const THEMES = { 16 | robust: Robust, 17 | snowbrush: Snowbrush, 18 | business: Business, 19 | }; 20 | 21 | /** 22 | * @description Invisible external 23 | */ 24 | export class Theme { 25 | private readonly value: Model.Theme; 26 | 27 | constructor(options: ThemeOptions = {}) { 28 | const name = options.themeName; 29 | if (!name || typeof name !== 'string' || 30 | !ALLOWED_THEMES.includes(name.toLocaleLowerCase())) { 31 | debug('W - Only ', ALLOWED_THEMES.join(', '), 'are allowed for now.'); 32 | throw new Error(`the theme name ${name} is not allowed`); 33 | } 34 | 35 | this.value = this.loader(name); 36 | } 37 | 38 | get data() { 39 | return this.value; 40 | } 41 | 42 | private loader(name: string) { 43 | const theme = THEMES[name]; 44 | theme.id = v4(); 45 | theme.title = name; 46 | return theme; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/core/topic.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AbstractTopic, TopicOptions, 3 | MarkerOptions, ImageOptions 4 | } from '../abstracts/topic.abstract'; 5 | import { SummaryOptions } from '../abstracts/summary.abstract'; 6 | import { Summary } from './summary'; 7 | import { Note } from './note'; 8 | import { isEmpty, isObject, isString } from '../utils/common'; 9 | 10 | import * as Model from '../common/model'; 11 | import * as Core from 'xmind-model'; 12 | import Base from './base'; 13 | 14 | 15 | /** 16 | * @description Topic common methods 17 | */ 18 | export class Topic extends Base implements AbstractTopic { 19 | private readonly sheet: Core.Sheet; 20 | private readonly root: Core.Topic; 21 | 22 | private parentId: string; 23 | private lastId: string; 24 | 25 | constructor(options: TopicOptions = {}) { 26 | super({debug: 'xmind-sdk:topic'}); 27 | if (options && !options.sheet) { 28 | throw new Error('options.sheet is required'); 29 | } 30 | 31 | this.sheet = options.sheet; 32 | this.root = this.sheet.getRootTopic(); 33 | this.parentId = this.lastId = this.root.getId(); 34 | this.setRoot({ id: this.parentId, title: 'Central Topic' }); 35 | } 36 | 37 | public on(componentId?: string): Topic { 38 | if (!componentId) { 39 | this.parentId = this.root.getId(); 40 | return this; 41 | } 42 | 43 | if (!this.isValidComponentId(String(componentId))) { 44 | throw new Error(`Invalid componentId ${String(componentId)}`); 45 | } 46 | 47 | this.parentId = componentId; 48 | return this; 49 | } 50 | 51 | public addLabel(text: string): Topic { 52 | const p = this.parent(); 53 | const labels = p.getLabels(); 54 | const options = { index: 0 }; 55 | if (!labels || labels.length === 0) { 56 | options.index = 0; 57 | } else { 58 | options.index = labels.length; 59 | } 60 | p.addLabel(text, options); 61 | return this; 62 | } 63 | 64 | public removeLabel(componentId?: string): Topic { 65 | const p = componentId ? this.find(componentId) : this.parent(); 66 | if (!p) { 67 | throw new Error(`does not found component: ${componentId}`); 68 | } 69 | p.removeLabels(); 70 | return this; 71 | } 72 | 73 | public add(topic: Model.Topic & { 74 | customId?: string | number, parentId?: string 75 | } = {} as any, options?: { index: number }): Topic { 76 | if (!isString(topic.title)) { 77 | throw new Error('topic.title should be a valid string'); 78 | } 79 | topic.id = topic.id || this.id; 80 | this.parent().addChildTopic(topic, options); 81 | this.addChildNode({ 82 | id: topic.id, title: topic.title, 83 | customId: topic.customId || null, 84 | parentId: topic.parentId || this.parentId 85 | }); 86 | this.lastId = topic.id; 87 | return this; 88 | } 89 | 90 | public image(options?: ImageOptions): string { 91 | const dir = `resources/${this.id}`; 92 | const params = Object.assign({}, {src: `xap:${dir}`}, options || {}); 93 | this.parent().addImage(params); 94 | return dir; 95 | } 96 | 97 | public note(text: string, del?: boolean): Topic { 98 | const p = this.parent(); 99 | if (del === true) { 100 | p.removeNotes(); 101 | return this; 102 | } 103 | if (!text) return this; 104 | const n = new Note(); 105 | n.text = text; 106 | p.addNotes(n.toJSON()); 107 | return this; 108 | } 109 | 110 | public destroy(componentId: string): Topic { 111 | if (!this.isValidComponentId(componentId)) { 112 | this.debug('E - target: "%s" does not exist', componentId); 113 | return this; 114 | } 115 | try { 116 | const topic = this.find(componentId); 117 | topic.parent().removeChildTopic(topic); 118 | this.destroyNode({ id: componentId }); 119 | } catch (e) { 120 | /* istanbul ignore next */ 121 | this.debug('D - %s', e.message); 122 | } 123 | return this; 124 | } 125 | 126 | public summary(options: SummaryOptions = {}): Topic { 127 | if (this.parent().isRootTopic()) { 128 | this.debug('I - Not allowed add summary on root topic.'); 129 | return this; 130 | } 131 | 132 | let edge = null; 133 | if (options.edge) { 134 | if (this.exist(options.edge)) { 135 | edge = options.edge; 136 | } else { 137 | this.debug('W - Topic "%s" does not exist', options.edge); 138 | } 139 | } 140 | 141 | const summary = new Summary(); 142 | const type = this.parent().getType(); 143 | const grandfather = this.grandfather(); 144 | const children = grandfather.getChildrenByType(type); 145 | const condition = [this.parentId, !edge ? this.parentId : edge]; 146 | summary.range({ children, condition }); 147 | const summaryOptions = {title: options.title || 'Summary', id: this.id}; 148 | summary.topicId = summaryOptions.id; 149 | grandfather.addSummary(summary.toJSON(), summaryOptions); 150 | this.addChildNode({ 151 | id: summaryOptions.id, title: summaryOptions.title, 152 | parentId: this.parentId 153 | }); 154 | this.lastId = summaryOptions.id; 155 | return this; 156 | } 157 | 158 | public marker(options: MarkerOptions = {}): Topic { 159 | if ( 160 | !isObject(options) || isEmpty(options) || 161 | !options['groupId'] || !options['markerId'] 162 | ) { 163 | this.debug('E - Invalid marker options: %j', options); 164 | return this; 165 | } 166 | 167 | if (options.del === true) { 168 | delete options.del; 169 | this.parent().removeMarker(options); 170 | return this; 171 | } 172 | 173 | this.parent().addMarker(options); 174 | return this; 175 | } 176 | 177 | 178 | public cid(title?: string, dependencies: { 179 | parentId?: number | string, customId?: number | string 180 | } = {}): string | null { 181 | const validTitle = isString(title); 182 | if (validTitle && dependencies) { 183 | if (dependencies.parentId) { 184 | return this.getConflictedComponentId({ 185 | title, parentId: dependencies.parentId 186 | }); 187 | } 188 | if (dependencies.customId) { 189 | return this.getConflictedComponentId({ 190 | title, customId: dependencies.customId 191 | }); 192 | } 193 | } 194 | 195 | if (validTitle) { 196 | return this.findComponentIdBy(title); 197 | } 198 | return this.lastId; 199 | } 200 | 201 | public cids(): Record { 202 | return this.all(); 203 | } 204 | 205 | public find(componentId: string = null) { 206 | const rootId = this.root.getId(); 207 | 208 | if (!componentId || componentId === rootId) { 209 | return this.root; 210 | } 211 | 212 | return this.sheet.findComponentById(componentId); 213 | } 214 | 215 | private grandfather() { 216 | return this.parent().parent(); 217 | } 218 | 219 | private parent() { 220 | return this.parentId === this.root.getId() ? 221 | this.root : 222 | this.sheet.findComponentById(this.parentId); 223 | } 224 | 225 | get rootTopic() { 226 | /* istanbul ignore next */ 227 | return this.root; 228 | } 229 | 230 | get rootTopicId() { 231 | return this.root.getId(); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/core/workbook.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AbstractWorkbook, 3 | CreateSheetsOptions, 4 | ResponseOfSheets 5 | } from '../abstracts/workbook.abstract'; 6 | import { Theme } from './theme'; 7 | import Base from './base'; 8 | import * as Core from 'xmind-model'; 9 | import { ErrorObject } from 'ajv'; 10 | 11 | /** 12 | * @description The implementation of Workbook 13 | * @extends {Base} 14 | */ 15 | export class Workbook extends Base implements AbstractWorkbook { 16 | public sheet: Core.Sheet; 17 | private workbook: Core.Workbook; 18 | private readonly resources; 19 | 20 | 21 | constructor() { 22 | super(); 23 | this.resources = {}; 24 | } 25 | 26 | 27 | public theme(sheetTitle:string, themeName: string) { 28 | /* istanbul ignore next */ 29 | if (!sheetTitle || !this.resources[sheetTitle]) { 30 | return false; 31 | } 32 | /* istanbul ignore next */ 33 | if (!themeName || typeof themeName !== 'string') { 34 | return false; 35 | } 36 | 37 | const instance = new Theme({themeName}); 38 | this.sheet.changeTheme(instance.data); 39 | return true; 40 | } 41 | 42 | 43 | public toString() { 44 | return this.workbook.toString(); 45 | } 46 | 47 | public toJSON() { 48 | return this.workbook.toJSON(); 49 | } 50 | 51 | public validate() { 52 | return Core.validator(this.workbook.toJSON()) as { 53 | status: boolean, 54 | errors: ErrorObject[] 55 | }; 56 | } 57 | 58 | public getSheets(): ResponseOfSheets[] { 59 | return Object.entries(this.resources || {}) 60 | .map(sheet => ({ id: sheet[1] as string, title: sheet[0] })); 61 | } 62 | 63 | public getSheet(id: string) { 64 | if (!id) { 65 | throw new Error('The sheetId is required'); 66 | } 67 | return this.workbook.getSheetById(id); 68 | } 69 | 70 | public createSheets(options: CreateSheetsOptions[] = []): ResponseOfSheets[] { 71 | if (options.length <= 0) { 72 | throw new Error('Options are empty'); 73 | } 74 | 75 | const sheets = []; 76 | const created = []; 77 | for (let i = 0; i < options.length; i++) { 78 | if (this.resources.hasOwnProperty(options[i].s)) { 79 | continue; 80 | } 81 | const id = this.id; 82 | this.resources[options[i].s] = id; 83 | const sheetBody = { id, title: options[i].s }; 84 | const rootTopic = { rootTopic: { id: this.id, title: options[i].t } }; 85 | sheets.push(Object.assign({}, sheetBody, rootTopic)); 86 | created.push(sheetBody); 87 | } 88 | 89 | this.workbook = new Core.Workbook(sheets); 90 | return created; 91 | } 92 | 93 | public createSheet(sheetTitle: string, centralTopicTitle = 'Central Topic') { 94 | if (!sheetTitle) { 95 | throw new Error('The title of sheet is required'); 96 | } 97 | 98 | if (this.resources.hasOwnProperty(sheetTitle)) { 99 | throw new Error('You are trying to create the sheet repeatedly that is not allowed'); 100 | } 101 | 102 | const sheetId = this.id; 103 | this.resources[sheetTitle] = sheetId; 104 | 105 | const options = [{ 106 | id: sheetId, title: sheetTitle, 107 | rootTopic: { id: this.id, title: centralTopicTitle } 108 | }]; 109 | this.workbook = new Core.Workbook(options); 110 | this.sheet = this.workbook.getSheetById(sheetId); 111 | return this.sheet; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core/workbook'; 2 | export * from './core/topic'; 3 | export * from './core/marker'; 4 | export * from './core/note'; 5 | export * from './utils/zipper'; -------------------------------------------------------------------------------- /src/utils/common.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Checking for value 3 | * @param {*} v - any values 4 | * @return {Boolean} 5 | */ 6 | const isEmpty = function(v: any): boolean { 7 | if (v === 0 || v === false) { 8 | return false; 9 | } 10 | 11 | // undefined, null, '' are empty 12 | if (v === undefined || v === null || v === '') { 13 | return true; 14 | } 15 | 16 | // {} and [] are empty 17 | if (typeof v === 'object') { 18 | // Object.keys({}).length === 0 19 | // Object.keys([]).length === 0 20 | // Object.keys([1, 2]).length !== 0 21 | // Object.keys([null, undefined]).length !== 0 22 | return Object.keys(v).length === 0; 23 | } 24 | 25 | 26 | return false; 27 | }; 28 | 29 | const isObject = function(v: any): boolean { 30 | const type = typeof v; 31 | return v != null && (type === 'object' || type === 'function'); 32 | }; 33 | 34 | const isRuntime = function(): boolean { 35 | return typeof global === 'object' && typeof window === 'undefined'; 36 | }; 37 | 38 | const isString = function(v: any): v is string { 39 | return typeof v === 'string'; 40 | }; 41 | 42 | export {isEmpty, isObject, isRuntime, isString}; 43 | 44 | -------------------------------------------------------------------------------- /src/utils/dumper.ts: -------------------------------------------------------------------------------- 1 | import { Workbook } from '../core/workbook'; 2 | import { PACKAGE_MAP } from '../common/constants'; 3 | 4 | const XMLContents = `Warning 5 | 警告 6 | Attention 7 | Warnung 8 | 경고This file can not be opened normally, please do not modify and save, otherwise the contents will be permanently lost!You can try using XMind 8 Update 3 or later version to open该文件无法正常打开,请勿修改并保存,否则文件内容将会永久性丢失!你可以尝试使用 XMind 8 Update 3 或更新版本打开該文件無法正常打開,請勿修改並保存,否則文件內容將會永久性丟失!你可以嘗試使用 XMind 8 Update 3 或更新版本打開この文書は正常に開かないので、修正して保存しないようにしてください。そうでないと、書類の内容が永久に失われます。!XMind 8 Update 3 や更新版を使って開くこともできますDatei kann nicht richtig geöffnet werden. Bitte ändern Sie diese Datei nicht und speichern Sie sie, sonst wird die Datei endgültig gelöscht werden.Bitte versuchen Sie, diese Datei mit XMind 8 Update 3 oder später zu öffnen.Ce fichier ne peut pas ouvert normalement, veuillez le rédiger et sauvegarder, sinon le fichier sera perdu en permanence. Vous pouvez essayer d'ouvrir avec XMind 8 Update 3 ou avec une version plus récente.파일을 정상적으로 열 수 없으며, 수정 및 저장하지 마십시오. 그렇지 않으면 파일의 내용이 영구적으로 손실됩니다!XMind 8 Update 3 또는 이후 버전을 사용하여-1Sheet 1`; 9 | 10 | export interface DumperOptions { 11 | workbook: Workbook; 12 | } 13 | 14 | type FileContent = ArrayBuffer | File | Blob; 15 | 16 | export interface FileCreator { 17 | /** 18 | * hint that should enter a folder-like structure, create one if not exists 19 | * @param name Folder name 20 | */ 21 | folder(name: string): Promise 22 | /** 23 | * hint that a file-like object should be created with the `content`, overrides if exists 24 | * @param name Filename 25 | */ 26 | file(name: string, content: FileContent): Promise 27 | } 28 | 29 | export class Dumper { 30 | private workbook: Workbook; 31 | 32 | private _manifest: Record; 33 | 34 | constructor(protected options: DumperOptions = {}) { 35 | if (!(options.workbook instanceof Workbook)) { 36 | throw new Error('The instance of workbook is required'); 37 | } 38 | this.workbook = options.workbook; 39 | this._manifest = { 40 | 'file-entries': {'content.json': {}, 'metadata.json':{}} 41 | }; 42 | } 43 | 44 | /** 45 | * @description dumping an object that contains the pair of $filename: $value 46 | * @return {Array} Array<{filename: string, value: any}> 47 | */ 48 | public dumping() { 49 | return [] 50 | .concat(this.json) 51 | .concat(this.xml) 52 | .concat(this.manifest) 53 | .concat(this.metadata); 54 | } 55 | 56 | /** 57 | * @description Update manifest metadata 58 | * @param { string } key - a string key 59 | * @param { FileContent } content - file content 60 | * @param { FileCreator } creator - specify how to save the file 61 | * @return { Promise } a promise that follows the promise returned by the method of `creator`, or rejects if the key is empty 62 | */ 63 | public async updateManifestMetadata(key: string, content: FileContent, creator: FileCreator) { 64 | if (!key) throw new Error('key is empty'); 65 | const [folderName, fileName] = key.split('/'); 66 | await creator.folder(folderName); 67 | await creator.file(fileName, content); 68 | this._manifest['file-entries'][key] = {}; 69 | } 70 | 71 | private wrap(name: string, value: any) { 72 | return {filename: name, value}; 73 | } 74 | 75 | /** 76 | * @description metadata.json 77 | * 78 | */ 79 | get metadata() { 80 | return this.wrap(PACKAGE_MAP.METADATA.NAME, '{}'); 81 | } 82 | 83 | /** 84 | * @description manifest.json 85 | */ 86 | get manifest() { 87 | return this.wrap(PACKAGE_MAP.MANIFEST.NAME, JSON.stringify(this._manifest)); 88 | } 89 | 90 | /** 91 | * @description content.json 92 | */ 93 | get json() { 94 | const value = this.workbook.toString(); 95 | return this.wrap(PACKAGE_MAP.CONTENT_JSON.NAME, value); 96 | } 97 | 98 | /** 99 | * @description content.xml 100 | */ 101 | get xml() { 102 | return this.wrap(PACKAGE_MAP.CONTENT_XML.NAME, XMLContents); 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /src/utils/zipper.ts: -------------------------------------------------------------------------------- 1 | import * as JSZip from 'jszip'; 2 | import Base from '../core/base'; 3 | import { Workbook } from '../core/workbook'; 4 | import { isObject } from './common'; 5 | import { PACKAGE_MAP } from '../common/constants'; 6 | 7 | const path = require('path'); 8 | const fs = require('fs'); 9 | const { promisify } = require('util'); 10 | const iconv = require('iconv-lite'); 11 | 12 | /* istanbul ignore next */ 13 | const join = (process.platform === 'win32' ? path.win32.join : path.join); 14 | 15 | const SUFFIX = '.xmind'; 16 | const DEFAULT_FILENAME = `default${SUFFIX}`; 17 | 18 | interface ZipperOptions { 19 | path: string; 20 | workbook: Workbook; 21 | filename?: string; 22 | } 23 | 24 | /** 25 | * @description Zipper for .xmind file 26 | */ 27 | export class Zipper extends Base { 28 | public zip: JSZip; 29 | public manifest: any; 30 | 31 | public filename: string; 32 | public path: string; 33 | public workbook: Workbook; 34 | 35 | constructor(options: ZipperOptions) { 36 | super({debug: 'xmind-sdk:zipper'}); 37 | if (!options.path || !fs.existsSync(options.path)) { 38 | this.debug('received %s', options.path); 39 | throw new Error('the `path` is required or must exists'); 40 | } 41 | this.filename = options.filename || DEFAULT_FILENAME; 42 | this.filename = this.filename.endsWith(SUFFIX) ? this.filename : `${this.filename}${SUFFIX}`; 43 | this.path = options.path; 44 | this.zip = new JSZip(); 45 | this.workbook = options.workbook || null; 46 | this.manifest = { 47 | 'file-entries': {'content.json': {}, 'metadata.json':{}} 48 | }; 49 | } 50 | 51 | public target() { 52 | return join(this.path, this.filename); 53 | } 54 | 55 | /** 56 | * @description Saving zip file 57 | * @return { Promise } 58 | */ 59 | public async save() { 60 | if (this.workbook) { 61 | this.addJSONContent(this.workbook.toString()); 62 | this.addMetadataContents(); 63 | this.addXMLContent(); 64 | this.addManifestContents(); 65 | } 66 | 67 | const options: JSZip.JSZipGeneratorOptions = { 68 | type: 'nodebuffer', 69 | compression: 'STORE', 70 | compressionOptions: { level: 9 } 71 | }; 72 | 73 | const metadata = await this.zip.generateAsync(options); 74 | const target = join(this.path, this.filename); 75 | return promisify(fs.writeFile)(target, metadata) 76 | .then(() => true) 77 | .catch(/* istanbul ignore next */ () => false); 78 | } 79 | 80 | /** 81 | * @description Update manifest metadata 82 | * @param { String } key - a string key 83 | * @param { Buffer } content - file contents 84 | * @return { Zipper } 85 | */ 86 | public updateManifestMetadata(key: string, content: Buffer) { 87 | if (!key) return this; 88 | if (!content || !Buffer.isBuffer(content)) { 89 | return this; 90 | } 91 | const arr = key.split('/'); 92 | this.manifest['file-entries'][key] = {}; 93 | this.zip.folder(arr[0]).file(arr[1], content, { binary: false }); 94 | return this; 95 | } 96 | 97 | /** 98 | * @description add contents to metadata.json file 99 | * 100 | */ 101 | private addMetadataContents() { 102 | this.zip.file(PACKAGE_MAP.METADATA.NAME, '{}'); 103 | return this; 104 | } 105 | 106 | /** 107 | * @description add contents to manifest.json 108 | */ 109 | private addManifestContents() { 110 | this.zip.file(PACKAGE_MAP.MANIFEST.NAME, JSON.stringify(this.manifest)); 111 | return this; 112 | } 113 | 114 | /** 115 | * @description add contents to content.json 116 | */ 117 | private addJSONContent(contents: string) { 118 | if (isObject(contents)) { 119 | contents = JSON.stringify(contents); 120 | } 121 | this.zip.file(PACKAGE_MAP.CONTENT_JSON.NAME, iconv.decode(Buffer.from(contents), 'utf8')); 122 | return this; 123 | } 124 | 125 | /** 126 | * @description add contents to content.xml 127 | */ 128 | private addXMLContent() { 129 | const p = join(__dirname, '../common/templates/content.xml'); 130 | this.zip.file(PACKAGE_MAP.CONTENT_XML.NAME, iconv.decode(fs.readFileSync(p), 'utf8')); 131 | return this; 132 | } 133 | } 134 | 135 | -------------------------------------------------------------------------------- /test/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | XMind-sdk-js browser test 6 | 7 | 8 | XMind-sdk-js browser test 9 | 10 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/fixtures/19442.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmindltd/xmind-sdk-js/d35e820a2d09995c69bbb452d4edc42b09a13946/test/fixtures/19442.png -------------------------------------------------------------------------------- /test/fixtures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xmindltd/xmind-sdk-js/d35e820a2d09995c69bbb452d4edc42b09a13946/test/fixtures/logo.png -------------------------------------------------------------------------------- /test/fixtures/utils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | const {join, win32 } = path; 5 | 6 | const getBuildTemporaryPath: (filename?: string) => string = function(filename) { 7 | if (process.platform === 'win32') { 8 | if (!fs.existsSync(win32.normalize('C:\\tmp'))) { 9 | fs.mkdirSync(win32.normalize('C:\\tmp')); 10 | } 11 | return filename ? win32.join(win32.normalize('C:\\tmp'), filename): win32.normalize('C:\\tmp'); 12 | } 13 | 14 | return filename ? join('/tmp', filename): '/tmp'; 15 | } 16 | 17 | export { 18 | getBuildTemporaryPath 19 | }; 20 | -------------------------------------------------------------------------------- /test/functional/index.test.ts: -------------------------------------------------------------------------------- 1 | import { Workbook, Topic, Marker, Zipper } from '../../src'; 2 | import * as chai from 'chai'; 3 | import * as fs from 'fs'; 4 | import * as JSZip from 'jszip'; 5 | // @ts-ignore 6 | import { getBuildTemporaryPath } from '../fixtures/utils'; 7 | import Core = require('xmind-model'); 8 | import { join } from 'path'; 9 | 10 | 11 | const expect = chai.expect; 12 | 13 | 14 | const getComponents = function() { 15 | const workbook = new Workbook(); 16 | const sheet = workbook.createSheet('sheet1', 'centralTopic'); 17 | const topic = new Topic({sheet}); 18 | const zip = new Zipper({path: getBuildTemporaryPath(), workbook}); 19 | return {topic, workbook, sheet, zip}; 20 | }; 21 | 22 | 23 | describe('# Functional Test', () => { 24 | describe('# Entries', () => { 25 | it('the sheet should be created and has rootTopic', done => { 26 | const workbook = new Workbook(); 27 | const sheet = workbook.createSheet('sheet1'); 28 | expect(sheet instanceof Core.Sheet).to.be.true; 29 | expect(sheet.getRootTopic()).to.be.not.null; 30 | done(); 31 | }); 32 | 33 | it('the topic should be created and has .on .add etc. ', done => { 34 | const workbook = new Workbook(); 35 | const sheet = workbook.createSheet('sheet1'); 36 | const topic = new Topic({sheet}); 37 | expect(topic instanceof Topic).to.be.true; 38 | expect(topic).to.have.property('on'); 39 | expect(topic).to.have.property('add'); 40 | expect(topic).to.have.property('note'); 41 | done(); 42 | }); 43 | }); 44 | 45 | describe('# Topic', () => { 46 | it('should be a lots of main topic added to rootTopic', done => { 47 | const topics = ['main topic 1', 'main topic 2', 'main topic 3', 'main topic 4', '']; 48 | const {workbook, topic} = getComponents(); 49 | topic.add({title: 'main topic 1'}) 50 | .add({title: 'main topic 2'}) 51 | .add({title: 'main topic 3'}) 52 | .add({title: 'main topic 4'}) 53 | .add({title: ''}); 54 | 55 | const children = workbook.toJSON()[0].rootTopic.children.attached; 56 | for (let i = 0; i < children.length; i++) { 57 | expect(children[i]).to.have.property('title'); 58 | expect(children[i]).to.have.property('id'); 59 | expect(topics).to.include(children[i].title); 60 | } 61 | done(); 62 | }); 63 | 64 | it('should be subtopic added on main topic 1', done => { 65 | const {workbook, topic} = getComponents(); 66 | topic.add({title: 'main topic 1'}); 67 | 68 | const children = workbook.toJSON()[0].rootTopic.children.attached; 69 | expect(children[0]).to.have.property('title').that.to.be.eq('main topic 1'); 70 | expect(children[0]).to.have.property('id'); 71 | 72 | topic 73 | .on(topic.cid()) 74 | .add({title: 'subtopic 1'}) 75 | .add({title: 'subtopic 2'}) 76 | .add({title: 'subtopic 3'}) 77 | .add({title: ''}); 78 | 79 | const subtopics = workbook.toJSON()[0].rootTopic.children.attached[0].children.attached; 80 | for(let i = 0; i < subtopics.length; i++) { 81 | expect(subtopics[i].title.startsWith('subtopic') || subtopics[i].title === '').to.be.true; 82 | expect(subtopics[i].id).to.not.be.empty; 83 | } 84 | done(); 85 | }); 86 | 87 | it('should be subtopic removed', done => { 88 | const {workbook, topic} = getComponents(); 89 | topic.add({title: 'main topic 1'}); 90 | 91 | const children = workbook.toJSON()[0].rootTopic.children.attached; 92 | expect(children[0]).to.have.property('title').that.to.be.eq('main topic 1'); 93 | expect(children[0]).to.have.property('id'); 94 | 95 | topic 96 | .on(topic.cid()) 97 | .add({title: 'subtopic 1'}) 98 | .add({title: 'subtopic 2'}); 99 | const subTopic2Id = topic.cid(); 100 | 101 | topic.add({title: 'subtopic 3'}); 102 | topic.add({title: ''}); 103 | topic.destroy(subTopic2Id); 104 | // Do nothing if you have to remove topic twice 105 | topic.destroy(subTopic2Id); 106 | 107 | const subtopics = workbook.toJSON()[0].rootTopic.children.attached[0].children.attached; 108 | expect(subtopics.length).to.be.eq(3); 109 | for(let i = 0; i < subtopics.length; i++) { 110 | expect(subtopics[i].title.startsWith('subtopic') || subtopics[i].title === '').to.be.true; 111 | expect(subtopics[i].id).to.not.be.empty; 112 | } 113 | done(); 114 | }); 115 | 116 | it('should be topic found by componentId', done => { 117 | const {topic} = getComponents(); 118 | topic.add({title: 'main topic 1'}); 119 | const mainTopic1 = topic.find(topic.cid()); 120 | expect(mainTopic1).to.not.be.empty; 121 | done(); 122 | }); 123 | 124 | it(`should be default.xmind file to save in ${getBuildTemporaryPath('default.xmind')}`, done => { 125 | const p = getBuildTemporaryPath('default.xmind'); 126 | if (fs.existsSync(p)) { 127 | fs.unlinkSync(p); 128 | } 129 | 130 | const {topic, zip} = getComponents(); 131 | topic 132 | .add({title: 'main topic 1'}) 133 | .add({title: 'main topic 1111'}) 134 | .add({title: 'main topic 222'}) 135 | .add({title: 'main topic 11'}); 136 | 137 | topic 138 | .on(topic.cid('main topic 1111')) 139 | .add({title: 'subtopic 1111'}); 140 | 141 | topic 142 | .on(topic.cid('main topic 1')) 143 | .add({title: 'subtopic 1'}); 144 | 145 | topic 146 | .on(topic.cid('main topic 222')) 147 | .note('add note to main topic 222') 148 | .add({title: 'subtopic 222 with a note'}) 149 | .on(topic.cid('subtopic 222 with a note')) 150 | .note('this is the note with'); 151 | 152 | zip.save().then((status) => { 153 | expect(status).to.be.true; 154 | expect(fs.existsSync(p)).to.be.true; 155 | fs.unlinkSync(p); 156 | done(); 157 | }); 158 | }); 159 | 160 | it('should be a topic destroyed', done => { 161 | const {topic, zip} = getComponents(); 162 | topic 163 | .add({title: 'main topic 1'}) 164 | .add({title: 'main topic 2'}) 165 | .add({title: 'main topic 3'}) 166 | .add({title: ''}); 167 | 168 | topic.destroy(topic.cid('main topic 2')); 169 | topic.destroy(topic.cid('')); 170 | zip.save().then(async status => { 171 | expect(status).to.be.true; 172 | const p = getBuildTemporaryPath('default.xmind'); 173 | const content = fs.readFileSync(p); 174 | JSZip.loadAsync(content).then(async zip => { 175 | const text = await zip.file('content.json').async('text'); 176 | const map = JSON.parse(text)[0]; 177 | expect(map).to.be.an('object'); 178 | const {attached} = map.rootTopic.children; 179 | expect(attached.length).to.be.eq(2); 180 | expect(attached.find(child => child.title === topic.cid('main topic 2'))).to.be.undefined; 181 | expect(attached.find(child => child.title === topic.cid(''))).to.be.undefined; 182 | fs.unlinkSync(p); 183 | done(); 184 | }); 185 | }); 186 | }); 187 | 188 | 189 | it('should add image on topic', done => { 190 | const {topic, zip} = getComponents(); 191 | topic 192 | .add({title: 'main topic 1'}) 193 | .add({title: 'main topic 2'}); 194 | 195 | const mt1 = topic.on(topic.cid('main topic 1')); 196 | const key1 = mt1.image(); 197 | 198 | mt1.addLabel('attach label text on main topic 1').removeLabel(); 199 | 200 | zip.updateManifestMetadata(key1, fs.readFileSync(join(__dirname, '../fixtures/19442.png'))); 201 | const key2 = topic.on(topic.cid('main topic 2')).image(); 202 | zip.updateManifestMetadata(key2, fs.readFileSync(join(__dirname, '../fixtures/logo.png'))); 203 | zip.save().then(async status => { 204 | expect(status).to.be.true; 205 | const p = getBuildTemporaryPath('default.xmind'); 206 | const content = fs.readFileSync(p); 207 | JSZip.loadAsync(content).then(async zip => { 208 | const text = await zip.file('content.json').async('text'); 209 | const map = JSON.parse(text)[0]; 210 | expect(map).to.be.an('object'); 211 | const {attached} = map.rootTopic.children; 212 | expect(attached.length).to.gt(0); 213 | // expect(attached[0].labels).to.be.an('array'); 214 | // expect(attached[0].labels.length).to.gt(0); 215 | expect(attached[0].image.src).to.eq(`xap:${key1}`); 216 | expect(attached[1].image.src).to.eq(`xap:${key2}`); 217 | fs.unlinkSync(p); 218 | done(); 219 | }); 220 | }); 221 | }); 222 | 223 | }); 224 | 225 | describe('# Note', () => { 226 | 227 | it('attach a text note to main topic 1', done => { 228 | const { topic, workbook} = getComponents(); 229 | const title = 'main topic 1'; 230 | const text = 'this is a text note'; 231 | 232 | topic 233 | .add({title}) 234 | .on(topic.cid(title)) 235 | .note(text); 236 | 237 | const obj = workbook.toJSON()[0].rootTopic.children.attached[0]; 238 | expect(obj).to.have.property('notes'); 239 | expect(obj.notes.plain.content).to.eq(text); 240 | done(); 241 | }); 242 | 243 | it('detach a text note from main topic 1', done => { 244 | const { topic, workbook} = getComponents(); 245 | const title = 'main topic 1'; 246 | const text = 'this is a text note'; 247 | 248 | topic 249 | .add({title}) 250 | .on(topic.cid(title)) 251 | .note(text); 252 | 253 | topic.note(null, true); 254 | const obj = workbook.toJSON()[0].rootTopic.children.attached[0]; 255 | expect(obj).to.have.not.property('notes'); 256 | done(); 257 | }); 258 | 259 | }); 260 | 261 | describe('# Marker', () => { 262 | 263 | it('should be failed to add marker', done => { 264 | const {topic} = getComponents(); 265 | const title = 'main topic 1'; 266 | topic 267 | .add({title}) 268 | .on(topic.cid()) // topic.cid === last add title or topic.cid(title) 269 | // @ts-ignore 270 | .marker({}) 271 | // @ts-ignore 272 | .marker(); 273 | done(); 274 | }); 275 | 276 | it('should add the one of smiley marker flag', done => { 277 | const {topic, zip} = getComponents(); 278 | const marker = new Marker(); 279 | const title = 'main topic 1'; 280 | topic 281 | .add({title}) 282 | .on(topic.cid(title)) 283 | .marker(marker.smiley('cry')); 284 | zip.save().then(status => { 285 | expect(status).to.be.true; 286 | const p = getBuildTemporaryPath('default.xmind'); 287 | const content = fs.readFileSync(p); 288 | JSZip.loadAsync(content).then(async zip => { 289 | const text = await zip.file('content.json').async('text'); 290 | const map = JSON.parse(text)[0]; 291 | expect(map).to.be.an('object'); 292 | const {attached} = map.rootTopic.children 293 | expect(attached).to.be.an('array'); 294 | expect(attached.find(child => child.title === title)).to.have.property('markers').that.to.be.an('array'); 295 | fs.unlinkSync(p); 296 | done(); 297 | }); 298 | }); 299 | }); 300 | 301 | it('should be marker removed', done => { 302 | const {topic, workbook} = getComponents(); 303 | const marker = new Marker(); 304 | const title = 'main topic 1'; 305 | const cry = 'smiley-cry'; 306 | 307 | topic 308 | .on() 309 | .add({title}) 310 | .on(topic.cid()) 311 | .marker(marker.smiley('cry')) 312 | .marker(marker.week('fri')); 313 | 314 | // before destroy 315 | let ctx = workbook.toJSON(); 316 | let main = ctx[0].rootTopic.children.attached[0]; 317 | expect(main).to.have.property('markers').that.to.be.an('array'); 318 | expect(main.markers.length).to.eq(2); 319 | 320 | // delete 321 | topic.marker(Object.assign({}, marker.smiley('cry'), {del: true})); 322 | 323 | // after destroy 324 | ctx = workbook.toJSON(); 325 | main = ctx[0].rootTopic.children.attached[0]; 326 | expect(main).to.have.property('markers'); 327 | expect(main.markers.length).to.eq(1); 328 | expect(main.markers[0].markerId).to.not.eq(cry); 329 | done(); 330 | }); 331 | }); 332 | 333 | describe('# Summary', () => { 334 | it('should be edge ignored if topic id does not exists', done => { 335 | const {topic, zip} = getComponents(); 336 | 337 | topic 338 | .add({title: 'main topic 1'}) 339 | .on(topic.cid()) 340 | .add({title: 'subtopic 1'}) 341 | .add({title: 'subtopic 2'}) 342 | .summary({title: 'Test Summary', edge: 'does not exists'}); 343 | 344 | zip.save().then(status => { 345 | expect(status).to.be.true; 346 | const p = getBuildTemporaryPath('default.xmind'); 347 | const content = fs.readFileSync(p); 348 | JSZip.loadAsync(content).then(async zip => { 349 | const text = await zip.file('content.json').async('text'); 350 | const map = JSON.parse(text)[0]; 351 | expect(map).to.be.an('object'); 352 | expect(map).to.have.property('rootTopic'); 353 | expect(map.rootTopic).to.have.property('summaries'); 354 | expect(map.rootTopic.summaries[0]).to.have.property('range').that.to.be.an('string'); 355 | expect(map.rootTopic.summaries[0].range).to.eq('(0,0)'); 356 | fs.unlinkSync(p); 357 | done(); 358 | }); 359 | }); 360 | }); 361 | 362 | it('should be a summary object added that contains 1 main topic and 2 subtopics', done => { 363 | const {topic, zip} = getComponents(); 364 | 365 | topic 366 | .add({title: 'main topic 1'}) 367 | .on(topic.cid()) 368 | .add({title: 'subtopic 1'}) 369 | .add({title: 'subtopic 2'}) 370 | .summary({title: 'Test Summary'}); 371 | 372 | zip.save().then(status => { 373 | expect(status).to.be.true; 374 | const p = getBuildTemporaryPath('default.xmind'); 375 | const content = fs.readFileSync(p); 376 | JSZip.loadAsync(content).then(async zip => { 377 | const text = await zip.file('content.json').async('text'); 378 | const map = JSON.parse(text)[0]; 379 | expect(map).to.be.an('object'); 380 | expect(map).to.have.property('rootTopic'); 381 | expect(map.rootTopic).to.have.property('summaries'); 382 | expect(map.rootTopic.summaries[0]).to.have.property('range').that.to.be.an('string'); 383 | // contains 1 main topic 384 | // (0,0) 385 | // 1st: 0 - that's meaning where is the element start at children list 386 | // 2nd: 0 - that's meaning where is the element end at children list 387 | expect(map.rootTopic.summaries[0].range).to.eq('(0,0)'); 388 | fs.unlinkSync(p); 389 | done(); 390 | }); 391 | }); 392 | }); 393 | 394 | 395 | it('should be a summary object added that contains 2 main topic and 3 subtopics', done => { 396 | const {topic, zip} = getComponents(); 397 | 398 | topic 399 | .add({title: 'main topic 1'}) 400 | .add({title: 'main topic 2'}) 401 | .add({title: 'main topic 3'}) 402 | .on(topic.cid('main topic 1')) 403 | .add({title: 'subtopic 1'}) 404 | .add({title: 'subtopic 2'}) 405 | .on(topic.cid('main topic 2')) 406 | .add({title: 'subtopic 1'}) 407 | 408 | .on(topic.cid('main topic 1')) /* position topic title */ 409 | .summary({title: 'Test Summary', edge: topic.cid('main topic 2')}); 410 | 411 | zip.save().then(status => { 412 | expect(status).to.be.true; 413 | const p = getBuildTemporaryPath('default.xmind'); 414 | const content = fs.readFileSync(p); 415 | JSZip.loadAsync(content).then(async zip => { 416 | const text = await zip.file('content.json').async('text'); 417 | const map = JSON.parse(text)[0]; 418 | expect(map).to.be.an('object'); 419 | expect(map.rootTopic.summaries[0].range).to.eq('(0,1)'); 420 | fs.unlinkSync(p); 421 | done(); 422 | }); 423 | }); 424 | }); 425 | 426 | it('only contains 1 main topic if given a invalid range topic name', done => { 427 | const {topic, zip} = getComponents(); 428 | 429 | topic 430 | .add({title: 'main topic 1'}) 431 | .add({title: 'main topic 2'}) 432 | .add({title: 'main topic 3'}) 433 | .on(topic.cid('main topic 1')) 434 | .add({title: 'subtopic 1'}) 435 | .add({title: 'subtopic 2'}) 436 | .on(topic.cid('main topic 2')) 437 | .add({title: 'subtopic 1'}) 438 | 439 | .on(topic.cid('main topic 1')) /* position topic title */ 440 | .summary({title: 'Test Summary', edge: topic.cid('main topic does not exists')}); 441 | 442 | zip.save().then(status => { 443 | expect(status).to.be.true; 444 | const p = getBuildTemporaryPath('default.xmind'); 445 | const content = fs.readFileSync(p); 446 | JSZip.loadAsync(content).then(async zip => { 447 | const text = await zip.file('content.json').async('text'); 448 | const map = JSON.parse(text)[0]; 449 | expect(map).to.be.an('object'); 450 | expect(map.rootTopic.summaries[0].range).to.eq('(0,0)'); 451 | fs.unlinkSync(p); 452 | done(); 453 | }); 454 | }); 455 | }); 456 | 457 | it('only contains start position if the index position (start > end)', done => { 458 | const {topic, zip, workbook} = getComponents(); 459 | topic 460 | .add({title: 'main topic 1'}) 461 | .add({title: 'main topic 2'}) 462 | .add({title: 'main topic 3'}) 463 | .on(topic.cid('main topic 1')) 464 | .add({title: 'subtopic 1'}) 465 | .add({title: 'subtopic 2'}) 466 | .on(topic.cid('main topic 2')) 467 | .add({title: 'subtopic 1'}) 468 | 469 | .on(topic.cid('main topic 3')) /* position topic title */ 470 | .summary({title: 'Test Summary', edge: topic.cid('main topic 1')}); 471 | 472 | const {status, errors} = workbook.validate(); 473 | if (!status) { 474 | throw errors; 475 | } 476 | 477 | zip.save().then(status => { 478 | expect(status).to.be.true; 479 | const p = getBuildTemporaryPath('default.xmind'); 480 | const content = fs.readFileSync(p); 481 | JSZip.loadAsync(content).then(async zip => { 482 | const text = await zip.file('content.json').async('text'); 483 | const map = JSON.parse(text)[0]; 484 | expect(map).to.be.an('object'); 485 | expect(map.rootTopic.summaries[0].range).to.eq('(2,2)'); 486 | fs.unlinkSync(p); 487 | done(); 488 | }); 489 | }); 490 | }); 491 | }); 492 | 493 | describe('# Theme Test', () => { 494 | it('authenticating theme data 100 times', done => { 495 | for (let i = 0; i < 100; i++) { 496 | const workbook = new Workbook(); 497 | const sheet = workbook.createSheet('sheet1'); 498 | const topic = new Topic({sheet}); 499 | topic 500 | .on() 501 | .add({title: 'main topic 1'}) 502 | .add({title: 'main topic 2'}); 503 | 504 | workbook.theme('sheet1', 'snowbrush'); 505 | const data = workbook.toJSON()[0]; 506 | expect(data).to.have.property('theme'); 507 | expect(data.theme.title).to.eq('snowbrush'); 508 | } 509 | done(); 510 | }); 511 | 512 | it('authenticating theme 100 times after zip', async () => { 513 | for (let i = 0; i < 100; i++) { 514 | const workbook = new Workbook(); 515 | const sheet = workbook.createSheet('sheet1'); 516 | const topic = new Topic({sheet}); 517 | const zip = new Zipper({workbook, path: getBuildTemporaryPath(), filename: `test_${i}`}); 518 | topic 519 | .on() 520 | .add({title: 'main topic 1'}) 521 | .add({title: 'main topic 2'}); 522 | 523 | workbook.theme('sheet1', 'snowbrush'); 524 | await zip.save(); 525 | const file = join(getBuildTemporaryPath(), `test_${i}.xmind`); 526 | await JSZip.loadAsync(fs.readFileSync(file)) 527 | .then(zip => { 528 | return zip.file(`content.json`).async('text') 529 | }) 530 | .then(content => { 531 | const data = JSON.parse(content)[0] 532 | expect(data).to.have.property('theme'); 533 | expect(data.theme.title).to.eq('snowbrush'); 534 | fs.unlinkSync(file) 535 | }) 536 | .catch(err => { throw err }); 537 | } 538 | 539 | return null 540 | }); 541 | }); 542 | }); 543 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require ts-node/register 2 | --require source-map-support/register 3 | --reporter spec 4 | --recursive 5 | test/**/*.test.ts 6 | 7 | -------------------------------------------------------------------------------- /test/units/common.test.ts: -------------------------------------------------------------------------------- 1 | import { isEmpty, isObject, isString } from '../../src/utils/common'; 2 | import { expect } from 'chai'; 3 | 4 | 5 | describe('# Common Utils Test', function() { 6 | 7 | it('isEmpty(), should return true if given null', done => { 8 | expect(isEmpty(null)).to.eq(true); 9 | done(); 10 | }); 11 | 12 | it('isEmpty(), should return true if given undefined', done => { 13 | expect(isEmpty(undefined)).to.eq(true); 14 | done(); 15 | }); 16 | 17 | it('isEmpty(), should return true if given ""', done => { 18 | expect(isEmpty('')).to.eq(true); 19 | done(); 20 | }); 21 | 22 | it('isEmpty(), should return true if given [] || {}', done => { 23 | expect(isEmpty({})).to.eq(true); 24 | expect(isEmpty({})).to.eq(true); 25 | done(); 26 | }); 27 | 28 | it('isEmpty(), should return false if given false || 0 || "123"', done => { 29 | expect(isEmpty(false)).to.eq(false); 30 | expect(isEmpty(0)).to.eq(false); 31 | expect(isEmpty('123')).to.eq(false); 32 | done(); 33 | }); 34 | 35 | 36 | it('isEmpty(), should return false if given [1, 2, 3] || {a: 1}', done => { 37 | expect(isEmpty([1, 2, 3])).to.eq(false); 38 | expect(isEmpty({a: 1})).to.eq(false); 39 | done(); 40 | }); 41 | 42 | 43 | it('isObject(), should return false if given 0 || "" || null || undefined', done => { 44 | expect(isObject(0)).to.eq(false); 45 | expect(isObject('')).to.eq(false); 46 | expect(isObject(null)).to.eq(false); 47 | expect(isObject(undefined)).to.eq(false); 48 | done(); 49 | }); 50 | 51 | it('isObject(), should return true if given {} || [] || Function', done => { 52 | expect(isObject({})).to.eq(true); 53 | expect(isObject([])).to.eq(true); 54 | expect(isObject(() => {})).to.eq(true); 55 | done(); 56 | }); 57 | 58 | it('isString(), should return true if given any string', done => { 59 | expect(isString('')).to.eq(true); 60 | expect(isString('foo')).to.eq(true); 61 | done(); 62 | }) 63 | 64 | it('isString(), should return false if given any non string value', done => { 65 | expect(isString(42)).to.eq(false); 66 | expect(isString(null)).to.eq(false); 67 | expect(isString(undefined)).to.eq(false); 68 | expect(isString({})).to.eq(false); 69 | expect(isString(() => {})).to.eq(false); 70 | expect(isString([])).to.eq(false); 71 | expect(isString(new String)).to.eq(false); 72 | done(); 73 | }) 74 | }); 75 | -------------------------------------------------------------------------------- /test/units/dumper.test.ts: -------------------------------------------------------------------------------- 1 | import { Topic, Workbook } from '../../src'; 2 | import { Dumper } from '../../src/utils/dumper'; 3 | import { expect } from 'chai'; 4 | 5 | const unreachable = () => { throw new Error('unreachable'); }; 6 | 7 | describe('# Dumper Unit Test', () => { 8 | 9 | it('should be failed to create dumper if does not given the instance of workbook', done => { 10 | try { 11 | // @ts-ignore 12 | new Dumper(); 13 | } catch (e) { 14 | expect(e).to.be.an('error'); 15 | expect(e.message).to.eq('The instance of workbook is required'); 16 | done(); 17 | } 18 | }); 19 | 20 | it('should be able to update manifest', done => { 21 | const workbook = new Workbook(); 22 | const sheet = workbook.createSheet('s1','r1'); 23 | const topic = new Topic({ sheet }); 24 | const imageKey1 = topic.image(); 25 | const imageKey2 = topic.image(); 26 | const dumper = new Dumper({workbook}); 27 | 28 | const mockImageData1 = new Uint8Array(); 29 | dumper.updateManifestMetadata(imageKey1, mockImageData1, { 30 | file(name, content) { 31 | expect(name).to.be.a('string'); 32 | expect(content).to.be.an('Uint8Array'); 33 | return Promise.resolve(); 34 | }, 35 | folder(name) { 36 | expect(name).to.be.a('string'); 37 | return Promise.resolve(); 38 | } 39 | }) 40 | .then(() => ( 41 | dumper.updateManifestMetadata(imageKey2, mockImageData1, { 42 | file(name, content) { 43 | expect(name).to.be.a('string'); 44 | expect(content).to.be.an('Uint8Array'); 45 | return Promise.resolve(); 46 | }, 47 | folder(name) { 48 | expect(name).to.be.a('string'); 49 | return Promise.resolve(); 50 | } 51 | }) 52 | )) 53 | .then(() => { 54 | const files = dumper.dumping(); 55 | expect(files).to.be.an('array'); 56 | expect(files.length).to.eq(4); 57 | for (const obj of files) { 58 | expect(obj).to.have.property('filename'); 59 | expect(obj).to.have.property('value').that.to.be.an('string'); 60 | } 61 | const parsedManifest = JSON.parse(dumper.manifest.value) 62 | expect(parsedManifest['file-entries']).to.have.ownProperty(imageKey1) 63 | expect(parsedManifest['file-entries']).to.have.ownProperty(imageKey2) 64 | done(); 65 | }); 66 | }); 67 | 68 | it('should fail if the key is empty when updating manifest', done => { 69 | const workbook = new Workbook(); 70 | workbook.createSheet('s1','r1'); 71 | const invalidKey = ''; 72 | const dumper = new Dumper({workbook}); 73 | 74 | const mockImageData = new Uint8Array(); 75 | dumper.updateManifestMetadata(invalidKey, mockImageData, { 76 | file(name, content) { 77 | return unreachable(); 78 | }, 79 | folder(name) { 80 | return unreachable(); 81 | } 82 | }) 83 | .then(() => { 84 | return unreachable(); 85 | }) 86 | .catch((e) => { 87 | expect(e).to.be.an('error').with.property('message').include('key') 88 | done() 89 | }) 90 | }); 91 | 92 | it('should fail if creator failed to create file when updating manifest', done => { 93 | const workbook = new Workbook(); 94 | const sheet = workbook.createSheet('s1','r1'); 95 | const topic = new Topic({ sheet }) 96 | const imageKey = topic.image() 97 | const dumper = new Dumper({workbook}); 98 | 99 | const mockImageData = new Uint8Array(); 100 | dumper 101 | .updateManifestMetadata(imageKey, mockImageData, { 102 | file(name, content) { 103 | return unreachable(); 104 | }, 105 | folder(name) { 106 | throw new Error("create folder failed"); 107 | }, 108 | }) 109 | .then(() => { 110 | return unreachable(); 111 | }) 112 | .catch((e) => { 113 | expect(e).to.be.an("error").with.property("message").include("folder"); 114 | }) 115 | .then(() => 116 | dumper.updateManifestMetadata(imageKey, mockImageData, { 117 | file(name, content) { 118 | throw new Error("create file failed"); 119 | }, 120 | folder(name) { 121 | expect(name).to.be.a("string"); 122 | return Promise.resolve(); 123 | }, 124 | }) 125 | ) 126 | .then(() => { 127 | return unreachable(); 128 | }) 129 | .catch((e) => { 130 | expect(e).to.be.an("error").with.property("message").include("file"); 131 | done(); 132 | }); 133 | }); 134 | 135 | it('should dumping an array object that contains 4 files', done => { 136 | const workbook = new Workbook(); 137 | workbook.createSheet('s1','r1'); 138 | const dumper = new Dumper({workbook}); 139 | const files = dumper.dumping(); 140 | expect(files).to.be.an('array'); 141 | expect(files.length).to.eq(4); 142 | for (const obj of files) { 143 | expect(obj).to.have.property('filename'); 144 | expect(obj).to.have.property('value').that.to.be.an('string'); 145 | } 146 | done(); 147 | }); 148 | 149 | }); 150 | -------------------------------------------------------------------------------- /test/units/marker.test.ts: -------------------------------------------------------------------------------- 1 | import { Marker } from '../../src'; 2 | import { expect } from 'chai'; 3 | 4 | const marker = new Marker(); 5 | 6 | const groups = [ 7 | 'other', 'half', 'week', 8 | 'month', 'symbol', 'arrow', 9 | 'people', 'star', 'flag', 10 | 'task', 'priority', 'smiley' 11 | ]; 12 | 13 | describe('# Marker Unit Test', () => { 14 | it('should be found groups on instance of Marker', done => { 15 | for (const grp of groups) { 16 | expect(marker).to.have.property(grp).that.to.be.an('function'); 17 | } 18 | done(); 19 | }); 20 | 21 | it('should return an object that contains groupId & markerId', done => { 22 | expect(marker.smiley('cry')).to.have.property('groupId'); 23 | expect(marker.smiley('cry')).to.have.property('markerId'); 24 | done(); 25 | }); 26 | 27 | it('should return null if name is empty', done => { 28 | expect(marker.smiley('')).to.be.null; 29 | done(); 30 | }); 31 | 32 | it('should return null if name does not exists', done => { 33 | // @ts-ignore 34 | expect(marker.smiley('does not exists name')).to.be.null; 35 | done(); 36 | }); 37 | 38 | it('should return an object if name is a valid number for group priority', done => { 39 | // @ts-ignore 40 | expect(marker.priority(1)).to.have.property('groupId'); 41 | // @ts-ignore 42 | expect(marker.priority(2)).to.have.property('markerId'); 43 | done(); 44 | }); 45 | 46 | it('should return an array of group names', done => { 47 | expect(Marker.groups()).to.be.an('array'); 48 | expect(Marker.groups().length).to.greaterThan(0); 49 | done(); 50 | }); 51 | 52 | it('should find out an array of names', done => { 53 | const groups = Marker.groups(); 54 | expect(Marker.names(groups[0])).to.be.an('array'); 55 | expect(Marker.names(groups[0]).length).to.greaterThan(0); 56 | expect(Marker.names(null)).to.be.undefined; 57 | expect(Marker.names('{}')).to.be.undefined; 58 | expect(Marker.names(undefined)).to.be.undefined; 59 | // @ts-ignore 60 | expect(Marker.names(new Object())).to.be.undefined; 61 | done(); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/units/theme.test.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '../../src/core/theme'; 2 | import { expect } from 'chai'; 3 | 4 | 5 | describe('# Theme Unit Test', () => { 6 | it('should have an error if does given nothing to Theme', done => { 7 | try { 8 | new Theme(); 9 | } catch (e) { 10 | expect(e).to.be.an('error'); 11 | done(); 12 | } 13 | }); 14 | 15 | it('should return the robust theme object', done => { 16 | const theme = new Theme({themeName: 'robust'}); 17 | const data = theme.data; 18 | expect(data.title).to.be.eq('robust'); 19 | done(); 20 | }); 21 | 22 | it('should return the snowbrush theme object', done => { 23 | const theme = new Theme({themeName: 'snowbrush'}); 24 | const data = theme.data; 25 | expect(data.title).to.eq('snowbrush'); 26 | done(); 27 | }); 28 | 29 | it('should return the business theme object', done => { 30 | const theme = new Theme({themeName: 'business'}); 31 | const data = theme.data; 32 | expect(data.title).to.be.eq('business'); 33 | done(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/units/topic.test.ts: -------------------------------------------------------------------------------- 1 | import {Topic, Workbook, Zipper, Marker} from '../../src'; 2 | import {expect} from 'chai'; 3 | import * as fs from "fs"; 4 | import * as JSZip from 'jszip'; 5 | import {extend} from 'lodash'; 6 | 7 | const { getBuildTemporaryPath } = require('../fixtures/utils'); 8 | 9 | // @ts-ignore 10 | const getComponents = function() { 11 | const workbook = new Workbook(); 12 | const topic = new Topic({sheet: workbook.createSheet('sheet1', 'centralTopic')}); 13 | const zip = new Zipper({path: getBuildTemporaryPath(), workbook}); 14 | const marker = new Marker(); 15 | return {topic, workbook, zip, marker }; 16 | } 17 | 18 | describe('# Topic Unit Test', () => { 19 | it('should be failed to create instance of Topic with empty options', done => { 20 | try { 21 | // @ts-ignore 22 | new Topic(); 23 | } catch (e) { 24 | expect(e.message).to.be.eq('options.sheet is required'); 25 | done(); 26 | } 27 | }); 28 | 29 | it('should be failed to add topic with non string title', done => { 30 | const {topic} = getComponents(); 31 | try { 32 | topic.on().add(); 33 | } catch (e) { 34 | expect(e.message).to.be.eq('topic.title should be a valid string'); 35 | done(); 36 | } 37 | }); 38 | 39 | it('should be able to add topic with empty title', done => { 40 | const {topic} = getComponents(); 41 | 42 | topic.on().add({ title: '' }); 43 | 44 | const topics = topic.cids(); 45 | expect(topics).to.have.property(topic.cid('')); 46 | expect(topics).to.have.property(topic.cid('Central Topic')); 47 | expect(topics).to.have.property(topic.rootTopicId); 48 | done(); 49 | }); 50 | 51 | it('should be failed if call .on() with an invalid componentId', done => { 52 | try { 53 | const {topic} = getComponents(); 54 | // @ts-ignore 55 | topic.on({}); 56 | } catch (e) { 57 | expect(e).to.be.an('error'); 58 | done(); 59 | } 60 | }); 61 | 62 | it('should be failed to add a topic with an invalid componentId', done => { 63 | const doesNotExists = 'componentId does not exist'; 64 | try { 65 | const {topic} = getComponents(); 66 | topic 67 | .on() 68 | .add({title: 'main topic 1'}) 69 | .on(doesNotExists); 70 | } catch (e) { 71 | expect(e.message).to.be.eq(`Invalid componentId ${doesNotExists}`); 72 | done(); 73 | } 74 | }); 75 | 76 | it('should be failed to destroy topic with invalid title', done => { 77 | const {topic, zip} = getComponents(); 78 | topic 79 | .add({title: '1'}) 80 | .add({title: '2'}) 81 | .destroy('22'); 82 | 83 | // @ts-ignore 84 | topic.destroy(); 85 | 86 | zip.save().then(async status => { 87 | expect(status).to.be.true; 88 | const p = getBuildTemporaryPath('default.xmind'); 89 | const content = fs.readFileSync(p); 90 | JSZip.loadAsync(content).then(async zip => { 91 | const text = await zip.file('content.json').async('text'); 92 | const map = JSON.parse(text)[0]; 93 | expect(map).to.be.an('object'); 94 | const {attached} = map.rootTopic.children; 95 | expect(attached.length).to.be.eq(2); 96 | fs.unlinkSync(p); 97 | done(); 98 | }); 99 | }); 100 | }); 101 | 102 | it('should be failed to add summary on central topic', done => { 103 | const {topic} = getComponents(); 104 | topic.summary({title: 'Summary title'}); 105 | done(); 106 | }); 107 | 108 | it('should be failed to add note with empty text', done => { 109 | const {topic} = getComponents(); 110 | // @ts-ignore 111 | topic.note(); 112 | done(); 113 | }); 114 | 115 | it('should return all the topics that has been stored on the data set', done => { 116 | const {topic} = getComponents(); 117 | 118 | topic 119 | .add({title: '1'}) 120 | .add({title: '2'}); 121 | 122 | const topics = topic.cids(); 123 | expect(topics).to.have.property(topic.cid('1')); 124 | expect(topics).to.have.property(topic.cid('2')); 125 | expect(topics).to.have.property(topic.cid('Central Topic')); 126 | expect(topics).to.have.property(topic.rootTopicId); 127 | done(); 128 | }); 129 | 130 | it('should be .find(componentId?) worked with a componentId', done => { 131 | const {topic} = getComponents(); 132 | const component = topic.find(topic.rootTopicId) || null; 133 | expect(component).to.be.not.null; 134 | done(); 135 | }); 136 | 137 | it('should return a component of root topic by .find(componentId?) if does not given topicId', done => { 138 | const {topic} = getComponents(); 139 | const component = topic.find() || null; 140 | expect(component).to.be.not.null; 141 | done(); 142 | }); 143 | 144 | it('the component of summary should be created if does not given options', done => { 145 | const {topic, zip, marker } = getComponents(); 146 | topic.add({title: 'Programming Language'}) 147 | .add({title: 'Software Name'}) 148 | .add({title: 'Network device'}) 149 | .add({title: 'Computer Brand'}) 150 | .marker(marker.smiley('smile')) 151 | 152 | 153 | .on(topic.cid('Programming Language')) 154 | .add({title: 'dynamic'}) 155 | .add({title: 'static'}) 156 | 157 | .on(topic.cid()/* Also the topic.cid('static') is working */) 158 | .add({title: 'C'}) 159 | .add({title: 'C++'}) 160 | .add({title: '中文测试'}) 161 | .add({title: 'にほんご/にっぽんご'}) 162 | .add({title: 'mixed123中文ぽんご😋'}) 163 | .add({title: 'Java'}) 164 | .on(topic.cid('C')) 165 | .summary({title: 'Low level that is hard to learning', edge: topic.cid('C++')}) 166 | 167 | .on(topic.cid('dynamic')) 168 | .note('The static languages are fast more than dynamic language') 169 | .add({title: 'Node.js'}) 170 | .add({title: 'Python'}) 171 | .add({title: 'Ruby'}) 172 | .on(topic.cid('dynamic')) 173 | .summary({title: 'In popular'}) 174 | 175 | 176 | // on Software 177 | .on(topic.cid('Software')) 178 | .add({title: 'jetBrains'}) 179 | .add({title: 'Microsoft'}) 180 | 181 | .on(topic.cid('jetBrains')) 182 | .marker(marker.smiley('smile')) 183 | .add({title: 'WebStorm'}) 184 | .add({title: 'Pycharm'}) 185 | .add({title: 'CLion'}) 186 | .add({title: 'IntelliJ Idea'}) 187 | .add({title: 'etc.'}) 188 | .summary({title: 'all of the productions are belonging to jetbrains'}) 189 | 190 | .on(topic.cid('Microsoft')) 191 | .marker(marker.smiley('cry')) 192 | .add({title: 'vs code'}); 193 | 194 | zip.save().then(status => { 195 | status && done() 196 | console.info('==========', zip.target()) 197 | }); 198 | }); 199 | 200 | it('should return the central topic id if never to add component', done => { 201 | const {topic} = getComponents(); 202 | 203 | const id = topic.cid(); 204 | expect(id).to.eq(topic.rootTopic.getId()); 205 | expect(id).to.eq(topic.rootTopicId); 206 | done(); 207 | }); 208 | 209 | it('should be marker removed', done => { 210 | const {topic} = getComponents(); 211 | const marker = new Marker(); 212 | const cry = marker.smiley('cry'); 213 | topic 214 | .add({title: 'main topic 1'}) 215 | .marker(cry) 216 | // del 217 | .marker(extend({}, cry, {del: true})); 218 | 219 | done(); 220 | }); 221 | 222 | it('should return the componentId accurately, if the titles are duplicated', done => { 223 | const { topic } = getComponents(); 224 | topic.add({ title: 'main topic - 1', customId: 1 }); 225 | const a1 = topic.cid(); 226 | topic.add({ title: 'main topic - 1', customId: 2 }) 227 | const a2 = topic.cid(); 228 | expect(topic.cid('main topic - 1', { customId: 1 })).to.eq(a1); 229 | expect(topic.cid('main topic - 1', { customId: 2 })).to.eq(a2); 230 | 231 | topic.on(a2).add({title: 'abc'}); 232 | const abc1 = topic.cid(); 233 | topic.add({ title: 'bca' }); 234 | topic.on(a1).add({title: 'abc' }); 235 | const abc2 = topic.cid(); 236 | 237 | 238 | expect(topic.cid('abc', { parentId: a2})).to.eq(abc1); 239 | expect(topic.cid('abc', { parentId: a1})).to.eq(abc2); 240 | 241 | topic.add({ title: 'main topic - 1', customId: 3 }); 242 | const a3 = topic.cid(); 243 | topic.add({ title: 'main topic - 1', customId: 4 }); 244 | const a4 = topic.cid(); 245 | expect(topic.cid('main topic - 1', { customId: 3 })).to.eq(a3); 246 | expect(topic.cid('main topic - 1', { customId: 4 })).to.eq(a4); 247 | 248 | topic.on(a4).add({title: ''}); 249 | const emptyString1 = topic.cid(); 250 | topic.on(a3).add({title: ''}); 251 | const emptyString2 = topic.cid(); 252 | 253 | expect(topic.cid('', { parentId: a4 })).to.eq(emptyString1); 254 | expect(topic.cid('', { parentId: a3 })).to.eq(emptyString2); 255 | 256 | topic.destroy(topic.cid('abc', { parentId: a1})); 257 | topic.destroy(topic.cid('abc', { parentId: a2})); 258 | 259 | expect(topic.cid('abc', { parentId: a1})).to.eq(null); 260 | expect(topic.cid('abc', { parentId: a2})).to.eq(null); 261 | 262 | topic.destroy(topic.cid('', { parentId: a3})); 263 | topic.destroy(topic.cid('', { parentId: a4})); 264 | 265 | expect(topic.cid('', { parentId: a4})).to.eq(null); 266 | expect(topic.cid('', { parentId: a3})).to.eq(null); 267 | done(); 268 | }); 269 | }); 270 | -------------------------------------------------------------------------------- /test/units/workbook.test.ts: -------------------------------------------------------------------------------- 1 | import { Workbook } from '../../src'; 2 | import { expect } from 'chai'; 3 | 4 | describe('# Workbook Unit Test', () => { 5 | 6 | it('should be workbook created', () => { 7 | const workbook1 = new Workbook(); 8 | expect(workbook1 instanceof Workbook).to.be.true; 9 | }); 10 | 11 | it('should be workbook created on darwin/linux system', () => { 12 | const workbook1 = new Workbook(); 13 | expect(workbook1 instanceof Workbook).to.be.true; 14 | }); 15 | 16 | it('should set theme to be failed if given an invalid theme name', done => { 17 | const workbook = new Workbook(); 18 | workbook.createSheet('sheet-1'); 19 | const ret = workbook.theme('sheet-1', 'robust'); 20 | expect(ret).to.be.true; 21 | done(); 22 | }); 23 | 24 | it('should be failed if sheet title as empty', done => { 25 | try { 26 | const workbook = new Workbook(); 27 | // @ts-ignore 28 | workbook.createSheet(); 29 | } catch (e) { 30 | expect(e.message).to.be.eq('The title of sheet is required'); 31 | done(); 32 | } 33 | }); 34 | 35 | it('should be failed if given a title of the sheet of duplication', done => { 36 | try { 37 | const workbook = new Workbook(); 38 | workbook.createSheet('sheet-1'); 39 | workbook.createSheet('sheet-1'); 40 | } catch (e) { 41 | expect(e.message).to.be.eq('You are trying to create the sheet repeatedly that is not allowed'); 42 | done(); 43 | } 44 | }); 45 | 46 | 47 | it('should be an error throwing out if getSheet with a wrong sheetId', done => { 48 | const workbook = new Workbook(); 49 | try { 50 | // @ts-ignore 51 | workbook.createSheets(); 52 | } catch (err) { 53 | expect(err instanceof Error).to.be.true; 54 | } 55 | 56 | workbook.createSheets([ 57 | {s: 'sheetTitle_1', t: 'topicTitle_1'}, 58 | {s: 'sheetTitle_2', t: 'topicTitle_2'} 59 | ]); 60 | 61 | try { 62 | // @ts-ignore Trust me Typescript 63 | workbook.getSheet(); 64 | } catch (err) { 65 | expect(err instanceof Error).to.be.true; 66 | } 67 | 68 | const sheets = workbook.getSheets(); 69 | const sheet = workbook.getSheet(sheets[0].id); 70 | expect(sheet).to.be.an('object'); 71 | done(); 72 | }); 73 | 74 | it('should be success to create multiple sheets', done => { 75 | const workbook = new Workbook(); 76 | workbook.createSheets([ 77 | {s: 'sheetTitle_1', t: 'topicTitle_1'}, 78 | {s: 'sheetTitle_2', t: 'topicTitle_2'} 79 | ]); 80 | const sheets = workbook.getSheets(); 81 | expect(sheets.length).to.eq(2); 82 | done(); 83 | }); 84 | 85 | }); 86 | -------------------------------------------------------------------------------- /test/units/zip.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { join } from 'path'; 3 | import * as fs from 'fs'; 4 | // @ts-ignore 5 | import { getBuildTemporaryPath } from '../fixtures/utils'; 6 | const { Zipper } = require('../../src'); 7 | 8 | describe('# Zip Unit Test', () => { 9 | it('should return error if path does not specified', done => { 10 | const options = {path: '', filename: 'test'}; 11 | try { new Zipper(options) } catch (e) { 12 | expect(e).to.be.an('error'); 13 | expect(e.message).to.eq('the `path` is required or must exists'); 14 | done(); 15 | } 16 | }); 17 | 18 | it('should test.xmind to be saved', async () => { 19 | const options = { path: getBuildTemporaryPath(), filename: 'test' }; 20 | const zipper = new Zipper(options); 21 | const content: { id: string; title: string } = { id: '1231231313', title: 'sheet1' }; 22 | zipper.addJSONContent(content); 23 | await zipper.save(); 24 | const p = join(options.path, options.filename + '.xmind'); 25 | expect(fs.existsSync(p)).be.eq(true); 26 | fs.unlinkSync(p); 27 | return null; 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "compilerOptions": { 4 | "module": "CommonJS" 5 | } 6 | }, 7 | "compilerOptions": { 8 | "baseUrl": ".", 9 | "paths": { "*": ["types/*"] }, 10 | "target": "es6", 11 | "module": "CommonJS", 12 | "moduleResolution": "node", 13 | "experimentalDecorators": true, 14 | "resolveJsonModule": true, 15 | "noImplicitThis": true, 16 | "noUnusedLocals": true, 17 | "stripInternal": true, 18 | "pretty": true, 19 | "declaration": true, 20 | "outDir": "dist/", 21 | "rootDir": "src/", 22 | "lib": ["es5", "es6", "dom"], 23 | "sourceMap": true, 24 | "inlineSources": true, 25 | "allowJs": false 26 | }, 27 | "exclude": [ 28 | "dist", 29 | "node_modules", 30 | "test", 31 | "scripts", 32 | "docs", 33 | "example", 34 | "rollup.config.ts" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "linterOptions": { 3 | "exclude": ["**/test/**/**", "dist", "docs"] 4 | }, 5 | "rules": { 6 | "no-inferrable-types": true, 7 | "no-namespace": [true, "allow-declarations"], 8 | "semicolon": [true, "always", "ignore-interfaces"], 9 | "no-non-null-assertion": true, 10 | "no-unnecessary-type-assertion": false, 11 | "promise-function-async": false, 12 | "await-promise": false, 13 | "ban": false, 14 | "quotemark": [true, "single"], 15 | "no-implicit-dependencies": false, 16 | "member-access": false, 17 | "no-var-requires": false, 18 | "ordered-imports": false, 19 | "object-literal-sort-keys": false, 20 | "arrow-parens": false, 21 | "no-string-literal": false, 22 | "no-submodule-imports": false, 23 | "max-line-length": false, 24 | "no-empty": [true, "allow-empty-functions"], 25 | "no-empty-interface": false, 26 | "no-console": false, 27 | "space-before-function-paren": [ true, "asyncArrow" ], 28 | "no-shadowed-variable": false, 29 | "max-classes-per-file": false, 30 | "forin": false, 31 | "only-arrow-functions": false, 32 | "member-ordering": false, 33 | "no-unnecessary-initializer": false, 34 | "no-this-assignment": [true, {"allowed-names": ["^self$"], "allow-destructuring": true}], 35 | "trailing-comma": [true, {"multiline": {"imports": "ignore"}}], 36 | "interface-name": false, 37 | "variable-name": false, 38 | "switch-final-break": false, 39 | "return-undefined": false, 40 | "prefer-while": false, 41 | "prefer-template": false, 42 | "prefer-method-signature": false, 43 | "prefer-object-spread": false, 44 | "prefer-function-over-method": false, 45 | "prefer-conditional-expression": false 46 | } 47 | } 48 | --------------------------------------------------------------------------------