├── .gitattributes ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── resources └── icon.png ├── src ├── constant │ └── index.ts ├── extension.ts ├── services │ ├── image.ts │ ├── index.ts │ ├── markdown.ts │ └── xmind.ts ├── test │ ├── extension.test.ts │ └── index.ts └── types │ └── index.ts ├── tsconfig.json ├── tslint.json ├── webui ├── .babelrc ├── .bowerrc ├── .gitignore ├── .jscsrc ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── favicon.ico ├── index.html ├── less │ ├── _navigator.less │ ├── _tool_group.less │ ├── _vars.less │ ├── editor.less │ ├── imageDialog.less │ └── topTab │ │ ├── appearance │ │ ├── colorPanel.less │ │ ├── export.less │ │ ├── fontOperator.less │ │ ├── layout.less │ │ ├── styleOperator.less │ │ ├── templatePanel.less │ │ └── themePanel.less │ │ ├── idea │ │ ├── appendNode.less │ │ ├── arrange.less │ │ ├── hyperlink.less │ │ ├── image.less │ │ ├── note.less │ │ ├── noteEditor.less │ │ ├── operation.less │ │ ├── priority.less │ │ ├── progress.less │ │ ├── resource.less │ │ └── undoRedo.less │ │ ├── searchBox.less │ │ ├── topTab.less │ │ └── view │ │ ├── expand.less │ │ ├── search.less │ │ └── select.less ├── main.js ├── mindmap.html ├── package.json ├── rollup.config.js ├── server │ └── imageUpload.php ├── src │ ├── editor.js │ ├── expose-editor.js │ ├── hotbox.js │ ├── lang.js │ ├── minder.js │ ├── runtime │ │ ├── clipboard-mimetype.js │ │ ├── clipboard.js │ │ ├── container.js │ │ ├── drag.js │ │ ├── fsm.js │ │ ├── history.js │ │ ├── hotbox.js │ │ ├── input.js │ │ ├── jumping.js │ │ ├── minder.js │ │ ├── node.js │ │ ├── priority.js │ │ ├── progress.js │ │ └── receiver.js │ └── tool │ │ ├── debug.js │ │ ├── format.js │ │ ├── innertext.js │ │ ├── jsondiff.js │ │ ├── key.js │ │ └── keymap.js ├── ui │ ├── dialog │ │ ├── hyperlink │ │ │ ├── hyperlink.ctrl.js │ │ │ └── hyperlink.tpl.html │ │ ├── imExportNode │ │ │ ├── imExportNode.ctrl.js │ │ │ └── imExportNode.tpl.html │ │ └── image │ │ │ ├── image.ctrl.js │ │ │ └── image.tpl.html │ ├── directive │ │ ├── appendNode │ │ │ ├── appendNode.directive.js │ │ │ └── appendNode.html │ │ ├── arrange │ │ │ ├── arrange.directive.js │ │ │ └── arrange.html │ │ ├── colorPanel │ │ │ ├── colorPanel.directive.js │ │ │ └── colorPanel.html │ │ ├── expandLevel │ │ │ ├── expandLevel.directive.js │ │ │ └── expandLevel.html │ │ ├── export │ │ │ ├── export.directive.js │ │ │ └── export.html │ │ ├── fontOperator │ │ │ ├── fontOperator.directive.js │ │ │ └── fontOperator.html │ │ ├── hyperLink │ │ │ ├── hyperLink.directive.js │ │ │ └── hyperLink.html │ │ ├── imageBtn │ │ │ ├── imageBtn.directive.js │ │ │ └── imageBtn.html │ │ ├── kityminderEditor │ │ │ ├── kityminderEditor.directive.js │ │ │ └── kityminderEditor.html │ │ ├── kityminderViewer │ │ │ ├── kityminderViewer.directive.js │ │ │ └── kityminderViewer.html │ │ ├── layout │ │ │ ├── layout.directive.js │ │ │ └── layout.html │ │ ├── navigator │ │ │ ├── navigator.directive.js │ │ │ └── navigator.html │ │ ├── noteBtn │ │ │ ├── noteBtn.directive.js │ │ │ └── noteBtn.html │ │ ├── noteEditor │ │ │ ├── noteEditor.directive.js │ │ │ └── noteEditor.html │ │ ├── notePreviewer │ │ │ ├── notePreviewer.directive.js │ │ │ └── notePreviewer.html │ │ ├── operation │ │ │ ├── operation.directive.js │ │ │ └── operation.html │ │ ├── priorityEditor │ │ │ ├── priorityEditor.directive.js │ │ │ └── priorityEditor.html │ │ ├── progressEditor │ │ │ ├── progressEditor.directive.js │ │ │ └── progressEditor.html │ │ ├── resourceEditor │ │ │ ├── resourceEditor.directive.js │ │ │ └── resourceEditor.html │ │ ├── searchBox │ │ │ ├── searchBox.directive.js │ │ │ └── searchBox.html │ │ ├── searchBtn │ │ │ ├── searchBtn.directive.js │ │ │ └── searchBtn.html │ │ ├── selectAll │ │ │ ├── selectAll.directive.js │ │ │ └── selectAll.html │ │ ├── styleOperator │ │ │ ├── styleOperator.directive.js │ │ │ └── styleOperator.html │ │ ├── templateList │ │ │ ├── templateList.directive.js │ │ │ └── templateList.html │ │ ├── themeList │ │ │ ├── themeList.directive.js │ │ │ └── themeList.html │ │ ├── topTab │ │ │ ├── topTab.directive.js │ │ │ └── topTab.html │ │ └── undoRedo │ │ │ ├── undoRedo.directive.js │ │ │ └── undoRedo.html │ ├── filter │ │ ├── command.filters.js │ │ └── lang.filter.js │ ├── images │ │ ├── iconpriority.png │ │ ├── iconprogress.png │ │ ├── icons.png │ │ └── template.png │ ├── kityminder.app.js │ └── service │ │ ├── commandBinder.service.js │ │ ├── config.service.js │ │ ├── lang.en.service.js │ │ ├── lang.zh-cn.service.js │ │ ├── memory.service.js │ │ ├── minder.service.js │ │ ├── resource.service.js │ │ ├── revokeDialog.service.js │ │ ├── server.service.js │ │ └── valueTransfer.service.js └── yarn.lock └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | webui/node_modules 6 | webui/bower_components 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | webui/jscsrc -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | }; 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "eg2.tslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 14 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 15 | "preLaunchTask": "npm: watch" 16 | }, 17 | { 18 | "name": "Extension Tests", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "args": [ 23 | "--extensionDevelopmentPath=${workspaceFolder}", 24 | "--extensionTestsPath=${workspaceFolder}/out/test" 25 | ], 26 | "outFiles": ["${workspaceFolder}/out/test/**/*.js"], 27 | "preLaunchTask": "npm: watch" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "editor.formatOnSave": true 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | out/**/*.map 5 | src/** 6 | webui/node_modules/** 7 | webui/bower_components/** 8 | webui/src/** 9 | webui/less/** 10 | webui/ui/** 11 | webui/server/** 12 | webui/bower.json 13 | webui/favicon.ico 14 | webui/Gruntfile.js 15 | webui/index.html 16 | webui/LICENSE 17 | webui/main.js 18 | webui/package.json 19 | webui/README.md 20 | webui/rollup.config.js 21 | webui/yarn.lock 22 | webui/.babelrc 23 | webui/.bowerrc 24 | webui/.jscsrc 25 | !webui/node_modules/kityminder-core/dist/kityminder.core.css 26 | !webui/node_modules/kityminder-core/dist/kityminder.core.min.js 27 | !webui/bower_components/bootstrap/dist/css/bootstrap.css 28 | !webui/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 29 | !webui/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.woff 30 | !webui/bower_components/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf 31 | !webui/bower_components/codemirror/lib/codemirror.css 32 | !webui/bower_components/hotbox/hotbox.css 33 | !webui/bower_components/color-picker/dist/color-picker.min.css 34 | !webui/bower_components/jquery/dist/jquery.js 35 | !webui/bower_components/bootstrap/dist/js/bootstrap.js 36 | !webui/bower_components/angular/angular.js 37 | !webui/bower_components/angular-bootstrap/ui-bootstrap-tpls.js 38 | !webui/bower_components/codemirror/lib/codemirror.js 39 | !webui/bower_components/codemirror/mode/xml/xml.js 40 | !webui/bower_components/codemirror/mode/javascript/javascript.js 41 | !webui/bower_components/codemirror/mode/css/css.js 42 | !webui/bower_components/codemirror/mode/htmlmixed/htmlmixed.js 43 | !webui/bower_components/codemirror/mode/markdown/markdown.js 44 | !webui/bower_components/codemirror/addon/mode/overlay.js 45 | !webui/bower_components/codemirror/mode/gfm/gfm.js 46 | !webui/bower_components/angular-ui-codemirror/ui-codemirror.js 47 | !webui/bower_components/marked/lib/marked.js 48 | !webui/bower_components/kity/dist/kity.min.js 49 | !webui/bower_components/hotbox/hotbox.js 50 | !webui/bower_components/json-diff/json-diff.js 51 | !webui/bower_components/color-picker/dist/color-picker.min.js 52 | .gitignore 53 | tsconfig.json 54 | vsc-extension-quickstart.md 55 | tslint.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 0.0.2 - (2019.4.23) 4 | 5 | - first release 6 | - transpile .xmind to .km 7 | - export mindmap files to pictures(eg: png) 8 | 9 | ## 0.0.3 - (2019.4.28) 10 | 11 | - add the function of automatically opening mindmaps 12 | 13 | ## 0.0.4 - (2019.5.9) 14 | 15 | - fix the error of opening the mindmap on the markdown file and .gitignore 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

vscode-mindmap

3 |

4 | 5 | ![mindmap](https://img.souche.com/f2e/f12837b4057a8f1c5dd5033560a48f20.gif) 6 | 7 | ## Features 8 | 9 | - File Edit(eg: .km), Save, Export 10 | - Transpile .xmind to .km 11 | - Support export to image(.png) 12 | 13 | ## Installation 14 | 15 | Install through VS Code extensions. Search for "vscode-mindmap" 16 | 17 | Visual Studio Code Market Place: vscode-mindmap 18 | 19 | ## Usage 20 | 21 | open any file with extension of km or xmind after install the plugin 22 | 23 | ## Keyboard Shortcuts 24 | 25 | | Key | Command | 26 | | -------------------------------- | ------------------------------ | 27 | | cmd + m(mac) / ctrl + m(windows) | open a webview of textDocument | 28 | | cmd + s(mac) / ctrl + s(windows) | save mindmap file | 29 | 30 | ## FAQ 31 | - **File parsed incorrectly, the current webview only show initial mindmap** 32 | 33 | close the file and webview, try to reopen this file 34 | 35 | - **Extension invalid when the plugin installed** 36 | 37 | check your vscode version, please ensure the version is 1.29.0 or above 38 | 39 | ## Feedback 40 | 41 | ![Feedback](https://img.souche.com/f2e/5e127e01cf164f9f9cff4892653d7d02.jpeg) 42 | 43 | ## 44 | 45 | [大搜车无线开发中心](https://blog.souche.com/tag/frontend/) Present 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-mindmap", 3 | "displayName": "vscode-mindmap", 4 | "icon": "resources/icon.png", 5 | "description": "mindmap for vscode", 6 | "version": "0.0.5", 7 | "publisher": "Souche", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/souche/vscode-mindmap.git" 11 | }, 12 | "engines": { 13 | "vscode": "^1.29.0" 14 | }, 15 | "categories": [ 16 | "Other" 17 | ], 18 | "activationEvents": [ 19 | "onLanguage:xmind", 20 | "onLanguage:km", 21 | "onCommand:extension.mindmap" 22 | ], 23 | "main": "./out/extension", 24 | "contributes": { 25 | "languages": [ 26 | { 27 | "id": "km", 28 | "extensions": [ 29 | ".km" 30 | ], 31 | "aliases": [ 32 | "KM", 33 | "km" 34 | ] 35 | }, 36 | { 37 | "id": "xmind", 38 | "extensions": [ 39 | ".xmind" 40 | ], 41 | "aliases": [ 42 | "XMIND", 43 | "xmind" 44 | ] 45 | } 46 | ], 47 | "commands": [ 48 | { 49 | "command": "extension.mindmap", 50 | "title": "mindmap" 51 | } 52 | ], 53 | "keybindings": [ 54 | { 55 | "command": "extension.mindmap", 56 | "key": "ctrl+m", 57 | "mac": "cmd+m", 58 | "when": "editorTextFocus" 59 | } 60 | ] 61 | }, 62 | "scripts": { 63 | "vscode:prepublish": "npm run compile", 64 | "compile": "tsc -p ./", 65 | "watch": "tsc -watch -p ./", 66 | "postinstall": "node ./node_modules/vscode/bin/install", 67 | "test": "npm run compile && node ./node_modules/vscode/bin/test" 68 | }, 69 | "dependencies": { 70 | "xmind": "^0.5.0" 71 | }, 72 | "devDependencies": { 73 | "typescript": "^2.6.1", 74 | "vscode": "^1.1.21", 75 | "tslint": "^5.8.0", 76 | "@types/node": "^8.10.25", 77 | "@types/mocha": "^2.2.42" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souche/vscode-mindmap/94b0b5a572cd7bf9971a52a82e0aa9ffb1354725/resources/icon.png -------------------------------------------------------------------------------- /src/constant/index.ts: -------------------------------------------------------------------------------- 1 | // resource placeholder for mindmap.html 2 | export const resourceSchema: string = 'vscode-resource'; 3 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import * as vscode from 'vscode'; 3 | import * as path from 'path'; 4 | import * as fs from 'fs'; 5 | import { resourceSchema } from './constant'; 6 | import { Xmind, Img } from './services'; 7 | const matchableFileTypes: string[] = ['xmind', 'km']; 8 | 9 | // type ProcessService = Xmind | Markdown; 10 | 11 | export function activate(context: vscode.ExtensionContext) { 12 | const openedPanelMap = new Map(); 13 | let isFirstActivate: boolean = true; 14 | let timer: any = null; 15 | let disposable = vscode.commands.registerTextEditorCommand( 16 | 'extension.mindmap', 17 | () => { 18 | const editor: vscode.TextEditor | undefined = 19 | vscode.window.activeTextEditor; 20 | const onDiskPath = vscode.Uri.file( 21 | path.join(context.extensionPath, 'webui', 'mindmap.html') 22 | ); 23 | const resourcePath = vscode.Uri.file( 24 | path.join(context.extensionPath, 'webui') 25 | ); 26 | const resourceRealPath = resourcePath.with({ scheme: resourceSchema }); 27 | const fileContent = 28 | process.platform === 'win32' 29 | ? fs.readFileSync(onDiskPath.path.slice(1)).toString() 30 | : fs.readFileSync(onDiskPath.path).toString(); 31 | const html = fileContent.replace( 32 | /\$\{vscode\}/g, 33 | resourceRealPath.toString() 34 | ); 35 | const fileName = (editor).document.fileName; 36 | const basename = path.basename(fileName); 37 | const extName = path.extname(fileName); 38 | const xmindService = new Xmind(fileName); 39 | const imgService = new Img(); 40 | const importData = getImportData(fileName, extName, xmindService) || '{}'; 41 | 42 | if (!matchableFileTypes.includes(extName.slice(1))) { 43 | return; 44 | } 45 | 46 | const panel = createWebviewPanel(basename); 47 | panel.webview.html = html; 48 | panel.webview.onDidReceiveMessage( 49 | message => { 50 | let destFileName = ''; 51 | switch (message.command) { 52 | case 'loaded': 53 | panel.webview.postMessage({ 54 | command: 'import', 55 | importData, 56 | extName, 57 | }); 58 | return; 59 | 60 | case 'save': 61 | try { 62 | const retData = JSON.parse(message.exportData); 63 | destFileName = 64 | extName === '.xmind' 65 | ? fileName.replace(/(\.xmind)/, '.km') 66 | : fileName; 67 | 68 | writeFileToDisk(destFileName, JSON.stringify(retData, null, 4)); 69 | } catch (ex) { 70 | console.error(ex); 71 | } 72 | return; 73 | 74 | case 'exportToImage': 75 | const buffer = imgService.base64ToPng(message.exportData); 76 | destFileName = fileName.replace(/(\.xmind|\.kme|\.km)/, '.png'); 77 | writeFileToDisk(destFileName, buffer); 78 | return; 79 | } 80 | }, 81 | undefined, 82 | context.subscriptions 83 | ); 84 | 85 | panel.onDidDispose( 86 | () => { 87 | // emit event to webview 88 | }, 89 | null, 90 | context.subscriptions 91 | ); 92 | } 93 | ); 94 | 95 | const executeFirstCommand = (originFileName: string) => { 96 | if (isFirstActivate) { 97 | isFirstActivate = false; 98 | openedPanelMap.set(originFileName, true); 99 | timer = setTimeout(() => { 100 | vscode.commands.executeCommand('extension.mindmap'); 101 | }, 300); 102 | } 103 | }; 104 | 105 | context.subscriptions.push(disposable); 106 | executeFirstCommand( 107 | (vscode.window.activeTextEditor as vscode.TextEditor).document.fileName 108 | ); 109 | vscode.workspace.onDidOpenTextDocument(e => { 110 | const originFileName = e.fileName.replace('.git', ''); 111 | if (isFirstActivate) { 112 | executeFirstCommand(originFileName); 113 | return; 114 | } 115 | 116 | if (!openedPanelMap.get(originFileName)) { 117 | openedPanelMap.set(originFileName, true); 118 | timer = setTimeout(() => { 119 | vscode.commands.executeCommand('extension.mindmap', originFileName); 120 | }, 300); 121 | } 122 | }); 123 | vscode.workspace.onDidCloseTextDocument(e => { 124 | if (e.fileName.endsWith('.git')) { 125 | return; 126 | } 127 | 128 | if (openedPanelMap.get(e.fileName)) { 129 | clearTimeout(timer); 130 | openedPanelMap.set(e.fileName, false); 131 | } 132 | }); 133 | } 134 | 135 | export function deactivate() {} 136 | 137 | /** 138 | * create webview 139 | * @param fileName 140 | */ 141 | function createWebviewPanel(fileName: string) { 142 | return vscode.window.createWebviewPanel( 143 | 'mindMap', 144 | `${fileName}-mindmap`, 145 | vscode.ViewColumn.One, 146 | { 147 | enableScripts: true, 148 | retainContextWhenHidden: true, 149 | } 150 | ); 151 | } 152 | 153 | /** 154 | * processing source data 155 | * @param fileName 156 | * @param extName 157 | */ 158 | function getImportData(fileName: string, extName: string, xmind: Xmind) { 159 | if (extName === '.xmind') { 160 | return JSON.stringify(xmind.process()); 161 | } 162 | 163 | return fs.readFileSync(fileName).toString(); 164 | } 165 | 166 | function writeFileToDisk(fileName: string, data: any) { 167 | fs.writeFile(fileName, data, (err: any) => { 168 | if (err) { 169 | vscode.window.showErrorMessage(`write ${fileName} failed`); 170 | console.log(err); 171 | throw err; 172 | } 173 | vscode.window.showInformationMessage(`write ${fileName} successed`); 174 | }); 175 | } 176 | -------------------------------------------------------------------------------- /src/services/image.ts: -------------------------------------------------------------------------------- 1 | export class Img { 2 | base64ToPng(base64: string) { 3 | const formattedBase64Str = base64.replace(/^data:image\/\w+;base64,/, ''); 4 | 5 | return Buffer.from(formattedBase64Str, 'base64'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './xmind'; 2 | export * from './markdown'; 3 | export * from './image'; 4 | -------------------------------------------------------------------------------- /src/services/markdown.ts: -------------------------------------------------------------------------------- 1 | export class Markdown {} 2 | -------------------------------------------------------------------------------- /src/services/xmind.ts: -------------------------------------------------------------------------------- 1 | import { KMRootNode, KMSubNode } from '../types'; 2 | const xmind = require('xmind'); 3 | 4 | export class Xmind { 5 | private filename: string; 6 | constructor(filename: string) { 7 | this.filename = filename; 8 | } 9 | 10 | process() { 11 | const workbook = xmind.open(this.filename); 12 | const primarySheet = workbook.getPrimarySheet(); 13 | const rootTopic = primarySheet.rootTopic; 14 | const result: KMRootNode = { 15 | root: {}, 16 | template: 'right', 17 | theme: 'fresh-blue-compat', 18 | version: '1.4.43', 19 | }; 20 | 21 | const walkTopic = (topic: any) => { 22 | const item: KMSubNode = { 23 | data: { 24 | id: topic.id, 25 | text: topic.getTitle(), 26 | created: Date.now(), 27 | }, 28 | children: [], 29 | }; 30 | 31 | if (topic.children && topic.children.length) { 32 | item.children = topic.children.map((child: any) => walkTopic(child)); 33 | } 34 | 35 | return item; 36 | }; 37 | 38 | result.root = walkTopic(rootTopic); 39 | 40 | return result; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | // import * as vscode from 'vscode'; 12 | // import * as myExtension from '../extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", function () { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", function() { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /src/test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | import * as testRunner from 'vscode/lib/testrunner'; 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true, // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; 23 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface KMRootNode { 2 | root: any; 3 | template: string; 4 | theme: string; 5 | version: string; 6 | } 7 | 8 | export interface KMSubNode { 9 | data: any; 10 | children: any[]; 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": ["es6", "es2017"], 7 | "sourceMap": true, 8 | "rootDir": "src", 9 | /* Strict Type-Checking Option */ 10 | "strict": true /* enable all strict type-checking options */, 11 | /* Additional Checks */ 12 | "noUnusedLocals": true /* Report errors on unused locals. */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": ["node_modules", ".vscode-test"] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [true, "always"], 9 | "triple-equals": true 10 | }, 11 | "defaultSeverity": "warning" 12 | } 13 | -------------------------------------------------------------------------------- /webui/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env"]] 3 | } 4 | -------------------------------------------------------------------------------- /webui/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "allow_root": true, 4 | "registry": "https://registry.bower.io" 5 | } 6 | -------------------------------------------------------------------------------- /webui/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | bower_components/ 4 | node_modules/ 5 | dist/ 6 | ui/templates.js 7 | .tmp/ 8 | upload/ -------------------------------------------------------------------------------- /webui/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "validateIndentation": 4, 3 | 4 | "requireSpaceBeforeBlockStatements": true, 5 | 6 | "requireSpaceAfterKeywords": [ 7 | "if", 8 | "else", 9 | "for", 10 | "while", 11 | "do", 12 | "try", 13 | "catch", 14 | "finally" 15 | ], 16 | 17 | "requireLeftStickedOperators": [",", ";"], 18 | 19 | "requireSpaceBeforeBinaryOperators": [ 20 | "+", 21 | "-", 22 | "*", 23 | "/", 24 | "=", 25 | "==", 26 | "===", 27 | "!=", 28 | "!==", 29 | "|", 30 | "||", 31 | "&", 32 | "&&" 33 | ], 34 | "requireSpaceAfterBinaryOperators": [ 35 | "+", 36 | "-", 37 | "*", 38 | "/", 39 | "=", 40 | "==", 41 | "===", 42 | "!=", 43 | "!==", 44 | "|", 45 | "||", 46 | "&", 47 | "&&", 48 | ":" 49 | ], 50 | 51 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 52 | 53 | "disallowSpacesInFunctionExpression": { 54 | "beforeOpeningRoundBrace": true 55 | }, 56 | 57 | "disallowSpacesInsideParentheses": true, 58 | 59 | "disallowTrailingWhitespace": true, 60 | 61 | "maximumLineLength": 120, 62 | 63 | "requireOperatorBeforeLineBreak": [ 64 | "?", 65 | "+", 66 | "-", 67 | "/", 68 | "*", 69 | "=", 70 | "==", 71 | "===", 72 | "!=", 73 | "!==", 74 | ">", 75 | ">=", 76 | "<", 77 | "<=", 78 | ",", 79 | ";", 80 | "&&", 81 | "&", 82 | "||", 83 | "|" 84 | ], 85 | 86 | "validateQuoteMarks": "'", 87 | 88 | "disallowMultipleLineStrings": true 89 | } 90 | -------------------------------------------------------------------------------- /webui/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "undef": true, 3 | "unused": false, 4 | "strict": false, 5 | "curly": false, 6 | "newcap": true, 7 | "trailing": true, 8 | "white": false, 9 | "quotmark": false, 10 | "browser": true, 11 | "boss": true, 12 | "indent": 4, 13 | "predef": ["define"] 14 | } 15 | -------------------------------------------------------------------------------- /webui/README.md: -------------------------------------------------------------------------------- 1 | KityMinder Editor 2 | ========== 3 | 4 | ## 简介 5 | 6 | KityMinder Editor 是一款强大、简洁、体验优秀的脑图编辑工具,适合用于编辑树/图/网等结构的数据。 7 | 8 | 编辑器由百度 [FEX](https://github.com/fex-team) 基于 [kityminder-core](https://github.com/fex-team/kityminder-core) 搭建,并且在[百度脑图](http://naotu.baidu.com)中使用。 9 | 10 | 他们的区别与联系如下: 11 | 12 | ![KityMinder 联系](relations.png "KityMinder 联系") 13 | 14 | - [kityminder-core](https://github.com/fex-team/kityminder-core) 是 kityminder 的核心部分,基于百度 [FEX](https://github.com/fex-team) 开发的矢量图形库 [kity](https://github.com/fex-team/kity)。包含了脑图数据的可视化展现,简单编辑功能等所有底层支持。 15 | - [kityminder-editor](https://github.com/fex-team/kityminder-editor) 基于 kityminder-core 搭建,依赖于 AngularJS,包含 UI 和热盒 [hotbox](https://github.com/fex-team/hotbox) 等方便用户输入的功能,简单来说,就是一款编辑器。 16 | - [百度脑图](http://naotu.baidu.com) 基于 kityminder-editor,加入了第三方格式导入导出 (FreeMind, XMind, MindManager) 、文件储存、用户认证、文件分享、历史版本等业务逻辑。 17 | 18 | ## 功能 19 | 20 | - 基本操作:文本编辑,节点折叠、插入、删除、排序、归纳、复制、剪切、粘贴等 21 | - 样式控制:字体、加粗、斜体、颜色、样式拷贝、样式粘贴等 22 | - 图标:优先级、进度等 23 | - 历史:撤销/重做 24 | - 标签:多标签贴入 25 | - 备注:支持 Markdown 格式备注 26 | - 图片:支持本地/网络/搜索图片插入 27 | - 超链接:支持 HTTP/HTTPS/MAIL/FTP 链接插入 28 | - 布局:支持多种布局切换 29 | - 主题:支持多种主题切换 30 | - 数据导入导出:支持多种格式的导入,多种格式(包括图片)的导出 31 | - 缩略图:支持缩略图查看/导航 32 | 33 | ## 开发使用 34 | 根目录下的 `index.html` 为开发环境,`dist` 目录下的 `index.html` 使用打包好的代码,适用于线上环境。 35 | 36 | 1. 安装 [nodejs](http://nodejs.org) 和 [npm](https://docs.npmjs.com/getting-started/installing-node) 37 | 2. 初始化:切到 kityminder-editor 根目录下运行 `npm run init` 38 | 3. 在 kityminder-editor 根目录下运行 `grunt dev` 即可启动项目 39 | 4. 你可以基于根目录的 `index.html` 开发,或者查看 `dist` 目录下用于生产环境的 `index.html`,Enjoy it! 40 | 41 | 另外,kityminder-editor 还提供了 bower 包,方便开发者直接使用。你可以在需要用到 kityminder-editor 的工程目录下 42 | 运行 `bower install kityminder-editor`,接着手动引入 kityminder-editor 所依赖的 css 和 js 文件,具体文件见 43 | `dist` 目录下的 `index.html`,推荐使用 npm 包 [wireDep](https://www.npmjs.com/package/wiredep) 自动进行, 44 | 可参考根目录下 `Gruntfile.js`。 45 | 46 | ## 构建 47 | 运行 `grunt build`,完成后 `dist` 目录里就是可用运行的 kityminder-editor, 双击 `index.html` 即可打开运行示例 48 | 49 | ## 初始化配置 50 | 用户可以根据需要,配置 `kityminder-editor`, 具体使用方法如下: 51 | ``` 52 | angular.module('kityminderDemo', ['kityminderEditor']) 53 | .config(function (configProvider) { 54 | configProvider.set('imageUpload', 'path/to/image/upload/handler'); 55 | }); 56 | 57 | ``` 58 | 59 | ## 数据导入导出 60 | 由于 kityminder-editor 是基于 kityminder-core 搭建的,而 kityminder-core 内置了五种常见 61 | 格式的导入或导出,在创建编辑器实例之后,可以使用四个接口进行数据的导入导出。 62 | 63 | * `editor.minder.exportJson()` - 导出脑图数据为 JSON 对象 64 | * `editor.minder.importJson(json)` - 导入 JSON 对象为当前脑图数据 65 | * `editor.minder.exportData(protocol, option)` - 导出脑图数据为指定的数据格式,返回一个 Promise,其值为导出的结果 66 | * `editor.minder.importData(protocol, data, option)` - 导入指定格式的数据为脑图数据,返回一个 Promise,其值为转换之后的脑图 Json 数据 67 | 68 | 目前支持的数据格式包括: 69 | 70 | * `json` - JSON 字符串,支持导入和导出 71 | * `text` - 纯文本格式,支持导入和导出 72 | * `markdown` - Markdown 格式,支持导入和导出 73 | * `svg` - SVG 矢量格式,仅支持导出 74 | * `png` - PNG 位图格式,仅支持导出 75 | 76 | 更多格式的支持,可以加载 [kityminder-protocol](https://github.com/fex-team/kityminder-protocol) 来扩展第三方格式支持。 77 | 78 | 数据格式的具体信息,可参考 [kityminder-core-wiki 的中的说明](https://github.com/fex-team/kityminder-core/wiki)。 79 | 80 | ## 联系我们 81 | 问题和建议反馈: 82 | 83 | [Github issues](https://github.com/fex-team/kityminder-editor/issues) 84 | 85 | 邮件组:kity@baidu.com 86 | 87 | QQ 讨论群:475962105 -------------------------------------------------------------------------------- /webui/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kityminder-editor", 3 | "version": "1.0.61", 4 | "authors": [ 5 | "fex" 6 | ], 7 | "description": "Kity Minder Editor", 8 | "main": [ 9 | "dist/kityminder.editor.js", 10 | "dist/kityminder.editor.css" 11 | ], 12 | "keywords": [ 13 | "kityminder", 14 | "fex", 15 | "ui", 16 | "javascript", 17 | "html5", 18 | "svg" 19 | ], 20 | "license": "BSD", 21 | "homepage": "https://github.com/fex-team/kityminder-editor", 22 | "ignore": [ 23 | "**/.*", 24 | "node_modules", 25 | "bower_components", 26 | "test", 27 | "tests", 28 | "less", 29 | "ui", 30 | "src", 31 | "Gruntfile.js", 32 | "package.json" 33 | ], 34 | "devDependencies": { 35 | "seajs": "~2.3.0" 36 | }, 37 | "dependencies": { 38 | "bootstrap": "~3.3.4", 39 | "angular": "~1.3.15", 40 | "angular-bootstrap": "~0.12.1", 41 | "angular-ui-codemirror": "~0.2.3", 42 | "codemirror": "~4.8.0", 43 | "marked": "git://github.com/chjj/marked.git#master", 44 | "hotbox": "~1.0.2", 45 | "color-picker": "~1.0.2", 46 | "kity": "^2.0.5", 47 | "json-diff": "*" 48 | }, 49 | "overrides": { 50 | "codemirror": { 51 | "main": [ 52 | "lib/codemirror.js", 53 | "lib/codemirror.css", 54 | "mode/xml/xml.js", 55 | "mode/javascript/javascript.js", 56 | "mode/css/css.js", 57 | "mode/htmlmixed/htmlmixed.js", 58 | "mode/markdown/markdown.js", 59 | "addon/mode/overlay.js", 60 | "mode/gfm/gfm.js" 61 | ] 62 | } 63 | }, 64 | "resolutions": { 65 | "angular": "~1.3.8" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /webui/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souche/vscode-mindmap/94b0b5a572cd7bf9971a52a82e0aa9ffb1354725/webui/favicon.ico -------------------------------------------------------------------------------- /webui/less/_navigator.less: -------------------------------------------------------------------------------- 1 | .nav-bar { 2 | position: absolute; 3 | width: 35px; 4 | height: 240px; 5 | padding: 5px 0; 6 | left: 10px; 7 | bottom: 10px; 8 | background: #fc8383; 9 | color: #fff; 10 | border-radius: 4px; 11 | z-index: 10; 12 | box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.2); 13 | transition: -webkit-transform .7s 0.1s ease; 14 | transition: transform .7s 0.1s ease; 15 | 16 | .nav-btn { 17 | width: 35px; 18 | height: 24px; 19 | line-height: 24px; 20 | text-align: center; 21 | 22 | .icon { 23 | background: url(images/icons.png); 24 | width: 20px; 25 | height: 20px; 26 | margin: 2px auto; 27 | display: block; 28 | } 29 | 30 | &.active { 31 | background-color: #5A6378; 32 | } 33 | } 34 | 35 | .zoom-in .icon { 36 | background-position: 0 -730px; 37 | } 38 | 39 | .zoom-out .icon { 40 | background-position: 0 -750px; 41 | } 42 | 43 | .hand .icon { 44 | background-position: 0 -770px; 45 | width: 25px; 46 | height: 25px; 47 | margin: 0 auto; 48 | } 49 | 50 | .camera .icon { 51 | background-position: 0 -870px; 52 | width: 25px; 53 | height: 25px; 54 | margin: 0 auto; 55 | } 56 | 57 | .nav-trigger .icon { 58 | background-position: 0 -845px; 59 | width: 25px; 60 | height: 25px; 61 | margin: 0 auto; 62 | } 63 | 64 | .zoom-pan { 65 | width: 2px; 66 | height: 70px; 67 | box-shadow: 0 1px #E50000; 68 | position: relative; 69 | background: white; 70 | margin: 3px auto; 71 | overflow: visible; 72 | 73 | .origin { 74 | position: absolute; 75 | width: 20px; 76 | height: 8px; 77 | left: -9px; 78 | margin-top: -4px; 79 | background: transparent; 80 | 81 | &:after { 82 | content: ' '; 83 | display: block; 84 | width: 6px; 85 | height: 2px; 86 | background: white; 87 | left: 7px; 88 | top: 3px; 89 | position: absolute; 90 | } 91 | } 92 | 93 | .indicator { 94 | position: absolute; 95 | width: 8px; 96 | height: 8px; 97 | left: -3px; 98 | background: white; 99 | border-radius: 100%; 100 | margin-top: -4px; 101 | } 102 | 103 | } 104 | } 105 | 106 | .nav-previewer { 107 | background: #fff; 108 | width: 140px; 109 | height: 120px; 110 | position: absolute; 111 | left: 45px; 112 | bottom: 30px; 113 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.2); 114 | border-radius: 0 2px 2px 0; 115 | padding: 1px; 116 | z-index: 9; 117 | cursor: crosshair; 118 | transition: -webkit-transform .7s 0.1s ease; 119 | transition: transform .7s 0.1s ease; 120 | 121 | &.grab { 122 | cursor: move; 123 | cursor: -webkit-grabbing; 124 | cursor: -moz-grabbing; 125 | cursor: grabbing; 126 | } 127 | } -------------------------------------------------------------------------------- /webui/less/_tool_group.less: -------------------------------------------------------------------------------- 1 | .tool-group { 2 | padding: 0; 3 | 4 | &[disabled] { 5 | opacity: 0.5; 6 | } 7 | 8 | .tool-group-item { 9 | display: inline-block; 10 | border-radius: 4px; 11 | 12 | .tool-group-icon { 13 | width: 20px; 14 | height: 20px; 15 | padding: 2px; 16 | margin: 1px; 17 | } 18 | 19 | &:hover {background-color: @button-hover;} 20 | &:active {background-color: @button-active;} 21 | 22 | &.active {background-color: @button-active;} 23 | } 24 | } -------------------------------------------------------------------------------- /webui/less/_vars.less: -------------------------------------------------------------------------------- 1 | @button-hover: hsl(222, 55%, 96%); 2 | @button-active: hsl(222, 55%, 85%); 3 | @button-pressed: hsl(222, 55%, 90%); 4 | 5 | @tool-hover: #eff3fa; 6 | @tool-active: #c4d0ee; 7 | @tool-selected: #87a9da; -------------------------------------------------------------------------------- /webui/less/editor.less: -------------------------------------------------------------------------------- 1 | .km-editor { 2 | overflow: hidden; 3 | z-index: 2; 4 | } 5 | 6 | .km-editor > .mask { 7 | display: block; 8 | position: absolute; 9 | left: 0; 10 | right: 0; 11 | top: 0; 12 | bottom: 0; 13 | background-color: transparent; 14 | } 15 | 16 | .km-editor > .receiver { 17 | position: absolute; 18 | background: white; 19 | outline: none; 20 | box-shadow: 0 0 20px fadeout(black, 50%); 21 | left: 0; 22 | top: 0; 23 | padding: 3px 5px; 24 | margin-left: -3px; 25 | margin-top: -5px; 26 | max-width: 300px; 27 | width: auto; 28 | overflow: hidden; 29 | font-size: 14px; 30 | line-height: 1.4em; 31 | min-height: 1.4em; 32 | box-sizing: border-box; 33 | overflow: hidden; 34 | word-break: break-all; 35 | word-wrap: break-word; 36 | border: none; 37 | -webkit-user-select: text; 38 | pointer-events: none; 39 | opacity: 0; 40 | z-index: -1000; 41 | &.debug { 42 | opacity: 1; 43 | outline: 1px solid green; 44 | background: none; 45 | z-index: 0; 46 | } 47 | 48 | &.input { 49 | pointer-events: all; 50 | opacity: 1; 51 | z-index: 999; 52 | background: white; 53 | outline: none; 54 | } 55 | } 56 | 57 | div.minder-editor-container { 58 | position: absolute; 59 | top: 0; 60 | bottom: 0; 61 | left: 0; 62 | right: 0; 63 | font-family: Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 64 | 'WenQuanYi Micro Hei', sans-serif; 65 | } 66 | 67 | .minder-editor { 68 | position: absolute; 69 | top: 92px; 70 | left: 0; 71 | right: 0; 72 | bottom: 0; 73 | } 74 | 75 | .minder-viewer { 76 | position: absolute; 77 | top: 0; 78 | left: 0; 79 | right: 0; 80 | bottom: 0; 81 | } 82 | 83 | .control-panel { 84 | position: absolute; 85 | top: 0; 86 | right: 0; 87 | width: 250px; 88 | bottom: 0; 89 | border-left: 1px solid #ccc; 90 | } 91 | .minder-divider { 92 | position: absolute; 93 | top: 0; 94 | right: 250px; 95 | bottom: 0; 96 | width: 2px; 97 | background-color: rgb(251, 251, 251); 98 | cursor: ew-resize; 99 | } 100 | 101 | // @override bootstrap 102 | .panel-body { 103 | padding: 10px; 104 | } 105 | 106 | @import (less) '_vars.less'; 107 | @import (less) 'imageDialog.less'; 108 | @import (less) 'topTab/topTab.less'; 109 | @import (less) 'topTab/idea/undoRedo.less'; 110 | @import (less) 'topTab/idea/appendNode.less'; 111 | @import (less) 'topTab/idea/arrange.less'; 112 | @import (less) 'topTab/idea/operation.less'; 113 | @import (less) 'topTab/idea/hyperlink.less'; 114 | @import (less) 'topTab/idea/image.less'; 115 | @import (less) 'topTab/idea/note.less'; 116 | @import (less) 'topTab/idea/noteEditor.less'; 117 | @import (less) 'topTab/idea/priority.less'; 118 | @import (less) 'topTab/idea/progress.less'; 119 | @import (less) 'topTab/idea/resource.less'; 120 | @import (less) 'topTab/appearance/templatePanel.less'; 121 | @import (less) 'topTab/appearance/themePanel.less'; 122 | @import (less) 'topTab/appearance/layout.less'; 123 | @import (less) 'topTab/appearance/styleOperator.less'; 124 | @import (less) 'topTab/appearance/fontOperator.less'; 125 | @import (less) 'topTab/appearance/colorPanel.less'; 126 | @import (less) 'topTab/appearance/export.less'; 127 | @import (less) 'topTab/view/expand.less'; 128 | @import (less) 'topTab/view/select.less'; 129 | @import (less) 'topTab/view/search.less'; 130 | @import (less) 'topTab/searchBox.less'; 131 | @import (less) '_tool_group.less'; 132 | @import (less) '_navigator.less'; 133 | -------------------------------------------------------------------------------- /webui/less/imageDialog.less: -------------------------------------------------------------------------------- 1 | .upload-image { 2 | width: 0.1px; 3 | height: 0.1px; 4 | opacity: 0; 5 | overflow: hidden; 6 | position: absolute; 7 | z-index: -1; 8 | } -------------------------------------------------------------------------------- /webui/less/topTab/appearance/colorPanel.less: -------------------------------------------------------------------------------- 1 | .bg-color-wrap { 2 | display: inline-block; 3 | width: 30px; 4 | height: 22px; 5 | margin: 3px 3px 0 0; 6 | border: 1px #efefef solid; 7 | vertical-align: middle; 8 | font-size: 0; 9 | -webkit-user-select: none; 10 | -moz-user-select: none; 11 | -ms-user-select: none; 12 | user-select: none; 13 | 14 | &[disabled] { 15 | opacity: 0.5; 16 | } 17 | 18 | .quick-bg-color { 19 | display: inline-block; 20 | width: 20px; 21 | height: 16px; 22 | font-size: 14px; 23 | line-height: 16px; 24 | vertical-align: top; 25 | text-align: center; 26 | cursor: default; 27 | color: #000; 28 | background: url(images/icons.png) no-repeat center -1260px; 29 | 30 | &:hover { 31 | background-color: @tool-hover; 32 | } 33 | 34 | &:active { 35 | background-color: @tool-active; 36 | } 37 | 38 | &[disabled] { 39 | opacity: 0.5; 40 | } 41 | } 42 | 43 | .bg-color-preview { 44 | display: inline-block; 45 | width: 12px; 46 | height: 2px; 47 | margin: 0 4px 0; 48 | background-color: #fff; 49 | 50 | &[disabled] { 51 | opacity: 0.5; 52 | } 53 | } 54 | } 55 | 56 | .bg-color { 57 | display: inline-block; 58 | width: 8px; 59 | height: 16px; 60 | 61 | &:hover { 62 | background-color: @tool-hover; 63 | } 64 | 65 | &:active { 66 | background-color: @tool-active; 67 | } 68 | 69 | &[disabled] { 70 | opacity: 0.5; 71 | } 72 | 73 | .caret { 74 | margin-left: -2px; 75 | margin-top: 7px; 76 | } 77 | } -------------------------------------------------------------------------------- /webui/less/topTab/appearance/export.less: -------------------------------------------------------------------------------- 1 | .export { 2 | .km-btn-export { 3 | font-size: 14px; 4 | padding: 6px 10px; 5 | margin-right: 5px; 6 | border-radius: 4px; 7 | color: #fff; 8 | font-weight: 500; 9 | } 10 | .km-export-save { 11 | background: rgba(24, 144, 255, 1); 12 | border-color: rgba(24, 144, 255, 1); 13 | &:hover { 14 | background: rgba(24, 144, 255, 0.8); 15 | } 16 | } 17 | .km-export-image { 18 | background: rgba(104, 203, 131, 1); 19 | border-color: rgba(104, 203, 131, 1); 20 | &:hover { 21 | background: rgba(104, 203, 131, 0.8); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /webui/less/topTab/appearance/fontOperator.less: -------------------------------------------------------------------------------- 1 | .font-operator { 2 | width: 220px; 3 | display: inline-block; 4 | vertical-align: middle; 5 | font-size: 12px; 6 | padding: 0 5px; 7 | 8 | .font-size-list { 9 | display: inline-block; 10 | border: 1px solid #eee; 11 | padding: 2px 4px; 12 | } 13 | 14 | .font-family-list { 15 | display: inline-block; 16 | border: 1px solid #eee; 17 | padding: 2px 4px; 18 | } 19 | } 20 | 21 | .current-font-item a { 22 | text-decoration: none; 23 | display: inline-block; 24 | } 25 | 26 | .current-font-family { 27 | width: 75px; 28 | height: 18px; 29 | overflow: hidden; 30 | vertical-align: bottom; 31 | } 32 | .current-font-size { 33 | width: 44px; 34 | height: 18px; 35 | overflow: hidden; 36 | vertical-align: bottom; 37 | } 38 | 39 | .current-font-item[disabled] { 40 | opacity: 0.5; 41 | } 42 | 43 | .font-item { 44 | line-height: 1em; 45 | text-align: left; 46 | } 47 | 48 | .font-item-selected { 49 | background-color: @tool-selected; 50 | } 51 | 52 | .font-bold, 53 | .font-italics { 54 | display: inline-block; 55 | background: url(images/icons.png) no-repeat; 56 | cursor: pointer; 57 | margin: 0 3px; 58 | 59 | &:hover { 60 | background-color: @tool-hover; 61 | } 62 | 63 | &:active { 64 | background-color: @tool-active; 65 | } 66 | 67 | &[disabled] { 68 | opacity: 0.5; 69 | } 70 | } 71 | 72 | .font-bold { 73 | background-position: 0 -240px; 74 | } 75 | 76 | .font-italics { 77 | background-position: 0 -260px; 78 | } 79 | 80 | .font-bold-selected, 81 | .font-italics-selected { 82 | background-color: @tool-selected; 83 | } 84 | 85 | .font-color-wrap { 86 | display: inline-block; 87 | width: 30px; 88 | height: 22px; 89 | margin: 3px 3px 0 0; 90 | border: 1px #efefef solid; 91 | vertical-align: middle; 92 | font-size: 0; 93 | -webkit-user-select: none; 94 | -moz-user-select: none; 95 | -ms-user-select: none; 96 | user-select: none; 97 | 98 | &[disabled] { 99 | opacity: 0.5; 100 | } 101 | 102 | .quick-font-color { 103 | display: inline-block; 104 | width: 20px; 105 | height: 16px; 106 | font-size: 14px; 107 | line-height: 16px; 108 | vertical-align: top; 109 | text-align: center; 110 | cursor: default; 111 | color: #000; 112 | 113 | &:hover { 114 | background-color: @tool-hover; 115 | } 116 | 117 | &:active { 118 | background-color: @tool-active; 119 | } 120 | 121 | &[disabled] { 122 | opacity: 0.5; 123 | } 124 | } 125 | 126 | .font-color-preview { 127 | display: inline-block; 128 | width: 12px; 129 | height: 2px; 130 | margin: 0 4px 0; 131 | background-color: #000; 132 | 133 | &[disabled] { 134 | opacity: 0.5; 135 | } 136 | } 137 | } 138 | 139 | .font-color { 140 | display: inline-block; 141 | width: 8px; 142 | height: 16px; 143 | 144 | &:hover { 145 | background-color: @tool-hover; 146 | } 147 | 148 | &:active { 149 | background-color: @tool-active; 150 | } 151 | 152 | &[disabled] { 153 | opacity: 0.5; 154 | } 155 | 156 | .caret { 157 | margin-left: -2px; 158 | margin-top: 7px; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /webui/less/topTab/appearance/layout.less: -------------------------------------------------------------------------------- 1 | .readjust-layout { 2 | display: inline-block; 3 | vertical-align: middle; 4 | padding: 0 10px 0 5px; 5 | border-right: 1px dashed #eee; 6 | } 7 | 8 | .btn-icon { 9 | width: 25px; 10 | height: 25px; 11 | margin-left: 12px; 12 | display: block; 13 | } 14 | 15 | .btn-label { 16 | font-size: 12px; 17 | } 18 | 19 | .btn-wrap { 20 | width: 50px; 21 | height: 42px; 22 | cursor: pointer; 23 | display: inline-block; 24 | text-decoration: none; 25 | 26 | &[disabled] span { 27 | opacity: 0.5; 28 | } 29 | 30 | &[disabled] { 31 | cursor: default; 32 | } 33 | 34 | &[disabled]:hover { 35 | background-color: transparent; 36 | } 37 | 38 | &[disabled]:active { 39 | background-color: transparent; 40 | } 41 | 42 | &:link { 43 | text-decoration: none; 44 | } 45 | 46 | &:visited { 47 | text-decoration: none; 48 | } 49 | 50 | &:hover { 51 | background-color: @tool-hover; 52 | text-decoration: none; 53 | } 54 | 55 | &:active { 56 | background-color: @tool-active; 57 | } 58 | 59 | } 60 | 61 | .reset-layout-icon { 62 | background: url(images/icons.png) no-repeat; 63 | background-position: 0 -150px; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /webui/less/topTab/appearance/styleOperator.less: -------------------------------------------------------------------------------- 1 | .style-operator { 2 | display: inline-block; 3 | vertical-align: middle; 4 | padding: 0 5px; 5 | border-right: 1px dashed #eee; 6 | 7 | .clear-style { 8 | vertical-align: middle; 9 | } 10 | 11 | } 12 | 13 | .clear-style-icon { 14 | background: url(images/icons.png) no-repeat; 15 | background-position: 0 -175px;; 16 | } 17 | 18 | .s-btn-group-vertical { 19 | display: inline-block; 20 | vertical-align: middle; 21 | } 22 | 23 | .s-btn-icon { 24 | width: 20px; 25 | height: 20px; 26 | margin-right: 3px; 27 | display: inline-block; 28 | vertical-align: middle; 29 | } 30 | 31 | .s-btn-label { 32 | font-size: 12px; 33 | vertical-align: middle; 34 | display: inline-block; 35 | } 36 | 37 | .s-btn-wrap { 38 | // margin-bottom: 2px; 39 | padding: 0 5px 0 3px; 40 | display: inline-block; 41 | text-decoration: none; 42 | font-size: 0; 43 | 44 | &[disabled] span { 45 | opacity: 0.5; 46 | } 47 | 48 | &[disabled] { 49 | cursor: default; 50 | } 51 | 52 | &[disabled]:hover { 53 | background-color: transparent; 54 | } 55 | 56 | &[disabled]:active { 57 | background-color: transparent; 58 | } 59 | 60 | &:hover { 61 | background-color: @tool-hover; 62 | text-decoration: none; 63 | } 64 | 65 | &:active { 66 | background-color: @tool-active; 67 | } 68 | 69 | } 70 | 71 | .copy-style-icon { 72 | background: url(images/icons.png) no-repeat; 73 | background-position: 0 -200px; 74 | } 75 | 76 | .paste-style-wrap { 77 | display: block; 78 | } 79 | 80 | .paste-style-icon { 81 | background: url(images/icons.png) no-repeat; 82 | background-position: 0 -220px; 83 | } -------------------------------------------------------------------------------- /webui/less/topTab/appearance/templatePanel.less: -------------------------------------------------------------------------------- 1 | .temp-panel { 2 | margin: 5px 5px 5px 10px; 3 | border-right: 1px dashed #eee; 4 | display: inline-block; 5 | vertical-align: middle; 6 | } 7 | 8 | .temp-list { 9 | min-width: 124px; 10 | } 11 | 12 | .temp-item-wrap { 13 | width: 50px; 14 | height: 40px; 15 | padding: 0 2px; 16 | margin: 5px; 17 | display: inline-block; 18 | } 19 | 20 | .temp-item { 21 | display: inline-block; 22 | width: 50px; 23 | height: 40px; 24 | background-image: url(images/template.png); 25 | background-repeat: no-repeat; 26 | 27 | &.default { 28 | background-position: 0 0; 29 | } 30 | 31 | &.structure { 32 | background-position: -50px 0; 33 | } 34 | 35 | &.filetree { 36 | background-position: -100px 0; 37 | } 38 | 39 | &.right { 40 | background-position: -150px 0; 41 | } 42 | 43 | &.fish-bone { 44 | background-position: -200px 0; 45 | } 46 | 47 | &.tianpan { 48 | background-position: -250px 0; 49 | } 50 | } 51 | 52 | .current-temp-item { 53 | width: 74px; 54 | padding: 0 0 0 5px; 55 | border: 1px solid #fff; 56 | 57 | &:hover { 58 | background-color: @tool-hover; 59 | } 60 | 61 | &[disabled] { 62 | opacity: 0.5; 63 | } 64 | 65 | .caret { 66 | margin-left: 5px; 67 | } 68 | } 69 | .temp-item-selected { 70 | background-color: @tool-selected; 71 | } -------------------------------------------------------------------------------- /webui/less/topTab/appearance/themePanel.less: -------------------------------------------------------------------------------- 1 | .theme-panel { 2 | height: 42px; 3 | margin: 5px; 4 | padding: 0 5px 0 0; 5 | border-right: 1px dashed #eee; 6 | display: inline-block; 7 | vertical-align: middle; 8 | } 9 | 10 | .theme-list { 11 | min-width: 225px; 12 | } 13 | 14 | div a.theme-item { 15 | display: inline-block; 16 | width: 100px; 17 | height: 30px; 18 | text-align: center; 19 | line-height: 30px; 20 | padding: 0 5px; 21 | font-size: 12px; 22 | cursor: pointer; 23 | text-decoration: none; 24 | color: #000; 25 | } 26 | 27 | .theme-item-selected { 28 | width: 100px; 29 | padding: 6px 7px; 30 | border: 1px solid #fff; 31 | 32 | &:hover { 33 | background-color: @tool-hover; 34 | } 35 | 36 | .caret { 37 | margin-left: 5px; 38 | } 39 | 40 | &[disabled] { 41 | opacity: 0.5; 42 | } 43 | } 44 | 45 | .theme-item-wrap { 46 | display: inline-block; 47 | width: 110px; 48 | height: 40px; 49 | padding: 5px; 50 | } 51 | .theme-item-wrap:hover { 52 | background-color: #eff3fa; 53 | } 54 | -------------------------------------------------------------------------------- /webui/less/topTab/idea/appendNode.less: -------------------------------------------------------------------------------- 1 | .append-group { 2 | width: 212px; 3 | } 4 | 5 | .append-child-node { 6 | .km-btn-icon { 7 | background-position: 0 0; 8 | } 9 | } 10 | 11 | .append-sibling-node { 12 | .km-btn-icon { 13 | background-position: 0 -20px; 14 | } 15 | } 16 | 17 | .append-parent-node { 18 | .km-btn-icon { 19 | background-position: 0 -40px; 20 | } 21 | } -------------------------------------------------------------------------------- /webui/less/topTab/idea/arrange.less: -------------------------------------------------------------------------------- 1 | .arrange-group { 2 | width: 80px; 3 | } 4 | 5 | .arrange-up { 6 | .km-btn-icon { 7 | background-position: 0 -280px; 8 | } 9 | } 10 | 11 | .arrange-down { 12 | .km-btn-icon { 13 | background-position: 0 -300px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /webui/less/topTab/idea/hyperlink.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | vertical-align: middle; 3 | margin: 5px; 4 | 5 | .hyperlink, .hyperlink-caption { 6 | width: 40px; 7 | margin: 0; 8 | padding: 0; 9 | border: none!important; 10 | border-radius: 0!important; 11 | 12 | &:hover { 13 | background-color: @tool-hover; 14 | } 15 | 16 | &:active { 17 | background-color: @tool-active; 18 | } 19 | 20 | &.active { 21 | box-shadow: none; 22 | background-color: @tool-hover; 23 | } 24 | } 25 | 26 | .hyperlink { 27 | height: 25px; 28 | background: url(images/icons.png) no-repeat center -100px; 29 | } 30 | 31 | .hyperlink-caption { 32 | height: 20px; 33 | 34 | .caption { 35 | font-size: 12px; 36 | } 37 | } 38 | } 39 | 40 | //override bootstrap 41 | .open > .dropdown-toggle.btn-default { 42 | background-color: @tool-hover; 43 | } 44 | -------------------------------------------------------------------------------- /webui/less/topTab/idea/image.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | 3 | .image-btn, .image-btn-caption { 4 | width: 40px; 5 | margin: 0; 6 | padding: 0; 7 | border: none!important; 8 | border-radius: 0!important; 9 | 10 | &:hover { 11 | background-color: @tool-hover; 12 | } 13 | 14 | &:active { 15 | background-color: @tool-active; 16 | } 17 | 18 | &.active { 19 | box-shadow: none; 20 | background-color: @tool-hover; 21 | } 22 | } 23 | 24 | .image-btn { 25 | height: 25px; 26 | background: url(images/icons.png) no-repeat center -125px; 27 | } 28 | 29 | .image-btn-caption { 30 | height: 20px; 31 | 32 | .caption { 33 | font-size: 12px; 34 | } 35 | } 36 | } 37 | 38 | .image-preview { 39 | display: block; 40 | max-width: 50%; 41 | } 42 | 43 | .modal-body { 44 | .tab-pane { 45 | font-size: inherit; 46 | padding-top: 15px; 47 | } 48 | } 49 | 50 | .search-result { 51 | margin-top: 15px; 52 | height: 370px; 53 | overflow: hidden; 54 | 55 | ul { 56 | margin: 0; 57 | padding: 0; 58 | list-style: none; 59 | clear: both; 60 | height: 100%; 61 | overflow-x: hidden; 62 | overflow-y: auto; 63 | 64 | li { 65 | list-style: none; 66 | float: left; 67 | display: block; 68 | width: 130px; 69 | height: 130px; 70 | line-height: 130px; 71 | margin: 6px; 72 | padding: 0; 73 | font-size: 12px; 74 | position: relative; 75 | vertical-align: top; 76 | text-align: center; 77 | overflow: hidden; 78 | cursor: pointer; 79 | border: 2px solid #fcfcfc; 80 | 81 | &.selected { 82 | border: 2px solid #fc8383; 83 | } 84 | 85 | 86 | img { 87 | max-width: 126px; 88 | max-height: 130px; 89 | vertical-align: middle; 90 | } 91 | 92 | span { 93 | display: block; 94 | position: absolute; 95 | bottom: 0; 96 | height: 20px; 97 | background: rgba(0, 0, 0, 0.5); 98 | left: 0; 99 | right: 0; 100 | color: white; 101 | line-height: 20px; 102 | overflow: hidden; 103 | text-overflow: ellipsis; 104 | word-break: break-all; 105 | white-space: nowrap; 106 | opacity: 0; 107 | -webkit-transform: translate(0, 20px); 108 | -ms-transform: translate(0, 20px); 109 | transform: translate(0, 20px); 110 | -webkit-transition: all .2s ease; 111 | transition: all .2s ease; 112 | } 113 | } 114 | 115 | li:hover span { 116 | opacity: 1; 117 | -webkit-transform: translate(0, 0); 118 | -ms-transform: translate(0, 0); 119 | transform: translate(0, 0); 120 | } 121 | } 122 | } 123 | 124 | // 覆盖 bootstrap 样式 125 | @media (min-width: 768px){ 126 | .form-inline .form-control { 127 | width: 422px; 128 | } 129 | } -------------------------------------------------------------------------------- /webui/less/topTab/idea/note.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | vertical-align: top; 3 | margin: 5px; 4 | 5 | &.note-btn-group { 6 | border-right: 1px dashed #eee; 7 | padding-right: 5px; 8 | } 9 | 10 | .note-btn, .note-btn-caption { 11 | width: 40px; 12 | margin: 0; 13 | padding: 0; 14 | border: none!important; 15 | border-radius: 0!important; 16 | 17 | &:hover { 18 | background-color: @tool-hover; 19 | } 20 | 21 | &:active { 22 | background-color: @tool-active; 23 | } 24 | 25 | &.active { 26 | box-shadow: none; 27 | background-color: @tool-hover; 28 | } 29 | } 30 | 31 | .note-btn { 32 | height: 25px; 33 | background: url(images/icons.png) no-repeat center -1150px; 34 | } 35 | 36 | .note-btn-caption { 37 | height: 20px; 38 | 39 | .caption { 40 | font-size: 12px; 41 | } 42 | } 43 | } 44 | 45 | //override bootstrap 46 | .open > .dropdown-toggle.btn-default { 47 | background-color: @tool-hover; 48 | } 49 | -------------------------------------------------------------------------------- /webui/less/topTab/idea/noteEditor.less: -------------------------------------------------------------------------------- 1 | .gfm-render { 2 | 3 | font-size: 12px; 4 | -webkit-user-select: text; 5 | color: #333; 6 | line-height: 1.8em; 7 | 8 | blockquote, ul, table, p, pre, hr { 9 | margin: 1em 0; 10 | cursor: text; 11 | &:first-child:last-child { 12 | margin: 0; 13 | } 14 | } 15 | 16 | img { 17 | max-width: 100%; 18 | } 19 | 20 | a { 21 | color: blue; 22 | &:hover { 23 | color: red; 24 | } 25 | } 26 | 27 | blockquote { 28 | display: block; 29 | border-left: 4px solid #E4AD91; 30 | color: darken(#E4AD91, 10%); 31 | padding-left: 10px; 32 | font-style: italic; 33 | margin-left: 2em; 34 | } 35 | 36 | ul, ol { 37 | padding-left: 3em; 38 | } 39 | 40 | table { 41 | width: 100%; 42 | border-collapse: collapse; 43 | th, td { 44 | border: 1px solid #666; 45 | padding: 2px 4px; 46 | } 47 | th { 48 | background: rgba(45, 141, 234, 0.2); 49 | } 50 | tr:nth-child(even) td { 51 | background: rgba(45, 141, 234, 0.03); 52 | } 53 | margin: 1em 0; 54 | } 55 | 56 | em { 57 | color: red; 58 | } 59 | 60 | del { 61 | color: #999; 62 | } 63 | 64 | pre { 65 | background: rgba(45, 141, 234, 0.1); 66 | padding: 5px; 67 | border-radius: 5px; 68 | word-break: break-all; 69 | word-wrap: break-word; 70 | } 71 | 72 | code { 73 | background: rgba(45, 141, 234, 0.1); 74 | /* display: inline-block; */ 75 | padding: 0 5px; 76 | border-radius: 3px; 77 | } 78 | 79 | pre code { 80 | background: none; 81 | } 82 | 83 | hr { 84 | border: none; 85 | border-top: 1px solid #CCC; 86 | } 87 | 88 | .highlight { 89 | background: yellow; 90 | color: red; 91 | } 92 | } 93 | 94 | .km-note { 95 | width: 300px; 96 | border-left: 1px solid #babfcd; 97 | padding: 5px 10px; 98 | background: white; 99 | position: absolute; 100 | top: 92px; 101 | right: 0; 102 | bottom: 0; 103 | left: auto; 104 | z-index: 3; 105 | 106 | &.panel { 107 | margin: 0; 108 | padding: 0; 109 | 110 | .panel-heading { 111 | 112 | h3 { 113 | display: inline-block; 114 | } 115 | 116 | .close-note-editor { 117 | width: 15px; 118 | height: 15px; 119 | display: inline-block; 120 | float: right; 121 | 122 | &:hover { 123 | cursor: pointer; 124 | } 125 | } 126 | } 127 | 128 | .panel-body { 129 | padding: 0; 130 | } 131 | } 132 | 133 | .CodeMirror { 134 | position: absolute; 135 | top: 41px; 136 | bottom: 0; 137 | height: auto; 138 | cursor: text; 139 | font-size: 14px; 140 | line-height: 1.3em; 141 | font-family: consolas; 142 | } 143 | } 144 | .km-note-tips { 145 | color: #ccc; 146 | padding: 3px 8px; 147 | } 148 | #previewer-content { 149 | position: absolute; 150 | background: #FFD; 151 | padding: 5px 15px; 152 | border-radius: 5px; 153 | max-width: 400px; 154 | max-height: 200px; 155 | overflow: auto; 156 | z-index: 10; 157 | box-shadow: 0 0 15px rgba(0, 0, 0, .5); 158 | word-break: break-all; 159 | .gfm-render; 160 | } 161 | #previewer-content.ng-hide { 162 | display: block!important; 163 | left: -99999px!important; 164 | top: -99999px!important; 165 | } 166 | .panel-body { 167 | padding: 10px; 168 | } -------------------------------------------------------------------------------- /webui/less/topTab/idea/operation.less: -------------------------------------------------------------------------------- 1 | .operation-group { 2 | width: 108px; 3 | } 4 | 5 | .edit-node { 6 | .km-btn-icon { 7 | background-position: 0 -60px; 8 | } 9 | } 10 | 11 | .remove-node { 12 | .km-btn-icon { 13 | background-position: 0 -80px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /webui/less/topTab/idea/priority.less: -------------------------------------------------------------------------------- 1 | .priority-sprite(@count) when (@count >= 0) { 2 | .priority-sprite(@count - 1); 3 | &.priority-@{count} { 4 | background-position: 0 (-20px * (@count - 1)); 5 | } 6 | } 7 | 8 | .tab-content .km-priority { 9 | vertical-align: middle; 10 | font-size: inherit; 11 | display: inline-block; 12 | width: 140px; 13 | margin: 5px; 14 | border-right: 1px dashed #eee; 15 | 16 | .km-priority-item { 17 | margin: 0 1px; 18 | padding: 1px; 19 | 20 | .km-priority-icon { 21 | .priority-sprite(9); 22 | background: url(images/iconpriority.png) repeat-y; 23 | background-color: transparent; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /webui/less/topTab/idea/progress.less: -------------------------------------------------------------------------------- 1 | .progress-sprite(@count) when (@count >= 0) { 2 | .progress-sprite(@count - 1); 3 | &.progress-@{count} { 4 | background-position: 0 (-20px * (@count - 1)); 5 | } 6 | } 7 | 8 | .tab-content .km-progress { 9 | vertical-align: middle; 10 | font-size: inherit; 11 | display: inline-block; 12 | width: 140px; 13 | margin: 5px; 14 | border-right: 1px dashed #eee; 15 | 16 | .km-progress-item { 17 | margin: 0 1px; 18 | padding: 1px; 19 | 20 | .km-progress-icon { 21 | .progress-sprite(9); 22 | background: url(images/iconprogress.png) repeat-y; 23 | background-color: transparent; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /webui/less/topTab/idea/resource.less: -------------------------------------------------------------------------------- 1 | .resource-editor { 2 | vertical-align: middle; 3 | display: inline-block; 4 | margin: 5px; 5 | 6 | .input-group, .km-resource { 7 | font-size: 12px; 8 | } 9 | 10 | .input-group { 11 | height: 20px; 12 | width: 168px; 13 | } 14 | 15 | .resource-dropdown { 16 | position: relative; 17 | width: 168px; 18 | border: 1px solid #ccc; 19 | margin-top: -1px; 20 | border-bottom-right-radius: 4px; 21 | border-bottom-left-radius: 4px; 22 | 23 | .km-resource { 24 | position: absolute; 25 | width: 154px; 26 | margin-bottom: 3px; 27 | padding: 0; 28 | list-style-type: none; 29 | overflow: scroll; 30 | max-height: 500px; 31 | 32 | &.open { 33 | z-index: 3; 34 | background-color: #fff; 35 | } 36 | 37 | li { 38 | display: inline-block; 39 | padding: 1px 2px; 40 | border-radius: 4px; 41 | margin: 2px 3px; 42 | 43 | &[disabled] { 44 | opacity: 0.5; 45 | } 46 | } 47 | } 48 | 49 | .resource-caret { 50 | display: block; 51 | float: right; 52 | vertical-align: middle; 53 | width: 12px; 54 | height: 24px; 55 | padding: 8px 1px; 56 | 57 | &:hover {background-color: @button-hover;} 58 | &:active {background-color: @button-active;} 59 | } 60 | } 61 | 62 | // 覆盖 bootstrap 63 | input.form-control, .btn { 64 | font-size: 12px; 65 | } 66 | 67 | input.form-control { 68 | padding: 2px 4px; 69 | height: 24px; 70 | border-bottom-left-radius: 0; 71 | } 72 | 73 | .input-group-btn { 74 | line-height: 24px; 75 | 76 | .btn { 77 | padding: 2px 4px; 78 | height: 24px; 79 | border-bottom-right-radius: 0; 80 | } 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /webui/less/topTab/idea/undoRedo.less: -------------------------------------------------------------------------------- 1 | .do-group { 2 | width: 38px; 3 | } 4 | 5 | .undo { 6 | .km-btn-icon { 7 | background-position: 0 -1240px; 8 | } 9 | } 10 | 11 | .redo { 12 | .km-btn-icon { 13 | background-position: 0 -1220px; 14 | } 15 | } -------------------------------------------------------------------------------- /webui/less/topTab/searchBox.less: -------------------------------------------------------------------------------- 1 | .search-box { 2 | float: right; 3 | background-color: #fff; 4 | border: 1px solid #dbdbdb; 5 | position: relative; 6 | top: 0; 7 | z-index: 3; 8 | width: 360px; 9 | height: 40px; 10 | padding: 3px 6px; 11 | opacity: 1; 12 | 13 | .search-input-wrap, .prev-and-next-btn { 14 | float: left; 15 | } 16 | 17 | .close-search { 18 | float: right; 19 | height: 16px; 20 | width: 16px; 21 | padding: 1px; 22 | border-radius: 100%; 23 | margin-top: 6px; 24 | margin-right: 10px; 25 | 26 | .glyphicon { 27 | top: -1px 28 | } 29 | 30 | &:hover { 31 | background-color: #efefef; 32 | } 33 | 34 | &:active { 35 | background-color: #999; 36 | } 37 | } 38 | 39 | .search-input-wrap { 40 | width: 240px; 41 | } 42 | 43 | .prev-and-next-btn { 44 | margin-left: 5px; 45 | 46 | .btn:focus { 47 | outline: none; 48 | } 49 | } 50 | 51 | .search-input { 52 | //border-right: none; 53 | } 54 | 55 | .search-addon { 56 | background-color: #fff; 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /webui/less/topTab/topTab.less: -------------------------------------------------------------------------------- 1 | .top-tab { 2 | .nav-tabs { 3 | background-color: #e1e1e1; 4 | border: 0; 5 | height: 32px; 6 | 7 | li { 8 | margin: 0; 9 | 10 | a { 11 | margin: 0; 12 | border: 0; 13 | padding: 6px 15px; 14 | border-radius: 0; 15 | vertical-align: middle; 16 | 17 | &:hover, 18 | &:focus { 19 | background: inherit; 20 | border: 0; 21 | } 22 | } 23 | 24 | &.active a { 25 | border: 0; 26 | background-color: #fff; 27 | 28 | &:hover, 29 | &:focus { 30 | border: 0; 31 | } 32 | } 33 | } 34 | } 35 | 36 | .tab-content { 37 | height: 60px; 38 | background-color: #fff; 39 | border-bottom: 1px solid #dbdbdb; 40 | } 41 | 42 | .tab-pane { 43 | font-size: 0; 44 | } 45 | } 46 | 47 | .km-btn-group { 48 | display: inline-block; 49 | // margin: 5px 0; 50 | padding: 0 5px; 51 | vertical-align: middle; 52 | border-right: 1px dashed #eee; 53 | } 54 | 55 | .km-btn-item { 56 | display: inline-block; 57 | margin: 0 3px; 58 | font-size: 0; 59 | cursor: default; 60 | 61 | &[disabled] { 62 | opacity: 0.5; 63 | 64 | &:hover, 65 | &:active { 66 | background-color: #fff; 67 | } 68 | } 69 | 70 | .km-btn-icon { 71 | display: inline-block; 72 | background: url(images/icons.png) no-repeat; 73 | background-position: 0 20px; 74 | width: 20px; 75 | height: 20px; 76 | padding: 2px; 77 | margin: 1px; 78 | vertical-align: middle; 79 | } 80 | 81 | .km-btn-caption { 82 | display: inline-block; 83 | font-size: 12px; 84 | vertical-align: middle; 85 | } 86 | 87 | &:hover { 88 | background-color: @button-hover; 89 | } 90 | &:active { 91 | background-color: @button-active; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /webui/less/topTab/view/expand.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | vertical-align: middle; 3 | margin: 5px; 4 | width: 90px; 5 | .expand, 6 | .expand-caption { 7 | width: 40px; 8 | margin: 0; 9 | padding: 0; 10 | border: none !important; 11 | border-radius: 0 !important; 12 | 13 | &:hover { 14 | background-color: @tool-hover; 15 | } 16 | 17 | &:active { 18 | background-color: @tool-active; 19 | } 20 | 21 | &.active { 22 | box-shadow: none; 23 | background-color: @tool-hover; 24 | } 25 | } 26 | 27 | .expand { 28 | height: 25px; 29 | background: url(images/icons.png) no-repeat 0 -995px; 30 | background-position-x: 50%; 31 | } 32 | 33 | .expand-caption { 34 | height: 20px; 35 | 36 | .caption { 37 | font-size: 12px; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /webui/less/topTab/view/search.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | vertical-align: middle; 3 | margin: 5px; 4 | 5 | .search, .search-caption { 6 | width: 40px; 7 | margin: 0; 8 | padding: 0; 9 | border: none!important; 10 | border-radius: 0!important; 11 | 12 | &:hover { 13 | background-color: @tool-hover; 14 | } 15 | 16 | &:active { 17 | background-color: @tool-active; 18 | } 19 | 20 | &.active { 21 | box-shadow: none; 22 | background-color: @tool-hover; 23 | } 24 | } 25 | 26 | .search { 27 | height: 25px; 28 | background: url(images/icons.png) no-repeat 0 -345px; 29 | background-position-x: 50%; 30 | } 31 | 32 | .search-caption { 33 | height: 20px; 34 | 35 | .caption { 36 | font-size: 12px; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /webui/less/topTab/view/select.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | vertical-align: middle; 3 | margin: 5px; 4 | width: 65px; 5 | .select, 6 | .select-caption { 7 | width: 40px; 8 | margin: 0; 9 | padding: 0; 10 | border: none !important; 11 | border-radius: 0 !important; 12 | 13 | &:hover { 14 | background-color: @tool-hover; 15 | } 16 | 17 | &:active { 18 | background-color: @tool-active; 19 | } 20 | 21 | &.active { 22 | box-shadow: none; 23 | background-color: @tool-hover; 24 | } 25 | } 26 | 27 | .select { 28 | height: 25px; 29 | background: url(images/icons.png) no-repeat 7px -1175px; 30 | } 31 | 32 | .select-caption { 33 | height: 20px; 34 | 35 | .caption { 36 | font-size: 12px; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /webui/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * initial kityminder-editor 3 | */ 4 | angular 5 | .module('kityminderDemo', ['kityminderEditor']) 6 | .config(function(configProvider) { 7 | configProvider.set('imageUpload', '../server/imageUpload.php'); 8 | }) 9 | .controller('MainController', function($scope) { 10 | $scope.initEditor = function(editor, minder) { 11 | window.editor = editor; 12 | window.minder = minder; 13 | 14 | /** 15 | * receive message event from extension 16 | */ 17 | window.addEventListener('message', function(event) { 18 | window.message = event.data; 19 | const { command } = window.message; 20 | 21 | switch (command) { 22 | case 'import': 23 | try { 24 | const importData = JSON.parse(window.message.importData); 25 | window.minder.importJson(importData); 26 | } catch (ex) { 27 | console.error(ex); 28 | } 29 | break; 30 | } 31 | }); 32 | 33 | window.addEventListener('keydown', e => { 34 | const keyCode = e.keyCode || e.which || e.charCode; 35 | const ctrlKey = e.ctrlKey || e.metaKey; 36 | if (ctrlKey && keyCode === 83) { 37 | const btnSave = document.querySelector('.km-export-save'); 38 | btnSave.click(); 39 | } 40 | }); 41 | 42 | window.vscode.postMessage({ 43 | command: 'loaded', 44 | }); 45 | }; 46 | }); 47 | -------------------------------------------------------------------------------- /webui/mindmap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mindmap 6 | 7 | 11 | 15 | 19 | 23 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /webui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kityminder-editor", 3 | "version": "1.0.67", 4 | "description": "A powerful mind map editor", 5 | "main": "kityminder.editor.js", 6 | "scripts": { 7 | "init": "npm i -g wr && npm install -g less && npm install -g bower && bower install && npm install", 8 | "build": "grunt build", 9 | "dev": "grunt dev", 10 | "watch": "wr --exec \"lessc --source-map less/editor.less dist/kityminder.editor.css && grunt build\" less ui", 11 | "postinstall": "bower install", 12 | "build:rollup": "rollup -w --config ./rollup.config.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/fex-team/kityminder-editor" 17 | }, 18 | "keywords": [ 19 | "kityminder", 20 | "editor", 21 | "html5", 22 | "js", 23 | "mindmap" 24 | ], 25 | "author": "fex ", 26 | "license": "GPL-2.0", 27 | "bugs": { 28 | "url": "https://github.com/fex-team/kityminder-editor/issues" 29 | }, 30 | "homepage": "https://github.com/fex-team/kityminder-editor", 31 | "devDependencies": { 32 | "@babel/preset-env": "^7.4.3", 33 | "cz-conventional-changelog": "^1.1.5", 34 | "grunt": "~0.4.1", 35 | "grunt-angular-templates": "~0.5.0", 36 | "grunt-browser-sync": "^2.2.0", 37 | "grunt-contrib-clean": "^0.5.0", 38 | "grunt-contrib-concat": "~0.5.0", 39 | "grunt-contrib-copy": "^0.5.0", 40 | "grunt-contrib-cssmin": "^0.12.0", 41 | "grunt-contrib-less": "^1.0.0", 42 | "grunt-contrib-uglify": "^3.3.0", 43 | "grunt-contrib-watch": "^1.0.0", 44 | "grunt-module-dependence": "~0.2.0", 45 | "grunt-ng-annotate": "^0.9.2", 46 | "grunt-replace": "~0.8.0", 47 | "grunt-wiredep": "^2.0.0", 48 | "jshint-stylish": "^1.0.0", 49 | "load-grunt-tasks": "^3.1.0", 50 | "rollup-plugin-babel": "^4.3.2", 51 | "uglify-js": "^2.8.29" 52 | }, 53 | "dependencies": { 54 | "@babel/core": "^7.4.3", 55 | "kityminder-core": "^1.4.50", 56 | "rollup-plugin-uglify": "^6.0.2" 57 | }, 58 | "config": { 59 | "commitizen": { 60 | "path": "./node_modules/cz-conventional-changelog" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /webui/rollup.config.js: -------------------------------------------------------------------------------- 1 | import { uglify } from 'rollup-plugin-uglify'; 2 | import babel from 'rollup-plugin-babel'; 3 | 4 | module.exports = { 5 | input: './main.js', 6 | output: { 7 | file: './dist/main.min.js', 8 | format: 'esm', 9 | strict: false, 10 | }, 11 | plugins: [ 12 | uglify({ 13 | mangle: false, 14 | }), 15 | babel(), 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /webui/server/imageUpload.php: -------------------------------------------------------------------------------- 1 | , msg: <错误信息>, data: {url: <返回的 URL>}}; 7 | * 2. 由于要兼容两种情况的上传:通过对话框选择本地文件上传和直接 Ctrl + V(多见于截图后),因此本文件分别进行了判断 8 | * 9 | * 10 | * 注意: 11 | * 1. 本文件的路径可以进行配置,详见 README.md 中「初始化配置」部分。 12 | * 2. 由于使用场景不同,请根据实际场景编写上传文件的处理。 13 | * 3. 本文件并没有做任何的安全方面的防护,请勿用于生产环境。 14 | * 15 | * @author: zhangbobell 16 | * 17 | * @date: 2016.07.06 18 | * 19 | */ 20 | 21 | // 返回给前端的地址是绝对地址,这里是前缀 22 | $HTTP_PREFIX = 'http://localhost/kityminder-editor/'; 23 | 24 | 25 | $errno = 0; 26 | $msg = 'ok'; 27 | $url = ''; 28 | 29 | 30 | if ((($_FILES["upload_file"]["type"] == "image/gif") 31 | || ($_FILES["upload_file"]["type"] == "image/jpeg") 32 | || ($_FILES["upload_file"]["type"] == "image/jpg") 33 | || ($_FILES["upload_file"]["type"] == "image/png")) 34 | && ($_FILES["upload_file"]["size"] < 1 * 1000 * 1000)) { 35 | 36 | if ($_FILES["upload_file"]["error"] > 0) { 37 | $errno = 414; 38 | $msg = $_FILES["upload_file"]["error"]; 39 | } else { 40 | 41 | // 分为两种情况 `Ctrl + V` 和普通上传 42 | if ($_FILES["upload_file"]["name"] === 'blob') { 43 | $ext_name = 'png'; 44 | } else { 45 | $ext_name = array_pop(explode('.', $_FILES["upload_file"]["name"])); 46 | } 47 | 48 | $sha1_name = sha1_file($_FILES["upload_file"]["tmp_name"]) . '.' . $ext_name; 49 | 50 | move_uploaded_file($_FILES["upload_file"]["tmp_name"], "upload/" . $sha1_name); 51 | $url = $HTTP_PREFIX . "server/upload/" . $sha1_name; 52 | } 53 | } else { 54 | $errno = 416; 55 | $msg = 'File is invalid'; 56 | } 57 | 58 | 59 | $result = array( 60 | 'errno' => $errno, 61 | 'msg' => $msg, 62 | 'data' => array( 63 | 'url' => $url 64 | ) 65 | ); 66 | 67 | echo json_encode($result); -------------------------------------------------------------------------------- /webui/src/editor.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | /** 4 | * 运行时 5 | */ 6 | var runtimes = []; 7 | 8 | function assemble(runtime) { 9 | runtimes.push(runtime); 10 | } 11 | 12 | function KMEditor(selector) { 13 | this.selector = selector; 14 | for (var i = 0; i < runtimes.length; i++) { 15 | if (typeof runtimes[i] == 'function') { 16 | runtimes[i].call(this, this); 17 | } 18 | } 19 | } 20 | 21 | KMEditor.assemble = assemble; 22 | 23 | assemble(require('./runtime/container')); 24 | assemble(require('./runtime/fsm')); 25 | assemble(require('./runtime/minder')); 26 | assemble(require('./runtime/receiver')); 27 | assemble(require('./runtime/hotbox')); 28 | assemble(require('./runtime/input')); 29 | assemble(require('./runtime/clipboard-mimetype')); 30 | assemble(require('./runtime/clipboard')); 31 | assemble(require('./runtime/drag')); 32 | assemble(require('./runtime/node')); 33 | assemble(require('./runtime/history')); 34 | assemble(require('./runtime/jumping')); 35 | assemble(require('./runtime/priority')); 36 | assemble(require('./runtime/progress')); 37 | 38 | 39 | return module.exports = KMEditor; 40 | }); -------------------------------------------------------------------------------- /webui/src/expose-editor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 打包暴露 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define('expose-editor', function(require, exports, module) { 10 | return module.exports = kityminder.Editor = require('./editor'); 11 | }); -------------------------------------------------------------------------------- /webui/src/hotbox.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | return module.exports = window.HotBox; 3 | }); -------------------------------------------------------------------------------- /webui/src/lang.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | }); -------------------------------------------------------------------------------- /webui/src/minder.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | return module.exports = window.kityminder.Minder; 3 | }); -------------------------------------------------------------------------------- /webui/src/runtime/clipboard-mimetype.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Desc: 新增一个用于处理系统ctrl+c ctrl+v等方式导入导出节点的MIMETYPE处理,如系统不支持clipboardEvent或者是FF则不初始化改class 3 | * @Editor: Naixor 4 | * @Date: 2015.9.21 5 | */ 6 | define(function(require, exports, module) { 7 | function MimeType() { 8 | /** 9 | * 私有变量 10 | */ 11 | var SPLITOR = '\uFEFF'; 12 | var MIMETYPE = { 13 | 'application/km': '\uFFFF' 14 | }; 15 | var SIGN = { 16 | '\uFEFF': 'SPLITOR', 17 | '\uFFFF': 'application/km' 18 | }; 19 | 20 | /** 21 | * 用于将一段纯文本封装成符合其数据格式的文本 22 | * @method process private 23 | * @param {MIMETYPE} mimetype 数据格式 24 | * @param {String} text 原始文本 25 | * @return {String} 符合该数据格式下的文本 26 | * @example 27 | * var str = "123"; 28 | * str = process('application/km', str); // 返回的内容再经过MimeType判断会读取出其数据格式为application/km 29 | * process('text/plain', str); // 若接受到一个非纯文本信息,则会将其转换为新的数据格式 30 | */ 31 | function process(mimetype, text) { 32 | if (!this.isPureText(text)) { 33 | var _mimetype = this.whichMimeType(text); 34 | if (!_mimetype) { 35 | throw new Error('unknow mimetype!'); 36 | }; 37 | text = this.getPureText(text); 38 | }; 39 | if (mimetype === false) { 40 | return text; 41 | }; 42 | return mimetype + SPLITOR + text; 43 | } 44 | 45 | /** 46 | * 注册数据类型的标识 47 | * @method registMimeTypeProtocol public 48 | * @param {String} type 数据类型 49 | * @param {String} sign 标识 50 | */ 51 | this.registMimeTypeProtocol = function(type, sign) { 52 | if (sign && SIGN[sign]) { 53 | throw new Error('sing has registed!'); 54 | } 55 | if (type && !!MIMETYPE[type]) { 56 | throw new Error('mimetype has registed!'); 57 | }; 58 | SIGN[sign] = type; 59 | MIMETYPE[type] = sign; 60 | } 61 | 62 | /** 63 | * 获取已注册数据类型的协议 64 | * @method getMimeTypeProtocol public 65 | * @param {String} type 数据类型 66 | * @param {String} text|undefiend 文本内容或不传入 67 | * @return {String|Function} 68 | * @example 69 | * text若不传入则直接返回对应数据格式的处理(process)方法 70 | * 若传入文本则直接调用对应的process方法进行处理,此时返回处理后的内容 71 | * var m = new MimeType(); 72 | * var kmprocess = m.getMimeTypeProtocol('application/km'); 73 | * kmprocess("123") === m.getMimeTypeProtocol('application/km', "123"); 74 | * 75 | */ 76 | this.getMimeTypeProtocol = function(type, text) { 77 | var mimetype = MIMETYPE[type] || false; 78 | 79 | if (text === undefined) { 80 | return process.bind(this, mimetype); 81 | }; 82 | 83 | return process(mimetype, text); 84 | } 85 | 86 | this.getSpitor = function() { 87 | return SPLITOR; 88 | } 89 | 90 | this.getMimeType = function(sign) { 91 | if (sign !== undefined) { 92 | return SIGN[sign] || null; 93 | }; 94 | return MIMETYPE; 95 | } 96 | } 97 | 98 | MimeType.prototype.isPureText = function(text) { 99 | return !(~text.indexOf(this.getSpitor())); 100 | } 101 | 102 | MimeType.prototype.getPureText = function(text) { 103 | if (this.isPureText(text)) { 104 | return text; 105 | }; 106 | return text.split(this.getSpitor())[1]; 107 | } 108 | 109 | MimeType.prototype.whichMimeType = function(text) { 110 | if (this.isPureText(text)) { 111 | return null; 112 | }; 113 | return this.getMimeType(text.split(this.getSpitor())[0]); 114 | } 115 | 116 | function MimeTypeRuntime() { 117 | if (this.minder.supportClipboardEvent && !kity.Browser.gecko) { 118 | this.MimeType = new MimeType(); 119 | }; 120 | } 121 | 122 | return module.exports = MimeTypeRuntime; 123 | }); -------------------------------------------------------------------------------- /webui/src/runtime/container.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 初始化编辑器的容器 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function(require, exports, module) { 10 | 11 | /** 12 | * 最先执行的 Runtime,初始化编辑器容器 13 | */ 14 | function ContainerRuntime() { 15 | var container; 16 | 17 | if (typeof(this.selector) == 'string') { 18 | container = document.querySelector(this.selector); 19 | } else { 20 | container = this.selector; 21 | } 22 | 23 | if (!container) throw new Error('Invalid selector: ' + this.selector); 24 | 25 | // 这个类名用于给编辑器添加样式 26 | container.classList.add('km-editor'); 27 | 28 | // 暴露容器给其他运行时使用 29 | this.container = container; 30 | } 31 | 32 | return module.exports = ContainerRuntime; 33 | }); -------------------------------------------------------------------------------- /webui/src/runtime/drag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 用于拖拽节点时屏蔽键盘事件 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function(require, exports, module) { 10 | 11 | var Hotbox = require('../hotbox'); 12 | var Debug = require('../tool/debug'); 13 | var debug = new Debug('drag'); 14 | 15 | function DragRuntime() { 16 | var fsm = this.fsm; 17 | var minder = this.minder; 18 | var hotbox = this.hotbox; 19 | var receiver = this.receiver; 20 | var receiverElement = receiver.element; 21 | 22 | // setup everything to go 23 | setupFsm(); 24 | 25 | // listen the fsm changes, make action. 26 | function setupFsm() { 27 | 28 | // when jumped to drag mode, enter 29 | fsm.when('* -> drag', function() { 30 | // now is drag mode 31 | }); 32 | 33 | fsm.when('drag -> *', function(exit, enter, reason) { 34 | if (reason == 'drag-finish') { 35 | // now exit drag mode 36 | } 37 | }); 38 | } 39 | 40 | var downX, downY; 41 | var MOUSE_HAS_DOWN = 0; 42 | var MOUSE_HAS_UP = 1; 43 | var BOUND_CHECK = 20; 44 | var flag = MOUSE_HAS_UP; 45 | var maxX, maxY, osx, osy, containerY; 46 | var freeHorizen = false, freeVirtical = false; 47 | var frame; 48 | 49 | function move(direction, speed) { 50 | if (!direction) { 51 | freeHorizen = freeVirtical = false; 52 | frame && kity.releaseFrame(frame); 53 | frame = null; 54 | return; 55 | } 56 | if (!frame) { 57 | frame = kity.requestFrame((function (direction, speed, minder) { 58 | return function (frame) { 59 | switch (direction) { 60 | case 'left': 61 | minder._viewDragger.move({x: -speed, y: 0}, 0); 62 | break; 63 | case 'top': 64 | minder._viewDragger.move({x: 0, y: -speed}, 0); 65 | break; 66 | case 'right': 67 | minder._viewDragger.move({x: speed, y: 0}, 0); 68 | break; 69 | case 'bottom': 70 | minder._viewDragger.move({x: 0, y: speed}, 0); 71 | break; 72 | default: 73 | return; 74 | } 75 | frame.next(); 76 | }; 77 | })(direction, speed, minder)); 78 | } 79 | } 80 | 81 | minder.on('mousedown', function(e) { 82 | flag = MOUSE_HAS_DOWN; 83 | var rect = minder.getPaper().container.getBoundingClientRect(); 84 | downX = e.originEvent.clientX; 85 | downY = e.originEvent.clientY; 86 | containerY = rect.top; 87 | maxX = rect.width; 88 | maxY = rect.height; 89 | }); 90 | 91 | minder.on('mousemove', function(e) { 92 | if (fsm.state() === 'drag' && flag == MOUSE_HAS_DOWN && minder.getSelectedNode() 93 | && (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK 94 | || Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK)) { 95 | osx = e.originEvent.clientX; 96 | osy = e.originEvent.clientY - containerY; 97 | 98 | if (osx < BOUND_CHECK) { 99 | move('right', BOUND_CHECK - osx); 100 | } else if (osx > maxX - BOUND_CHECK) { 101 | move('left', BOUND_CHECK + osx - maxX); 102 | } else { 103 | freeHorizen = true; 104 | } 105 | if (osy < BOUND_CHECK) { 106 | move('bottom', osy); 107 | } else if (osy > maxY - BOUND_CHECK) { 108 | move('top', BOUND_CHECK + osy - maxY); 109 | } else { 110 | freeVirtical = true; 111 | } 112 | if (freeHorizen && freeVirtical) { 113 | move(false); 114 | } 115 | } 116 | if (fsm.state() !== 'drag' 117 | && flag === MOUSE_HAS_DOWN 118 | && minder.getSelectedNode() 119 | && (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK 120 | || Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK)) { 121 | 122 | if (fsm.state() === 'hotbox') { 123 | hotbox.active(Hotbox.STATE_IDLE); 124 | } 125 | 126 | return fsm.jump('drag', 'user-drag'); 127 | } 128 | }); 129 | 130 | window.addEventListener('mouseup', function () { 131 | flag = MOUSE_HAS_UP; 132 | if (fsm.state() === 'drag') { 133 | move(false); 134 | return fsm.jump('normal', 'drag-finish'); 135 | } 136 | }, false); 137 | } 138 | 139 | return module.exports = DragRuntime; 140 | }); 141 | -------------------------------------------------------------------------------- /webui/src/runtime/fsm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 编辑器状态机 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function(require, exports, module) { 10 | 11 | var Debug = require('../tool/debug'); 12 | var debug = new Debug('fsm'); 13 | 14 | function handlerConditionMatch(condition, when, exit, enter) { 15 | if (condition.when != when) return false; 16 | if (condition.enter != '*' && condition.enter != enter) return false; 17 | if (condition.exit != '*' && condition.exit != exit) return; 18 | return true; 19 | } 20 | 21 | function FSM(defaultState) { 22 | var currentState = defaultState; 23 | var BEFORE_ARROW = ' - '; 24 | var AFTER_ARROW = ' -> '; 25 | var handlers = []; 26 | 27 | /** 28 | * 状态跳转 29 | * 30 | * 会通知所有的状态跳转监视器 31 | * 32 | * @param {string} newState 新状态名称 33 | * @param {any} reason 跳转的原因,可以作为参数传递给跳转监视器 34 | */ 35 | this.jump = function(newState, reason) { 36 | if (!reason) throw new Error('Please tell fsm the reason to jump'); 37 | 38 | var oldState = currentState; 39 | var notify = [oldState, newState].concat([].slice.call(arguments, 1)); 40 | var i, handler; 41 | 42 | // 跳转前 43 | for (i = 0; i < handlers.length; i++) { 44 | handler = handlers[i]; 45 | if (handlerConditionMatch(handler.condition, 'before', oldState, newState)) { 46 | if (handler.apply(null, notify)) return; 47 | } 48 | } 49 | 50 | currentState = newState; 51 | debug.log('[{0}] {1} -> {2}', reason, oldState, newState); 52 | 53 | // 跳转后 54 | for (i = 0; i < handlers.length; i++) { 55 | handler = handlers[i]; 56 | if (handlerConditionMatch(handler.condition, 'after', oldState, newState)) { 57 | handler.apply(null, notify); 58 | } 59 | } 60 | return currentState; 61 | }; 62 | 63 | /** 64 | * 返回当前状态 65 | * @return {string} 66 | */ 67 | this.state = function() { 68 | return currentState; 69 | }; 70 | 71 | /** 72 | * 添加状态跳转监视器 73 | * 74 | * @param {string} condition 75 | * 监视的时机 76 | * "* => *" (默认) 77 | * 78 | * @param {Function} handler 79 | * 监视函数,当状态跳转的时候,会接收三个参数 80 | * * from - 跳转前的状态 81 | * * to - 跳转后的状态 82 | * * reason - 跳转的原因 83 | */ 84 | this.when = function(condition, handler) { 85 | if (arguments.length == 1) { 86 | handler = condition; 87 | condition = '* -> *'; 88 | } 89 | 90 | var when, resolved, exit, enter; 91 | 92 | resolved = condition.split(BEFORE_ARROW); 93 | if (resolved.length == 2) { 94 | when = 'before'; 95 | } else { 96 | resolved = condition.split(AFTER_ARROW); 97 | if (resolved.length == 2) { 98 | when = 'after'; 99 | } 100 | } 101 | if (!when) throw new Error('Illegal fsm condition: ' + condition); 102 | 103 | exit = resolved[0]; 104 | enter = resolved[1]; 105 | 106 | handler.condition = { 107 | when: when, 108 | exit: exit, 109 | enter: enter 110 | }; 111 | 112 | handlers.push(handler); 113 | }; 114 | } 115 | 116 | function FSMRumtime() { 117 | this.fsm = new FSM('normal'); 118 | } 119 | 120 | return module.exports = FSMRumtime; 121 | }); -------------------------------------------------------------------------------- /webui/src/runtime/history.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 历史管理 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | 10 | 11 | define(function(require, exports, module) { 12 | var jsonDiff = require('../tool/jsondiff'); 13 | 14 | function HistoryRuntime() { 15 | var minder = this.minder; 16 | var hotbox = this.hotbox; 17 | 18 | var MAX_HISTORY = 100; 19 | 20 | var lastSnap; 21 | var patchLock; 22 | var undoDiffs; 23 | var redoDiffs; 24 | 25 | function reset() { 26 | undoDiffs = []; 27 | redoDiffs = []; 28 | lastSnap = minder.exportJson(); 29 | } 30 | 31 | function makeUndoDiff() { 32 | var headSnap = minder.exportJson(); 33 | var diff = jsonDiff(headSnap, lastSnap); 34 | if (diff.length) { 35 | undoDiffs.push(diff); 36 | while (undoDiffs.length > MAX_HISTORY) { 37 | undoDiffs.shift(); 38 | } 39 | lastSnap = headSnap; 40 | return true; 41 | } 42 | } 43 | 44 | function makeRedoDiff() { 45 | var revertSnap = minder.exportJson(); 46 | redoDiffs.push(jsonDiff(revertSnap, lastSnap)); 47 | lastSnap = revertSnap; 48 | } 49 | 50 | function undo() { 51 | patchLock = true; 52 | var undoDiff = undoDiffs.pop(); 53 | if (undoDiff) { 54 | minder.applyPatches(undoDiff); 55 | makeRedoDiff(); 56 | } 57 | patchLock = false; 58 | } 59 | 60 | function redo() { 61 | patchLock = true; 62 | var redoDiff = redoDiffs.pop(); 63 | if (redoDiff) { 64 | minder.applyPatches(redoDiff); 65 | makeUndoDiff(); 66 | } 67 | patchLock = false; 68 | } 69 | 70 | function changed() { 71 | if (patchLock) return; 72 | if (makeUndoDiff()) redoDiffs = []; 73 | } 74 | 75 | function hasUndo() { 76 | return !!undoDiffs.length; 77 | } 78 | 79 | function hasRedo() { 80 | return !!redoDiffs.length; 81 | } 82 | 83 | function updateSelection(e) { 84 | if (!patchLock) return; 85 | var patch = e.patch; 86 | switch (patch.express) { 87 | case 'node.add': 88 | minder.select(patch.node.getChild(patch.index), true); 89 | break; 90 | case 'node.remove': 91 | case 'data.replace': 92 | case 'data.remove': 93 | case 'data.add': 94 | minder.select(patch.node, true); 95 | break; 96 | } 97 | } 98 | 99 | this.history = { 100 | reset: reset, 101 | undo: undo, 102 | redo: redo, 103 | hasUndo: hasUndo, 104 | hasRedo: hasRedo 105 | }; 106 | reset(); 107 | minder.on('contentchange', changed); 108 | minder.on('import', reset); 109 | minder.on('patch', updateSelection); 110 | 111 | var main = hotbox.state('main'); 112 | main.button({ 113 | position: 'top', 114 | label: '撤销', 115 | key: 'Ctrl + Z', 116 | enable: hasUndo, 117 | action: undo, 118 | next: 'idle' 119 | }); 120 | main.button({ 121 | position: 'top', 122 | label: '重做', 123 | key: 'Ctrl + Y', 124 | enable: hasRedo, 125 | action: redo, 126 | next: 'idle' 127 | }); 128 | } 129 | 130 | window.diff = jsonDiff; 131 | 132 | return module.exports = HistoryRuntime; 133 | }); -------------------------------------------------------------------------------- /webui/src/runtime/hotbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 热盒 Runtime 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function(require, exports, module) { 10 | var Hotbox = require('../hotbox'); 11 | 12 | function HotboxRuntime() { 13 | var fsm = this.fsm; 14 | var minder = this.minder; 15 | var receiver = this.receiver; 16 | var container = this.container; 17 | 18 | var hotbox = new Hotbox(container); 19 | 20 | hotbox.setParentFSM(fsm); 21 | 22 | fsm.when('normal -> hotbox', function(exit, enter, reason) { 23 | var node = minder.getSelectedNode(); 24 | var position; 25 | if (node) { 26 | var box = node.getRenderBox(); 27 | position = { 28 | x: box.cx, 29 | y: box.cy 30 | }; 31 | } 32 | hotbox.active('main', position); 33 | }); 34 | 35 | fsm.when('normal -> normal', function(exit, enter, reason, e) { 36 | if (reason == 'shortcut-handle') { 37 | var handleResult = hotbox.dispatch(e); 38 | if (handleResult) { 39 | e.preventDefault(); 40 | } else { 41 | minder.dispatchKeyEvent(e); 42 | } 43 | } 44 | }); 45 | 46 | fsm.when('modal -> normal', function(exit, enter, reason, e) { 47 | if (reason == 'import-text-finish') { 48 | receiver.element.focus(); 49 | } 50 | }); 51 | 52 | this.hotbox = hotbox; 53 | } 54 | 55 | return module.exports = HotboxRuntime; 56 | }); -------------------------------------------------------------------------------- /webui/src/runtime/minder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 脑图示例运行时 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function(require, exports, module) { 10 | var Minder = require('../minder'); 11 | 12 | function MinderRuntime() { 13 | // 不使用 kityminder 的按键处理,由 ReceiverRuntime 统一处理 14 | var minder = new Minder({ 15 | enableKeyReceiver: false, 16 | enableAnimation: true, 17 | }); 18 | 19 | // 渲染,初始化 20 | minder.renderTo(this.selector); 21 | minder.setTheme(null); 22 | minder.select(minder.getRoot(), true); 23 | minder.execCommand('text', 'MainTopic'); 24 | 25 | // 导出给其它 Runtime 使用 26 | this.minder = minder; 27 | } 28 | 29 | return (module.exports = MinderRuntime); 30 | }); 31 | -------------------------------------------------------------------------------- /webui/src/runtime/node.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | function NodeRuntime() { 3 | var runtime = this; 4 | var minder = this.minder; 5 | var hotbox = this.hotbox; 6 | var fsm = this.fsm; 7 | 8 | var main = hotbox.state('main'); 9 | 10 | var buttons = [ 11 | '前移:Alt+Up:ArrangeUp', 12 | '下级:Tab|Insert:AppendChildNode', 13 | '同级:Enter:AppendSiblingNode', 14 | '后移:Alt+Down:ArrangeDown', 15 | '删除:Delete|Backspace:RemoveNode', 16 | '上级:Shift+Tab|Shift+Insert:AppendParentNode', 17 | //'全选:Ctrl+A:SelectAll' 18 | ]; 19 | 20 | var AppendLock = 0; 21 | 22 | buttons.forEach(function(button) { 23 | var parts = button.split(':'); 24 | var label = parts.shift(); 25 | var key = parts.shift(); 26 | var command = parts.shift(); 27 | main.button({ 28 | position: 'ring', 29 | label: label, 30 | key: key, 31 | action: function() { 32 | if (command.indexOf('Append') === 0) { 33 | AppendLock++; 34 | minder.execCommand(command, 'topic'); 35 | 36 | // provide in input runtime 37 | function afterAppend() { 38 | if (!--AppendLock) { 39 | runtime.editText(); 40 | } 41 | minder.off('layoutallfinish', afterAppend); 42 | } 43 | minder.on('layoutallfinish', afterAppend); 44 | } else { 45 | minder.execCommand(command); 46 | fsm.jump('normal', 'command-executed'); 47 | } 48 | }, 49 | enable: function() { 50 | return minder.queryCommandState(command) != -1; 51 | }, 52 | }); 53 | }); 54 | 55 | main.button({ 56 | position: 'bottom', 57 | label: '导入节点', 58 | key: 'Alt + V', 59 | enable: function() { 60 | var selectedNodes = minder.getSelectedNodes(); 61 | return selectedNodes.length == 1; 62 | }, 63 | action: importNodeData, 64 | next: 'idle', 65 | }); 66 | 67 | main.button({ 68 | position: 'bottom', 69 | label: '导出节点', 70 | key: 'Alt + C', 71 | enable: function() { 72 | var selectedNodes = minder.getSelectedNodes(); 73 | return selectedNodes.length == 1; 74 | }, 75 | action: exportNodeData, 76 | next: 'idle', 77 | }); 78 | 79 | function importNodeData() { 80 | minder.fire('importNodeData'); 81 | } 82 | 83 | function exportNodeData() { 84 | minder.fire('exportNodeData'); 85 | } 86 | 87 | //main.button({ 88 | // position: 'ring', 89 | // key: '/', 90 | // action: function(){ 91 | // if (!minder.queryCommandState('expand')) { 92 | // minder.execCommand('expand'); 93 | // } else if (!minder.queryCommandState('collapse')) { 94 | // minder.execCommand('collapse'); 95 | // } 96 | // }, 97 | // enable: function() { 98 | // return minder.queryCommandState('expand') != -1 || minder.queryCommandState('collapse') != -1; 99 | // }, 100 | // beforeShow: function() { 101 | // if (!minder.queryCommandState('expand')) { 102 | // this.$button.children[0].innerHTML = '展开'; 103 | // } else { 104 | // this.$button.children[0].innerHTML = '收起'; 105 | // } 106 | // } 107 | //}) 108 | } 109 | 110 | return (module.exports = NodeRuntime); 111 | }); 112 | -------------------------------------------------------------------------------- /webui/src/runtime/priority.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module){ 2 | 3 | function PriorityRuntime() { 4 | var minder = this.minder; 5 | var hotbox = this.hotbox; 6 | 7 | var main = hotbox.state('main'); 8 | 9 | main.button({ 10 | position: 'top', 11 | label: '优先级', 12 | key: 'P', 13 | next: 'priority', 14 | enable: function() { 15 | return minder.queryCommandState('priority') != -1; 16 | } 17 | }); 18 | 19 | var priority = hotbox.state('priority'); 20 | '123456789'.replace(/./g, function(p) { 21 | priority.button({ 22 | position: 'ring', 23 | label: 'P' + p, 24 | key: p, 25 | action: function() { 26 | minder.execCommand('Priority', p); 27 | } 28 | }); 29 | }); 30 | 31 | priority.button({ 32 | position: 'center', 33 | label: '移除', 34 | key: 'Del', 35 | action: function() { 36 | minder.execCommand('Priority', 0); 37 | } 38 | }); 39 | 40 | priority.button({ 41 | position: 'top', 42 | label: '返回', 43 | key: 'esc', 44 | next: 'back' 45 | }); 46 | 47 | } 48 | 49 | return module.exports = PriorityRuntime; 50 | 51 | }); -------------------------------------------------------------------------------- /webui/src/runtime/progress.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module){ 2 | 3 | function ProgressRuntime() { 4 | var minder = this.minder; 5 | var hotbox = this.hotbox; 6 | 7 | var main = hotbox.state('main'); 8 | 9 | main.button({ 10 | position: 'top', 11 | label: '进度', 12 | key: 'G', 13 | next: 'progress', 14 | enable: function() { 15 | return minder.queryCommandState('progress') != -1; 16 | } 17 | }); 18 | 19 | var progress = hotbox.state('progress'); 20 | '012345678'.replace(/./g, function(p) { 21 | progress.button({ 22 | position: 'ring', 23 | label: 'G' + p, 24 | key: p, 25 | action: function() { 26 | minder.execCommand('Progress', parseInt(p) + 1); 27 | } 28 | }); 29 | }); 30 | 31 | progress.button({ 32 | position: 'center', 33 | label: '移除', 34 | key: 'Del', 35 | action: function() { 36 | minder.execCommand('Progress', 0); 37 | } 38 | }); 39 | 40 | progress.button({ 41 | position: 'top', 42 | label: '返回', 43 | key: 'esc', 44 | next: 'back' 45 | }); 46 | 47 | 48 | } 49 | 50 | return module.exports = ProgressRuntime; 51 | 52 | }); -------------------------------------------------------------------------------- /webui/src/runtime/receiver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 键盘事件接收/分发器 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | 10 | define(function(require, exports, module) { 11 | var key = require('../tool/key'); 12 | var hotbox = require('hotbox'); 13 | 14 | function ReceiverRuntime() { 15 | var fsm = this.fsm; 16 | var minder = this.minder; 17 | var me = this; 18 | 19 | // 接收事件的 div 20 | var element = document.createElement('div'); 21 | element.contentEditable = true; 22 | /** 23 | * @Desc: 增加tabindex属性使得element的contenteditable不管是trur还是false都能有focus和blur事件 24 | * @Editor: Naixor 25 | * @Date: 2015.09.14 26 | */ 27 | element.setAttribute('tabindex', -1); 28 | element.classList.add('receiver'); 29 | element.onkeydown = element.onkeypress = element.onkeyup = dispatchKeyEvent; 30 | element.addEventListener('compositionstart', dispatchKeyEvent); 31 | // element.addEventListener('compositionend', dispatchKeyEvent); 32 | this.container.appendChild(element); 33 | 34 | // receiver 对象 35 | var receiver = { 36 | element: element, 37 | selectAll: function() { 38 | // 保证有被选中的 39 | if (!element.innerHTML) element.innerHTML = ' '; 40 | var range = document.createRange(); 41 | var selection = window.getSelection(); 42 | range.selectNodeContents(element); 43 | selection.removeAllRanges(); 44 | selection.addRange(range); 45 | element.focus(); 46 | }, 47 | /** 48 | * @Desc: 增加enable和disable方法用于解决热核态的输入法屏蔽问题 49 | * @Editor: Naixor 50 | * @Date: 2015.09.14 51 | */ 52 | enable: function() { 53 | element.setAttribute("contenteditable", true); 54 | }, 55 | disable: function() { 56 | element.setAttribute("contenteditable", false); 57 | }, 58 | /** 59 | * @Desc: hack FF下div contenteditable的光标丢失BUG 60 | * @Editor: Naixor 61 | * @Date: 2015.10.15 62 | */ 63 | fixFFCaretDisappeared: function() { 64 | element.removeAttribute("contenteditable"); 65 | element.setAttribute("contenteditable", "true"); 66 | element.blur(); 67 | element.focus(); 68 | }, 69 | /** 70 | * 以此事件代替通过mouse事件来判断receiver丢失焦点的事件 71 | * @editor Naixor 72 | * @Date 2015-12-2 73 | */ 74 | onblur: function (handler) { 75 | element.onblur = handler; 76 | } 77 | }; 78 | receiver.selectAll(); 79 | 80 | minder.on('beforemousedown', receiver.selectAll); 81 | minder.on('receiverfocus', receiver.selectAll); 82 | minder.on('readonly', function() { 83 | // 屏蔽minder的事件接受,删除receiver和hotbox 84 | minder.disable(); 85 | editor.receiver.element.parentElement.removeChild(editor.receiver.element); 86 | editor.hotbox.$container.removeChild(editor.hotbox.$element); 87 | }); 88 | 89 | // 侦听器,接收到的事件会派发给所有侦听器 90 | var listeners = []; 91 | 92 | // 侦听指定状态下的事件,如果不传 state,侦听所有状态 93 | receiver.listen = function(state, listener) { 94 | if (arguments.length == 1) { 95 | listener = state; 96 | state = '*'; 97 | } 98 | listener.notifyState = state; 99 | listeners.push(listener); 100 | }; 101 | 102 | function dispatchKeyEvent(e) { 103 | e.is = function(keyExpression) { 104 | var subs = keyExpression.split('|'); 105 | for (var i = 0; i < subs.length; i++) { 106 | if (key.is(this, subs[i])) return true; 107 | } 108 | return false; 109 | }; 110 | var listener, jumpState; 111 | for (var i = 0; i < listeners.length; i++) { 112 | 113 | listener = listeners[i]; 114 | // 忽略不在侦听状态的侦听器 115 | if (listener.notifyState != '*' && listener.notifyState != fsm.state()) { 116 | continue; 117 | } 118 | 119 | /** 120 | * 121 | * 对于所有的侦听器,只允许一种处理方式:跳转状态。 122 | * 如果侦听器确定要跳转,则返回要跳转的状态。 123 | * 每个事件只允许一个侦听器进行状态跳转 124 | * 跳转动作由侦听器自行完成(因为可能需要在跳转时传递 reason),返回跳转结果即可。 125 | * 比如: 126 | * 127 | * ```js 128 | * receiver.listen('normal', function(e) { 129 | * if (isSomeReasonForJumpState(e)) { 130 | * return fsm.jump('newstate', e); 131 | * } 132 | * }); 133 | * ``` 134 | */ 135 | if (listener.call(null, e)) { 136 | return; 137 | } 138 | } 139 | } 140 | 141 | this.receiver = receiver; 142 | } 143 | 144 | return module.exports = ReceiverRuntime; 145 | 146 | }); 147 | -------------------------------------------------------------------------------- /webui/src/tool/debug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 支持各种调试后门 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function(require, exports, module) { 10 | var format = require('./format'); 11 | 12 | function noop() {} 13 | 14 | function stringHash(str) { 15 | var hash = 0; 16 | for (var i = 0; i < str.length; i++) { 17 | hash += str.charCodeAt(i); 18 | } 19 | return hash; 20 | } 21 | 22 | /* global console */ 23 | function Debug(flag) { 24 | var debugMode = this.flaged = window.location.search.indexOf(flag) != -1; 25 | 26 | if (debugMode) { 27 | var h = stringHash(flag) % 360; 28 | 29 | var flagStyle = format( 30 | 'background: hsl({0}, 50%, 80%); ' + 31 | 'color: hsl({0}, 100%, 30%); ' + 32 | 'padding: 2px 3px; ' + 33 | 'margin: 1px 3px 0 0;' + 34 | 'border-radius: 2px;', h); 35 | 36 | var textStyle = 'background: none; color: black;'; 37 | this.log = function() { 38 | var output = format.apply(null, arguments); 39 | console.log(format('%c{0}%c{1}', flag, output), flagStyle, textStyle); 40 | }; 41 | } else { 42 | this.log = noop; 43 | } 44 | } 45 | 46 | return module.exports = Debug; 47 | }); -------------------------------------------------------------------------------- /webui/src/tool/format.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | function format(template, args) { 3 | if (typeof(args) != 'object') { 4 | args = [].slice.call(arguments, 1); 5 | } 6 | return String(template).replace(/\{(\w+)\}/ig, function(match, $key) { 7 | return args[$key] || $key; 8 | }); 9 | } 10 | return module.exports = format; 11 | }); -------------------------------------------------------------------------------- /webui/src/tool/innertext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * innerText polyfill 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | 10 | 11 | define(function(require, exports, module) { 12 | if ((!('innerText' in document.createElement('a'))) && ('getSelection' in window)) { 13 | HTMLElement.prototype.__defineGetter__('innerText', function() { 14 | var selection = window.getSelection(), 15 | ranges = [], 16 | str, i; 17 | 18 | // Save existing selections. 19 | for (i = 0; i < selection.rangeCount; i++) { 20 | ranges[i] = selection.getRangeAt(i); 21 | } 22 | 23 | // Deselect everything. 24 | selection.removeAllRanges(); 25 | 26 | // Select `el` and all child nodes. 27 | // 'this' is the element .innerText got called on 28 | selection.selectAllChildren(this); 29 | 30 | // Get the string representation of the selected nodes. 31 | str = selection.toString(); 32 | 33 | // Deselect everything. Again. 34 | selection.removeAllRanges(); 35 | 36 | // Restore all formerly existing selections. 37 | for (i = 0; i < ranges.length; i++) { 38 | selection.addRange(ranges[i]); 39 | } 40 | 41 | // Oh look, this is what we wanted. 42 | // String representation of the element, close to as rendered. 43 | return str; 44 | }); 45 | HTMLElement.prototype.__defineSetter__('innerText', function(text) { 46 | /** 47 | * @Desc: 解决FireFox节点内容删除后text为null,出现报错的问题 48 | * @Editor: Naixor 49 | * @Date: 2015.9.16 50 | */ 51 | this.innerHTML = (text || '').replace(//g, '>').replace(/\n/g, '
'); 52 | }); 53 | } 54 | }); -------------------------------------------------------------------------------- /webui/src/tool/jsondiff.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | 10 | 11 | define(function(require, exports, module) { 12 | /*! 13 | * https://github.com/Starcounter-Jack/Fast-JSON-Patch 14 | * json-patch-duplex.js 0.5.0 15 | * (c) 2013 Joachim Wester 16 | * MIT license 17 | */ 18 | 19 | var _objectKeys = (function () { 20 | if (Object.keys) 21 | return Object.keys; 22 | 23 | return function (o) { 24 | var keys = []; 25 | for (var i in o) { 26 | if (o.hasOwnProperty(i)) { 27 | keys.push(i); 28 | } 29 | } 30 | return keys; 31 | }; 32 | })(); 33 | function escapePathComponent(str) { 34 | if (str.indexOf('/') === -1 && str.indexOf('~') === -1) 35 | return str; 36 | return str.replace(/~/g, '~0').replace(/\//g, '~1'); 37 | } 38 | function deepClone(obj) { 39 | if (typeof obj === "object") { 40 | return JSON.parse(JSON.stringify(obj)); 41 | } else { 42 | return obj; 43 | } 44 | } 45 | 46 | // Dirty check if obj is different from mirror, generate patches and update mirror 47 | function _generate(mirror, obj, patches, path) { 48 | var newKeys = _objectKeys(obj); 49 | var oldKeys = _objectKeys(mirror); 50 | var changed = false; 51 | var deleted = false; 52 | 53 | for (var t = oldKeys.length - 1; t >= 0; t--) { 54 | var key = oldKeys[t]; 55 | var oldVal = mirror[key]; 56 | if (obj.hasOwnProperty(key)) { 57 | var newVal = obj[key]; 58 | if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null) { 59 | _generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key)); 60 | } else { 61 | if (oldVal != newVal) { 62 | changed = true; 63 | patches.push({ op: "replace", path: path + "/" + escapePathComponent(key), value: deepClone(newVal) }); 64 | } 65 | } 66 | } else { 67 | patches.push({ op: "remove", path: path + "/" + escapePathComponent(key) }); 68 | deleted = true; // property has been deleted 69 | } 70 | } 71 | 72 | if (!deleted && newKeys.length == oldKeys.length) { 73 | return; 74 | } 75 | 76 | for (var t = 0; t < newKeys.length; t++) { 77 | var key = newKeys[t]; 78 | if (!mirror.hasOwnProperty(key)) { 79 | patches.push({ op: "add", path: path + "/" + escapePathComponent(key), value: deepClone(obj[key]) }); 80 | } 81 | } 82 | } 83 | 84 | function compare(tree1, tree2) { 85 | var patches = []; 86 | _generate(tree1, tree2, patches, ''); 87 | return patches; 88 | } 89 | 90 | return module.exports = compare; 91 | }); -------------------------------------------------------------------------------- /webui/src/tool/key.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | var keymap = require('./keymap'); 3 | 4 | var CTRL_MASK = 0x1000; 5 | var ALT_MASK = 0x2000; 6 | var SHIFT_MASK = 0x4000; 7 | 8 | function hash(unknown) { 9 | if (typeof(unknown) == 'string') { 10 | return hashKeyExpression(unknown); 11 | } 12 | return hashKeyEvent(unknown); 13 | } 14 | function is(a, b) { 15 | return a && b && hash(a) == hash(b); 16 | } 17 | exports.hash = hash; 18 | exports.is = is; 19 | 20 | 21 | function hashKeyEvent(keyEvent) { 22 | var hashCode = 0; 23 | if (keyEvent.ctrlKey || keyEvent.metaKey) { 24 | hashCode |= CTRL_MASK; 25 | } 26 | if (keyEvent.altKey) { 27 | hashCode |= ALT_MASK; 28 | } 29 | if (keyEvent.shiftKey) { 30 | hashCode |= SHIFT_MASK; 31 | } 32 | // Shift, Control, Alt KeyCode ignored. 33 | if ([16, 17, 18, 91].indexOf(keyEvent.keyCode) === -1) { 34 | /** 35 | * 解决浏览器输入法状态下对keyDown的keyCode判断不准确的问题,使用keyIdentifier, 36 | * 可以解决chrome和safari下的各种问题,其他浏览器依旧有问题,然而那并不影响我们对特 37 | * 需判断的按键进行判断(比如Space在safari输入法态下就是229,其他的就不是) 38 | * @editor Naixor 39 | * @Date 2015-12-2 40 | */ 41 | if (keyEvent.keyCode === 229 && keyEvent.keyIdentifier) { 42 | return hashCode |= parseInt(keyEvent.keyIdentifier.substr(2), 16); 43 | } 44 | hashCode |= keyEvent.keyCode; 45 | } 46 | return hashCode; 47 | } 48 | 49 | function hashKeyExpression(keyExpression) { 50 | var hashCode = 0; 51 | keyExpression.toLowerCase().split(/\s*\+\s*/).forEach(function(name) { 52 | switch(name) { 53 | case 'ctrl': 54 | case 'cmd': 55 | hashCode |= CTRL_MASK; 56 | break; 57 | case 'alt': 58 | hashCode |= ALT_MASK; 59 | break; 60 | case 'shift': 61 | hashCode |= SHIFT_MASK; 62 | break; 63 | default: 64 | hashCode |= keymap[name]; 65 | } 66 | }); 67 | return hashCode; 68 | } 69 | }); 70 | -------------------------------------------------------------------------------- /webui/src/tool/keymap.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | var keymap = { 3 | 4 | 'Shift': 16, 5 | 'Control': 17, 6 | 'Alt': 18, 7 | 'CapsLock': 20, 8 | 9 | 'BackSpace': 8, 10 | 'Tab': 9, 11 | 'Enter': 13, 12 | 'Esc': 27, 13 | 'Space': 32, 14 | 15 | 'PageUp': 33, 16 | 'PageDown': 34, 17 | 'End': 35, 18 | 'Home': 36, 19 | 20 | 'Insert': 45, 21 | 22 | 'Left': 37, 23 | 'Up': 38, 24 | 'Right': 39, 25 | 'Down': 40, 26 | 27 | 'Direction': { 28 | 37: 1, 29 | 38: 1, 30 | 39: 1, 31 | 40: 1 32 | }, 33 | 34 | 'Del': 46, 35 | 36 | 'NumLock': 144, 37 | 38 | 'Cmd': 91, 39 | 'CmdFF': 224, 40 | 'F1': 112, 41 | 'F2': 113, 42 | 'F3': 114, 43 | 'F4': 115, 44 | 'F5': 116, 45 | 'F6': 117, 46 | 'F7': 118, 47 | 'F8': 119, 48 | 'F9': 120, 49 | 'F10': 121, 50 | 'F11': 122, 51 | 'F12': 123, 52 | 53 | '`': 192, 54 | '=': 187, 55 | '-': 189, 56 | 57 | '/': 191, 58 | '.': 190 59 | }; 60 | 61 | // 小写适配 62 | for (var key in keymap) { 63 | if (keymap.hasOwnProperty(key)) { 64 | keymap[key.toLowerCase()] = keymap[key]; 65 | } 66 | } 67 | var aKeyCode = 65; 68 | var aCharCode = 'a'.charCodeAt(0); 69 | 70 | // letters 71 | 'abcdefghijklmnopqrstuvwxyz'.split('').forEach(function(letter) { 72 | keymap[letter] = aKeyCode + (letter.charCodeAt(0) - aCharCode); 73 | }); 74 | 75 | // numbers 76 | var n = 9; 77 | do { 78 | keymap[n.toString()] = n + 48; 79 | } while (--n); 80 | 81 | module.exports = keymap; 82 | }); -------------------------------------------------------------------------------- /webui/ui/dialog/hyperlink/hyperlink.ctrl.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .controller('hyperlink.ctrl', function ($scope, $modalInstance, link) { 3 | 4 | var urlRegex = '^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$'; 5 | $scope.R_URL = new RegExp(urlRegex, 'i'); 6 | 7 | $scope.url = link.url || ''; 8 | $scope.title = link.title || ''; 9 | 10 | setTimeout(function() { 11 | var $linkUrl = $('#link-url'); 12 | $linkUrl.focus(); 13 | $linkUrl[0].setSelectionRange(0, $scope.url.length); 14 | }, 30); 15 | 16 | $scope.shortCut = function(e) { 17 | e.stopPropagation(); 18 | 19 | if (e.keyCode == 13) { 20 | $scope.ok(); 21 | } else if (e.keyCode == 27) { 22 | $scope.cancel(); 23 | } 24 | }; 25 | 26 | $scope.ok = function () { 27 | if($scope.R_URL.test($scope.url)) { 28 | $modalInstance.close({ 29 | url: $scope.url, 30 | title: $scope.title 31 | }); 32 | } else { 33 | $scope.urlPassed = false; 34 | 35 | var $linkUrl = $('#link-url'); 36 | $linkUrl.focus(); 37 | $linkUrl[0].setSelectionRange(0, $scope.url.length); 38 | } 39 | editor.receiver.selectAll(); 40 | }; 41 | 42 | $scope.cancel = function () { 43 | $modalInstance.dismiss('cancel'); 44 | editor.receiver.selectAll(); 45 | }; 46 | 47 | }); -------------------------------------------------------------------------------- /webui/ui/dialog/hyperlink/hyperlink.tpl.html: -------------------------------------------------------------------------------- 1 | 4 | 23 | -------------------------------------------------------------------------------- /webui/ui/dialog/imExportNode/imExportNode.ctrl.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .controller('imExportNode.ctrl', function ($scope, $modalInstance, title, defaultValue, type) { 3 | 4 | $scope.title = title; 5 | 6 | $scope.value = defaultValue; 7 | 8 | $scope.type = type; 9 | 10 | $scope.ok = function () { 11 | if ($scope.value == '') { 12 | return; 13 | } 14 | $modalInstance.close($scope.value); 15 | editor.receiver.selectAll(); 16 | }; 17 | 18 | $scope.cancel = function () { 19 | $modalInstance.dismiss('cancel'); 20 | editor.receiver.selectAll(); 21 | }; 22 | 23 | setTimeout(function() { 24 | $('.single-input').focus(); 25 | 26 | $('.single-input')[0].setSelectionRange(0, defaultValue.length); 27 | 28 | }, 30); 29 | 30 | $scope.shortCut = function(e) { 31 | e.stopPropagation(); 32 | 33 | //if (e.keyCode == 13 && e.shiftKey == false) { 34 | // $scope.ok(); 35 | //} 36 | 37 | if (e.keyCode == 27) { 38 | $scope.cancel(); 39 | } 40 | 41 | // tab 键屏蔽默认事件 和 backspace 键屏蔽默认事件 42 | if (e.keyCode == 8 && type == 'export') { 43 | e.preventDefault(); 44 | } 45 | 46 | if (e.keyCode == 9) { 47 | e.preventDefault(); 48 | var $textarea = e.target; 49 | var pos = getCursortPosition($textarea); 50 | var str = $textarea.value; 51 | $textarea.value = str.substr(0, pos) + '\t' + str.substr(pos); 52 | setCaretPosition($textarea, pos + 1); 53 | } 54 | 55 | }; 56 | 57 | /* 58 | * 获取 textarea 的光标位置 59 | * @Author: Naixor 60 | * @date: 2015.09.23 61 | * */ 62 | function getCursortPosition (ctrl) { 63 | var CaretPos = 0; // IE Support 64 | if (document.selection) { 65 | ctrl.focus (); 66 | var Sel = document.selection.createRange (); 67 | Sel.moveStart ('character', -ctrl.value.length); 68 | CaretPos = Sel.text.length; 69 | } 70 | // Firefox support 71 | else if (ctrl.selectionStart || ctrl.selectionStart == '0') { 72 | CaretPos = ctrl.selectionStart; 73 | } 74 | return (CaretPos); 75 | } 76 | 77 | /* 78 | * 设置 textarea 的光标位置 79 | * @Author: Naixor 80 | * @date: 2015.09.23 81 | * */ 82 | function setCaretPosition(ctrl, pos){ 83 | if(ctrl.setSelectionRange) { 84 | ctrl.focus(); 85 | ctrl.setSelectionRange(pos,pos); 86 | } else if (ctrl.createTextRange) { 87 | var range = ctrl.createTextRange(); 88 | range.collapse(true); 89 | range.moveEnd('character', pos); 90 | range.moveStart('character', pos); 91 | range.select(); 92 | } 93 | } 94 | 95 | }); -------------------------------------------------------------------------------- /webui/ui/dialog/imExportNode/imExportNode.tpl.html: -------------------------------------------------------------------------------- 1 | 4 | 13 | -------------------------------------------------------------------------------- /webui/ui/dialog/image/image.ctrl.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .controller('image.ctrl', ['$http', '$scope', '$modalInstance', 'image', 'server', function($http, $scope, $modalInstance, image, server) { 3 | 4 | $scope.data = { 5 | list: [], 6 | url: image.url || '', 7 | title: image.title || '', 8 | R_URL: /^https?\:\/\/\w+/ 9 | }; 10 | 11 | setTimeout(function() { 12 | var $imageUrl = $('#image-url'); 13 | $imageUrl.focus(); 14 | $imageUrl[0].setSelectionRange(0, $scope.data.url.length); 15 | }, 300); 16 | 17 | 18 | // 搜索图片按钮点击事件 19 | $scope.searchImage = function() { 20 | $scope.list = []; 21 | 22 | getImageData() 23 | .success(function(json) { 24 | if(json && json.data) { 25 | for(var i = 0; i < json.data.length; i++) { 26 | if(json.data[i].objURL) { 27 | $scope.list.push({ 28 | title: json.data[i].fromPageTitleEnc, 29 | src: json.data[i].middleURL, 30 | url: json.data[i].middleURL 31 | }); 32 | } 33 | } 34 | } 35 | }) 36 | .error(function() { 37 | 38 | }); 39 | }; 40 | 41 | // 选择图片的鼠标点击事件 42 | $scope.selectImage = function($event) { 43 | var targetItem = $('#img-item'+ (this.$index)); 44 | var targetImg = $('#img-'+ (this.$index)); 45 | 46 | targetItem.siblings('.selected').removeClass('selected'); 47 | targetItem.addClass('selected'); 48 | 49 | $scope.data.url = targetImg.attr('src'); 50 | $scope.data.title = targetImg.attr('alt'); 51 | }; 52 | 53 | // 自动上传图片,后端需要直接返回图片 URL 54 | $scope.uploadImage = function() { 55 | var fileInput = $('#upload-image'); 56 | if (!fileInput.val()) { 57 | return; 58 | } 59 | if (/^.*\.(jpg|JPG|jpeg|JPEG|gif|GIF|png|PNG)$/.test(fileInput.val())) { 60 | var file = fileInput[0].files[0]; 61 | return server.uploadImage(file).then(function (json) { 62 | var resp = json.data; 63 | if (resp.errno === 0) { 64 | $scope.data.url = resp.data.url; 65 | } 66 | }); 67 | } else { 68 | alert("后缀只能是 jpg、gif 及 png"); 69 | } 70 | }; 71 | 72 | $scope.shortCut = function(e) { 73 | e.stopPropagation(); 74 | 75 | if (e.keyCode == 13) { 76 | $scope.ok(); 77 | } else if (e.keyCode == 27) { 78 | $scope.cancel(); 79 | } 80 | }; 81 | 82 | $scope.ok = function () { 83 | if($scope.data.R_URL.test($scope.data.url)) { 84 | $modalInstance.close({ 85 | url: $scope.data.url, 86 | title: $scope.data.title 87 | }); 88 | } else { 89 | $scope.urlPassed = false; 90 | 91 | var $imageUrl = $('#image-url'); 92 | if ($imageUrl) { 93 | $imageUrl.focus(); 94 | $imageUrl[0].setSelectionRange(0, $scope.data.url.length); 95 | } 96 | 97 | } 98 | 99 | editor.receiver.selectAll(); 100 | }; 101 | 102 | $scope.cancel = function () { 103 | $modalInstance.dismiss('cancel'); 104 | editor.receiver.selectAll(); 105 | }; 106 | 107 | function getImageData() { 108 | var key = $scope.data.searchKeyword2; 109 | var currentTime = new Date(); 110 | var url = 'http://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&fp=result&queryWord='+ key +'&cl=2&lm=-1&ie=utf-8&oe=utf-8&st=-1&ic=0&word='+ key +'&face=0&istype=2&nc=1&pn=60&rn=60&gsm=3c&'+ currentTime.getTime() +'=&callback=JSON_CALLBACK'; 111 | 112 | return $http.jsonp(url); 113 | } 114 | }]); -------------------------------------------------------------------------------- /webui/ui/dialog/image/image.tpl.html: -------------------------------------------------------------------------------- 1 | 4 | 66 | -------------------------------------------------------------------------------- /webui/ui/directive/appendNode/appendNode.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor').directive('appendNode', [ 2 | 'commandBinder', 3 | function(commandBinder) { 4 | return { 5 | restrict: 'E', 6 | templateUrl: 'ui/directive/appendNode/appendNode.html', 7 | scope: { 8 | minder: '=', 9 | }, 10 | replace: true, 11 | link: function($scope) { 12 | var minder = $scope.minder; 13 | 14 | commandBinder.bind(minder, 'appendchildnode', $scope); 15 | 16 | $scope.execCommand = function(command) { 17 | minder.execCommand(command, 'topic'); 18 | editText(); 19 | }; 20 | 21 | function editText() { 22 | var receiverElement = editor.receiver.element; 23 | var fsm = editor.fsm; 24 | var receiver = editor.receiver; 25 | 26 | receiverElement.innerText = minder.queryCommandValue('text'); 27 | fsm.jump('input', 'input-request'); 28 | receiver.selectAll(); 29 | } 30 | }, 31 | }; 32 | }, 33 | ]); 34 | -------------------------------------------------------------------------------- /webui/ui/directive/appendNode/appendNode.html: -------------------------------------------------------------------------------- 1 |
2 |
6 | 7 | {{ 'appendchildnode' | lang:'ui/command' }} 8 |
9 |
13 | 14 | {{ 'appendparentnode' | lang:'ui/command' }} 15 |
16 |
20 | 21 | {{ 'appendsiblingnode' | lang:'ui/command' }} 22 |
23 |
-------------------------------------------------------------------------------- /webui/ui/directive/arrange/arrange.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('arrange', ['commandBinder', function(commandBinder) { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/arrange/arrange.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | 13 | //commandBinder.bind(minder, 'priority', $scope); 14 | } 15 | } 16 | }]); -------------------------------------------------------------------------------- /webui/ui/directive/arrange/arrange.html: -------------------------------------------------------------------------------- 1 |
2 |
6 | 7 | {{ 'arrangeup' | lang:'ui/command' }} 8 |
9 |
13 | 14 | {{ 'arrangedown' | lang:'ui/command' }} 15 |
16 |
-------------------------------------------------------------------------------- /webui/ui/directive/colorPanel/colorPanel.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('colorPanel', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/colorPanel/colorPanel.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function(scope) { 11 | 12 | var minder = scope.minder; 13 | var currentTheme = minder.getThemeItems(); 14 | 15 | scope.$on('colorPicked', function(event, color) { 16 | event.stopPropagation(); 17 | scope.bgColor = color; 18 | minder.execCommand('background', color); 19 | }); 20 | 21 | scope.setDefaultBg = function() { 22 | var currentNode = minder.getSelectedNode(); 23 | var bgColor = minder.getNodeStyle(currentNode, 'background'); 24 | 25 | // 有可能是 kity 的颜色类 26 | return typeof bgColor === 'object' ? bgColor.toHEX() : bgColor; 27 | }; 28 | 29 | scope.bgColor = scope.setDefaultBg() || '#fff'; 30 | 31 | } 32 | } 33 | }); -------------------------------------------------------------------------------- /webui/ui/directive/colorPanel/colorPanel.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 9 | 10 | 11 | 15 | 16 |
-------------------------------------------------------------------------------- /webui/ui/directive/expandLevel/expandLevel.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('expandLevel', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/expandLevel/expandLevel.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | 12 | $scope.levels = [1, 2, 3, 4, 5, 6]; 13 | } 14 | } 15 | }); -------------------------------------------------------------------------------- /webui/ui/directive/expandLevel/expandLevel.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 15 | 21 |
-------------------------------------------------------------------------------- /webui/ui/directive/export/export.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor').directive('export', [ 2 | 'commandBinder', 3 | function() { 4 | return { 5 | restrict: 'E', 6 | templateUrl: 'ui/directive/export/export.html', 7 | scope: { 8 | minder: '=', 9 | }, 10 | replace: true, 11 | link: function($scope) { 12 | var minder = $scope.minder; 13 | 14 | $scope.save = function() { 15 | window.vscode.postMessage({ 16 | command: 'save', 17 | exportData: JSON.stringify(minder.exportJson(), null, 4), 18 | }); 19 | }; 20 | $scope.exportToImage = function() { 21 | minder.exportData('png').then(function(res) { 22 | window.vscode.postMessage({ 23 | command: 'exportToImage', 24 | exportData: res, 25 | }); 26 | }); 27 | }; 28 | }, 29 | }; 30 | }, 31 | ]); 32 | -------------------------------------------------------------------------------- /webui/ui/directive/export/export.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 |
7 | -------------------------------------------------------------------------------- /webui/ui/directive/fontOperator/fontOperator.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor').directive('fontOperator', function() { 2 | return { 3 | restrict: 'E', 4 | templateUrl: 'ui/directive/fontOperator/fontOperator.html', 5 | scope: { 6 | minder: '=', 7 | }, 8 | replace: true, 9 | link: function(scope) { 10 | var minder = scope.minder; 11 | var currentTheme = minder.getThemeItems(); 12 | 13 | scope.fontSizeList = [10, 12, 16, 18, 24, 32, 48]; 14 | scope.fontFamilyList = [ 15 | { 16 | name: 'SimSun', 17 | val: '宋体,SimSun', 18 | }, 19 | { 20 | name: 'Microsoft YaHei', 21 | val: '微软雅黑,Microsoft YaHei', 22 | }, 23 | { 24 | name: 'SimKai', 25 | val: '楷体,楷体_GB2312,SimKai', 26 | }, 27 | { 28 | name: 'SimHei', 29 | val: '黑体, SimHei', 30 | }, 31 | { 32 | name: 'SimLi', 33 | val: '隶书, SimLi', 34 | }, 35 | { 36 | name: 'Andale Mono', 37 | val: 'andale mono', 38 | }, 39 | { 40 | name: 'Arial', 41 | val: 'arial,helvetica,sans-serif', 42 | }, 43 | { 44 | name: 'arialBlack', 45 | val: 'arial black,avant garde', 46 | }, 47 | { 48 | name: 'Comic Sans Ms', 49 | val: 'comic sans ms', 50 | }, 51 | { 52 | name: 'Impact', 53 | val: 'impact,chicago', 54 | }, 55 | { 56 | name: 'Times New Roman', 57 | val: 'times new roman', 58 | }, 59 | { 60 | name: 'Sans-Serif', 61 | val: 'sans-serif', 62 | }, 63 | ]; 64 | 65 | scope.$on('colorPicked', function(event, color) { 66 | event.stopPropagation(); 67 | 68 | scope.foreColor = color; 69 | minder.execCommand('forecolor', color); 70 | }); 71 | 72 | scope.setDefaultColor = function() { 73 | var currentNode = minder.getSelectedNode(); 74 | var fontColor = minder.getNodeStyle(currentNode, 'color'); 75 | 76 | // 有可能是 kity 的颜色类 77 | return typeof fontColor === 'object' ? fontColor.toHEX() : fontColor; 78 | }; 79 | 80 | scope.foreColor = scope.setDefaultColor() || '#000'; 81 | 82 | scope.getFontfamilyName = function(val) { 83 | var fontName = ''; 84 | scope.fontFamilyList.forEach(function(ele, idx, arr) { 85 | if (ele.val === val) { 86 | fontName = ele.name; 87 | return ''; 88 | } 89 | }); 90 | 91 | return fontName; 92 | }; 93 | }, 94 | }; 95 | }); 96 | -------------------------------------------------------------------------------- /webui/ui/directive/fontOperator/fontOperator.html: -------------------------------------------------------------------------------- 1 |
2 | 31 | 54 | 60 | 66 | 67 |
68 | A 74 | 80 | 81 | 82 | 88 |
89 | 90 |
91 | -------------------------------------------------------------------------------- /webui/ui/directive/hyperLink/hyperLink.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('hyperLink', ['$modal', function($modal) { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/hyperLink/hyperLink.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | 13 | $scope.addHyperlink = function() { 14 | 15 | var link = minder.queryCommandValue('HyperLink'); 16 | 17 | var hyperlinkModal = $modal.open({ 18 | animation: true, 19 | templateUrl: 'ui/dialog/hyperlink/hyperlink.tpl.html', 20 | controller: 'hyperlink.ctrl', 21 | size: 'md', 22 | resolve: { 23 | link: function() { 24 | return link; 25 | } 26 | } 27 | }); 28 | 29 | hyperlinkModal.result.then(function(result) { 30 | minder.execCommand('HyperLink', result.url, result.title || ''); 31 | }); 32 | } 33 | } 34 | } 35 | }]); -------------------------------------------------------------------------------- /webui/ui/directive/hyperLink/hyperLink.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 17 | 27 |
-------------------------------------------------------------------------------- /webui/ui/directive/imageBtn/imageBtn.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('imageBtn', ['$modal', function($modal) { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/imageBtn/imageBtn.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | 13 | $scope.addImage = function() { 14 | 15 | var image = minder.queryCommandValue('image'); 16 | 17 | var imageModal = $modal.open({ 18 | animation: true, 19 | templateUrl: 'ui/dialog/image/image.tpl.html', 20 | controller: 'image.ctrl', 21 | size: 'md', 22 | resolve: { 23 | image: function() { 24 | return image; 25 | } 26 | } 27 | }); 28 | 29 | imageModal.result.then(function(result) { 30 | minder.execCommand('image', result.url, result.title || ''); 31 | }); 32 | } 33 | } 34 | } 35 | }]); -------------------------------------------------------------------------------- /webui/ui/directive/imageBtn/imageBtn.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 17 | 27 |
-------------------------------------------------------------------------------- /webui/ui/directive/kityminderEditor/kityminderEditor.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor').directive('kityminderEditor', [ 2 | 'config', 3 | 'minder.service', 4 | 'revokeDialog', 5 | function(config, minderService, revokeDialog) { 6 | return { 7 | restrict: 'EA', 8 | templateUrl: 'ui/directive/kityminderEditor/kityminderEditor.html', 9 | replace: true, 10 | scope: { 11 | onInit: '&', 12 | }, 13 | link: function(scope, element, attributes) { 14 | var $minderEditor = element.children('.minder-editor')[0]; 15 | 16 | function onInit(editor, minder) { 17 | scope.onInit({ 18 | editor: editor, 19 | minder: minder, 20 | }); 21 | 22 | minderService.executeCallback(); 23 | } 24 | 25 | if (typeof seajs != 'undefined') { 26 | /* global seajs */ 27 | seajs.config({ 28 | base: './src', 29 | }); 30 | 31 | define('demo', function(require) { 32 | var Editor = require('editor'); 33 | 34 | var editor = (window.editor = new Editor($minderEditor)); 35 | 36 | if (window.vscode.getState()['__dev_minder_content']) { 37 | editor.minder.importJson( 38 | JSON.parse(window.vscode.getState()['__dev_minder_content']) 39 | ); 40 | } 41 | 42 | editor.minder.on('contentchange', function() { 43 | window.vscode.setState({ 44 | __dev_minder_content: JSON.stringify( 45 | editor.minder.exportJson() 46 | ), 47 | }); 48 | }); 49 | 50 | window.minder = window.km = editor.minder; 51 | 52 | scope.editor = editor; 53 | scope.minder = minder; 54 | scope.config = config.get(); 55 | 56 | //scope.minder.setDefaultOptions(scope.config); 57 | scope.$apply(); 58 | 59 | onInit(editor, minder); 60 | }); 61 | 62 | seajs.use('demo'); 63 | } else if (window.kityminder && window.kityminder.Editor) { 64 | var editor = new kityminder.Editor($minderEditor); 65 | 66 | window.editor = scope.editor = editor; 67 | window.minder = scope.minder = editor.minder; 68 | 69 | scope.config = config.get(); 70 | 71 | //scope.minder.setDefaultOptions(config.getConfig()); 72 | 73 | onInit(editor, editor.minder); 74 | } 75 | }, 76 | }; 77 | }, 78 | ]); 79 | -------------------------------------------------------------------------------- /webui/ui/directive/kityminderEditor/kityminderEditor.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | 8 |
-------------------------------------------------------------------------------- /webui/ui/directive/kityminderViewer/kityminderViewer.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('kityminderViewer', ['config', 'minder.service', function(config, minderService) { 3 | return { 4 | restrict: 'EA', 5 | templateUrl: 'ui/directive/kityminderViewer/kityminderViewer.html', 6 | replace: true, 7 | scope: { 8 | onInit: '&' 9 | }, 10 | link: function(scope, element, attributes) { 11 | 12 | var $minderEditor = element.children('.minder-viewer')[0]; 13 | 14 | function onInit(editor, minder) { 15 | scope.onInit({ 16 | editor: editor, 17 | minder: minder 18 | }); 19 | 20 | minderService.executeCallback(); 21 | } 22 | 23 | if (window.kityminder && window.kityminder.Editor) { 24 | var editor = new kityminder.Editor($minderEditor); 25 | 26 | window.editor = scope.editor = editor; 27 | window.minder = scope.minder = editor.minder; 28 | 29 | onInit(editor, editor.minder); 30 | } 31 | 32 | } 33 | } 34 | }]); -------------------------------------------------------------------------------- /webui/ui/directive/kityminderViewer/kityminderViewer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
-------------------------------------------------------------------------------- /webui/ui/directive/layout/layout.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('layout', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/layout/layout.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function(scope) { 11 | 12 | } 13 | } 14 | }); -------------------------------------------------------------------------------- /webui/ui/directive/layout/layout.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /webui/ui/directive/navigator/navigator.html: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /webui/ui/directive/noteBtn/noteBtn.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('noteBtn', ['valueTransfer', function(valueTransfer) { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/noteBtn/noteBtn.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | 13 | $scope.addNote =function() { 14 | valueTransfer.noteEditorOpen = true; 15 | }; 16 | } 17 | } 18 | }]); -------------------------------------------------------------------------------- /webui/ui/directive/noteBtn/noteBtn.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 17 | 27 |
-------------------------------------------------------------------------------- /webui/ui/directive/noteEditor/noteEditor.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | 3 | .directive('noteEditor', ['valueTransfer', function(valueTransfer) { 4 | return { 5 | restrict: 'A', 6 | templateUrl: 'ui/directive/noteEditor/noteEditor.html', 7 | scope: { 8 | minder: '=' 9 | }, 10 | replace: true, 11 | controller: function($scope) { 12 | var minder = $scope.minder; 13 | var isInteracting = false; 14 | var cmEditor; 15 | 16 | $scope.codemirrorLoaded = function(_editor) { 17 | 18 | cmEditor = $scope.cmEditor = _editor; 19 | 20 | _editor.setSize('100%', '100%'); 21 | }; 22 | 23 | function updateNote() { 24 | var enabled = $scope.noteEnabled = minder.queryCommandState('note') != -1; 25 | var noteValue = minder.queryCommandValue('note') || ''; 26 | 27 | if (enabled) { 28 | $scope.noteContent = noteValue; 29 | } 30 | 31 | isInteracting = true; 32 | $scope.$apply(); 33 | isInteracting = false; 34 | } 35 | 36 | 37 | $scope.$watch('noteContent', function(content) { 38 | var enabled = minder.queryCommandState('note') != -1; 39 | 40 | if (content && enabled && !isInteracting) { 41 | minder.execCommand('note', content); 42 | } 43 | 44 | setTimeout(function() { 45 | cmEditor.refresh(); 46 | }); 47 | }); 48 | 49 | 50 | var noteEditorOpen = function() { 51 | return valueTransfer.noteEditorOpen; 52 | }; 53 | 54 | // 监听面板状态变量的改变 55 | $scope.$watch(noteEditorOpen, function(newVal, oldVal) { 56 | if (newVal) { 57 | setTimeout(function() { 58 | cmEditor.refresh(); 59 | cmEditor.focus(); 60 | }); 61 | } 62 | $scope.noteEditorOpen = valueTransfer.noteEditorOpen; 63 | }, true); 64 | 65 | 66 | $scope.closeNoteEditor = function() { 67 | valueTransfer.noteEditorOpen = false; 68 | editor.receiver.selectAll(); 69 | }; 70 | 71 | 72 | 73 | minder.on('interactchange', updateNote); 74 | } 75 | } 76 | }]); -------------------------------------------------------------------------------- /webui/ui/directive/noteEditor/noteEditor.html: -------------------------------------------------------------------------------- 1 |
6 |
7 |

note

8 | 支持 GFM 语法书写 16 | 20 |
21 |
22 |
35 |

36 | Select Node to Edit Notes 37 |

38 |
39 |
40 | -------------------------------------------------------------------------------- /webui/ui/directive/notePreviewer/notePreviewer.directive.js: -------------------------------------------------------------------------------- 1 | // TODO: 使用一个 div 容器作为 previewer,而不是两个 2 | angular.module('kityminderEditor') 3 | 4 | .directive('notePreviewer', ['$sce', 'valueTransfer', function($sce, valueTransfer) { 5 | return { 6 | restrict: 'A', 7 | templateUrl: 'ui/directive/notePreviewer/notePreviewer.html', 8 | link: function(scope, element) { 9 | var minder = scope.minder; 10 | var $container = element.parent(); 11 | var $previewer = element.children(); 12 | scope.showNotePreviewer = false; 13 | 14 | marked.setOptions({ 15 | gfm: true, 16 | tables: true, 17 | breaks: true, 18 | pedantic: false, 19 | sanitize: true, 20 | smartLists: true, 21 | smartypants: false 22 | }); 23 | 24 | 25 | var previewTimer; 26 | minder.on('shownoterequest', function(e) { 27 | 28 | previewTimer = setTimeout(function() { 29 | preview(e.node, e.keyword); 30 | }, 300); 31 | }); 32 | minder.on('hidenoterequest', function() { 33 | clearTimeout(previewTimer); 34 | 35 | scope.showNotePreviewer = false; 36 | //scope.$apply(); 37 | }); 38 | 39 | var previewLive = false; 40 | $(document).on('mousedown mousewheel DOMMouseScroll', function() { 41 | if (!previewLive) return; 42 | scope.showNotePreviewer = false; 43 | scope.$apply(); 44 | }); 45 | 46 | element.on('mousedown mousewheel DOMMouseScroll', function(e) { 47 | e.stopPropagation(); 48 | }); 49 | 50 | function preview(node, keyword) { 51 | var icon = node.getRenderer('NoteIconRenderer').getRenderShape(); 52 | var b = icon.getRenderBox('screen'); 53 | var note = node.getData('note'); 54 | 55 | $previewer[0].scrollTop = 0; 56 | 57 | var html = marked(note); 58 | if (keyword) { 59 | html = html.replace(new RegExp('(' + keyword + ')', 'ig'), '$1'); 60 | } 61 | scope.noteContent = $sce.trustAsHtml(html); 62 | scope.$apply(); // 让浏览器重新渲染以获取 previewer 提示框的尺寸 63 | 64 | var cw = $($container[0]).width(); 65 | var ch = $($container[0]).height(); 66 | var pw = $($previewer).outerWidth(); 67 | var ph = $($previewer).outerHeight(); 68 | 69 | var x = b.cx - pw / 2 - $container[0].offsetLeft; 70 | var y = b.bottom + 10 - $container[0].offsetTop; 71 | 72 | if (x < 0) x = 10; 73 | if (x + pw > cw) x = b.left - pw - 10 - $container[0].offsetLeft; 74 | if (y + ph > ch) y = b.top - ph - 10 - $container[0].offsetTop; 75 | 76 | 77 | scope.previewerStyle = { 78 | 'left': Math.round(x) + 'px', 79 | 'top': Math.round(y) + 'px' 80 | }; 81 | 82 | scope.showNotePreviewer = true; 83 | 84 | var view = $previewer[0].querySelector('.highlight'); 85 | if (view) { 86 | view.scrollIntoView(); 87 | } 88 | previewLive = true; 89 | 90 | scope.$apply(); 91 | } 92 | } 93 | } 94 | }]); -------------------------------------------------------------------------------- /webui/ui/directive/notePreviewer/notePreviewer.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /webui/ui/directive/operation/operation.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('operation', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/operation/operation.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | $scope.editNode = function() { 12 | 13 | var receiverElement = editor.receiver.element; 14 | var fsm = editor.fsm; 15 | var receiver = editor.receiver; 16 | 17 | receiverElement.innerText = minder.queryCommandValue('text'); 18 | fsm.jump('input', 'input-request'); 19 | receiver.selectAll(); 20 | 21 | } 22 | 23 | } 24 | } 25 | }); -------------------------------------------------------------------------------- /webui/ui/directive/operation/operation.html: -------------------------------------------------------------------------------- 1 |
2 |
6 | 7 | {{ 'editnode' | lang:'ui/command' }} 8 |
9 |
13 | 14 | {{ 'removenode' | lang:'ui/command' }} 15 |
16 |
17 | -------------------------------------------------------------------------------- /webui/ui/directive/priorityEditor/priorityEditor.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('kityminderEditor') 3 | 4 | .directive('priorityEditor', [ 5 | 'commandBinder', 6 | function(commandBinder) { 7 | return { 8 | restrict: 'E', 9 | templateUrl: 'ui/directive/priorityEditor/priorityEditor.html', 10 | scope: { 11 | minder: '=', 12 | }, 13 | replace: true, 14 | link: function($scope) { 15 | var minder = $scope.minder; 16 | var priorities = []; 17 | 18 | for (var i = 0; i < 10; i++) { 19 | priorities.push(i); 20 | } 21 | 22 | commandBinder.bind(minder, 'priority', $scope); 23 | 24 | $scope.priorities = priorities; 25 | 26 | $scope.getPriorityTitle = function(p) { 27 | switch (p) { 28 | case 0: 29 | return 'clear'; 30 | default: 31 | return 'priority' + p; 32 | } 33 | }; 34 | }, 35 | }; 36 | }, 37 | ]); 38 | -------------------------------------------------------------------------------- /webui/ui/directive/priorityEditor/priorityEditor.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • 7 |
    8 |
  • 9 |
-------------------------------------------------------------------------------- /webui/ui/directive/progressEditor/progressEditor.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor').directive('progressEditor', [ 2 | 'commandBinder', 3 | function(commandBinder) { 4 | return { 5 | restrict: 'E', 6 | templateUrl: 'ui/directive/progressEditor/progressEditor.html', 7 | scope: { 8 | minder: '=', 9 | }, 10 | replace: true, 11 | link: function($scope) { 12 | var minder = $scope.minder; 13 | var progresses = []; 14 | 15 | for (var i = 0; i < 10; i++) { 16 | progresses.push(i); 17 | } 18 | 19 | commandBinder.bind(minder, 'progress', $scope); 20 | 21 | $scope.progresses = progresses; 22 | 23 | $scope.getProgressTitle = function(p) { 24 | switch (p) { 25 | case 0: 26 | return 'clear'; 27 | case 1: 28 | return 'undone'; 29 | case 9: 30 | return 'done'; 31 | default: 32 | return 'done' + (p - 1) + '/8'; 33 | } 34 | }; 35 | }, 36 | }; 37 | }, 38 | ]); 39 | -------------------------------------------------------------------------------- /webui/ui/directive/progressEditor/progressEditor.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • 7 |
    8 |
  • 9 |
-------------------------------------------------------------------------------- /webui/ui/directive/resourceEditor/resourceEditor.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('resourceEditor', function () { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/resourceEditor/resourceEditor.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | controller: function ($scope) { 11 | var minder = $scope.minder; 12 | 13 | var isInteracting = false; 14 | 15 | minder.on('interactchange', function () { 16 | var enabled = $scope.enabled = minder.queryCommandState('resource') != -1; 17 | var selected = enabled ? minder.queryCommandValue('resource') : []; 18 | var used = minder.getUsedResource().map(function (resourceName) { 19 | return { 20 | name: resourceName, 21 | selected: selected.indexOf(resourceName) > -1 22 | } 23 | }); 24 | $scope.used = used; 25 | 26 | isInteracting = true; 27 | $scope.$apply(); 28 | isInteracting = false; 29 | }); 30 | 31 | $scope.$watch('used', function (used) { 32 | if (minder.queryCommandState('resource') != -1 && used) { 33 | var resource = used.filter(function (resource) { 34 | return resource.selected; 35 | }).map(function (resource) { 36 | return resource.name; 37 | }); 38 | 39 | // 由于 interactchange 带来的改变则不用执行 resource 命令 40 | if (isInteracting) { 41 | return; 42 | } 43 | minder.execCommand('resource', resource); 44 | } 45 | }, true); 46 | 47 | $scope.resourceColor = function (resource) { 48 | return minder.getResourceColor(resource).toHEX(); 49 | }; 50 | 51 | $scope.addResource = function (resourceName) { 52 | var origin = minder.queryCommandValue('resource'); 53 | if (!resourceName || !/\S/.test(resourceName)) return; 54 | 55 | if (origin.indexOf(resourceName) == -1) { 56 | $scope.used.push({ 57 | name: resourceName, 58 | selected: true 59 | }); 60 | } 61 | 62 | $scope.newResourceName = null; 63 | }; 64 | 65 | } 66 | }; 67 | }) 68 | 69 | .directive('clickAnywhereButHere', ['$document', function ($document) { 70 | return { 71 | link: function(scope, element, attrs) { 72 | var onClick = function (event) { 73 | var isChild = $('#resource-dropdown').has(event.target).length > 0; 74 | var isSelf = $('#resource-dropdown') == event.target; 75 | var isInside = isChild || isSelf; 76 | if (!isInside) { 77 | scope.$apply(attrs.clickAnywhereButHere) 78 | } 79 | }; 80 | 81 | scope.$watch(attrs.isActive, function(newValue, oldValue) { 82 | if (newValue !== oldValue && newValue == true) { 83 | $document.bind('click', onClick); 84 | } 85 | else if (newValue !== oldValue && newValue == false) { 86 | $document.unbind('click', onClick); 87 | } 88 | }); 89 | } 90 | }; 91 | }]); -------------------------------------------------------------------------------- /webui/ui/directive/resourceEditor/resourceEditor.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 11 | 12 | 19 | 20 |
21 |
22 |
    27 |
  • 32 | 40 |
  • 41 |
42 |
48 | 49 |
50 |
51 |
52 | -------------------------------------------------------------------------------- /webui/ui/directive/searchBox/searchBox.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webui/ui/directive/searchBtn/searchBtn.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('searchBtn', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/searchBtn/searchBtn.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function (scope) { 11 | scope.enterSearch = enterSearch; 12 | 13 | function enterSearch() { 14 | minder.fire('searchNode'); 15 | } 16 | } 17 | } 18 | }); -------------------------------------------------------------------------------- /webui/ui/directive/searchBtn/searchBtn.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 14 |
-------------------------------------------------------------------------------- /webui/ui/directive/selectAll/selectAll.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('selectAll', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/selectAll/selectAll.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | 13 | $scope.items = ['revert', 'siblings', 'level', 'path', 'tree']; 14 | 15 | $scope.select = { 16 | all: function() { 17 | var selection = []; 18 | minder.getRoot().traverse(function(node) { 19 | selection.push(node); 20 | }); 21 | minder.select(selection, true); 22 | minder.fire('receiverfocus'); 23 | }, 24 | revert: function() { 25 | var selected = minder.getSelectedNodes(); 26 | var selection = []; 27 | minder.getRoot().traverse(function(node) { 28 | if (selected.indexOf(node) == -1) { 29 | selection.push(node); 30 | } 31 | }); 32 | minder.select(selection, true); 33 | minder.fire('receiverfocus'); 34 | }, 35 | siblings: function() { 36 | var selected = minder.getSelectedNodes(); 37 | var selection = []; 38 | selected.forEach(function(node) { 39 | if (!node.parent) return; 40 | node.parent.children.forEach(function(sibling) { 41 | if (selection.indexOf(sibling) == -1) selection.push(sibling); 42 | }); 43 | }); 44 | minder.select(selection, true); 45 | minder.fire('receiverfocus'); 46 | }, 47 | level: function() { 48 | var selectedLevel = minder.getSelectedNodes().map(function(node) { 49 | return node.getLevel(); 50 | }); 51 | var selection = []; 52 | minder.getRoot().traverse(function(node) { 53 | if (selectedLevel.indexOf(node.getLevel()) != -1) { 54 | selection.push(node); 55 | } 56 | }); 57 | minder.select(selection, true); 58 | minder.fire('receiverfocus'); 59 | }, 60 | path: function() { 61 | var selected = minder.getSelectedNodes(); 62 | var selection = []; 63 | selected.forEach(function(node) { 64 | while(node && selection.indexOf(node) == -1) { 65 | selection.push(node); 66 | node = node.parent; 67 | } 68 | }); 69 | minder.select(selection, true); 70 | minder.fire('receiverfocus'); 71 | }, 72 | tree: function() { 73 | var selected = minder.getSelectedNodes(); 74 | var selection = []; 75 | selected.forEach(function(parent) { 76 | parent.traverse(function(node) { 77 | if (selection.indexOf(node) == -1) selection.push(node); 78 | }); 79 | }); 80 | minder.select(selection, true); 81 | minder.fire('receiverfocus'); 82 | } 83 | }; 84 | } 85 | } 86 | }); -------------------------------------------------------------------------------- /webui/ui/directive/selectAll/selectAll.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 15 | 21 |
-------------------------------------------------------------------------------- /webui/ui/directive/styleOperator/styleOperator.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('styleOperator', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/styleOperator/styleOperator.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true 10 | } 11 | }); -------------------------------------------------------------------------------- /webui/ui/directive/styleOperator/styleOperator.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webui/ui/directive/templateList/templateList.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('templateList', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/templateList/templateList.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | $scope.templateList = kityminder.Minder.getTemplateList(); 12 | 13 | } 14 | } 15 | }); -------------------------------------------------------------------------------- /webui/ui/directive/templateList/templateList.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webui/ui/directive/themeList/themeList.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('themeList', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/themeList/themeList.html', 6 | replace: true, 7 | link: function($scope) { 8 | var themeList = kityminder.Minder.getThemeList(); 9 | 10 | //$scope.themeList = themeList; 11 | 12 | $scope.getThemeThumbStyle = function (theme) { 13 | var themeObj = themeList[theme]; 14 | if (!themeObj) { 15 | return; 16 | } 17 | var style = { 18 | 'color': themeObj['root-color'], 19 | 'border-radius': themeObj['root-radius'] / 2 20 | }; 21 | 22 | if (themeObj['root-background']) { 23 | style['background'] = themeObj['root-background'].toString(); 24 | } 25 | 26 | return style; 27 | }; 28 | 29 | // 维护 theme key 列表以保证列表美观(不按字母顺序排序) 30 | $scope.themeKeyList = [ 31 | 'classic', 32 | 'classic-compact', 33 | 'fresh-blue', 34 | 'fresh-blue-compat', 35 | 'fresh-green', 36 | 'fresh-green-compat', 37 | 'fresh-pink', 38 | 'fresh-pink-compat', 39 | 'fresh-purple', 40 | 'fresh-purple-compat', 41 | 'fresh-red', 42 | 'fresh-red-compat', 43 | 'fresh-soil', 44 | 'fresh-soil-compat', 45 | 'snow', 46 | 'snow-compact', 47 | 'tianpan', 48 | 'tianpan-compact', 49 | 'fish', 50 | 'wire' 51 | ]; 52 | } 53 | } 54 | }); -------------------------------------------------------------------------------- /webui/ui/directive/themeList/themeList.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webui/ui/directive/topTab/topTab.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('topTab', function() { 3 | return { 4 | restrict: 'A', 5 | templateUrl: 'ui/directive/topTab/topTab.html', 6 | scope: { 7 | minder: '=topTab', 8 | editor: '=' 9 | }, 10 | link: function(scope) { 11 | 12 | /* 13 | * 14 | * 用户选择一个新的选项卡会执行 setCurTab 和 foldTopTab 两个函数 15 | * 用户点击原来的选项卡会执行 foldTopTop 一个函数 16 | * 17 | * 也就是每次选择新的选项卡都会执行 setCurTab,初始化的时候也会执行 setCurTab 函数 18 | * 因此用 executedCurTab 记录是否已经执行了 setCurTab 函数 19 | * 用 isInit 记录是否是初始化的状态,在任意一个函数时候 isInit 设置为 false 20 | * 用 isOpen 记录是否打开了 topTab 21 | * 22 | * 因此用到了三个 mutex 23 | * */ 24 | var executedCurTab = false; 25 | var isInit = true; 26 | var isOpen = true; 27 | 28 | scope.setCurTab = function(tabName) { 29 | setTimeout(function() { 30 | //console.log('set cur tab to : ' + tabName); 31 | executedCurTab = true; 32 | //isOpen = false; 33 | if (tabName != 'idea') { 34 | isInit = false; 35 | } 36 | }); 37 | }; 38 | 39 | scope.toggleTopTab = function() { 40 | setTimeout(function() { 41 | if(!executedCurTab || isInit) { 42 | isInit = false; 43 | 44 | isOpen ? closeTopTab(): openTopTab(); 45 | isOpen = !isOpen; 46 | } 47 | 48 | executedCurTab = false; 49 | }); 50 | }; 51 | 52 | function closeTopTab() { 53 | var $tabContent = $('.tab-content'); 54 | var $minderEditor = $('.minder-editor'); 55 | 56 | $tabContent.animate({ 57 | height: 0, 58 | display: 'none' 59 | }); 60 | 61 | $minderEditor.animate({ 62 | top: '32px' 63 | }); 64 | } 65 | 66 | function openTopTab() { 67 | var $tabContent = $('.tab-content'); 68 | var $minderEditor = $('.minder-editor'); 69 | 70 | $tabContent.animate({ 71 | height: '60px', 72 | display: 'block' 73 | }); 74 | 75 | $minderEditor.animate({ 76 | top: '92px' 77 | }); 78 | } 79 | } 80 | } 81 | }); -------------------------------------------------------------------------------- /webui/ui/directive/topTab/topTab.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /webui/ui/directive/undoRedo/undoRedo.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('undoRedo', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/undoRedo/undoRedo.html', 6 | scope: { 7 | editor: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | 12 | } 13 | } 14 | }); -------------------------------------------------------------------------------- /webui/ui/directive/undoRedo/undoRedo.html: -------------------------------------------------------------------------------- 1 |
2 |
6 | 7 | 8 |
9 |
13 | 14 | 15 |
16 |
-------------------------------------------------------------------------------- /webui/ui/filter/command.filters.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .filter('commandState', function() { 3 | return function(minder, command) { 4 | return minder.queryCommandState(command); 5 | } 6 | }) 7 | .filter('commandValue', function() { 8 | return function(minder, command) { 9 | return minder.queryCommandValue(command); 10 | } 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /webui/ui/filter/lang.filter.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor').filter('lang', [ 2 | 'config', 3 | 'lang.en', 4 | function(config, lang) { 5 | return function(text, block) { 6 | var defaultLang = config.get('defaultLang'); 7 | 8 | if (lang[defaultLang] == undefined) { 9 | return '未发现对应语言包,请检查 lang.xxx.service.js!'; 10 | } else { 11 | var dict = lang[defaultLang]; 12 | block.split('/').forEach(function(ele, idx) { 13 | dict = dict[ele]; 14 | }); 15 | 16 | return dict[text] || null; 17 | } 18 | }; 19 | }, 20 | ]); 21 | -------------------------------------------------------------------------------- /webui/ui/images/iconpriority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souche/vscode-mindmap/94b0b5a572cd7bf9971a52a82e0aa9ffb1354725/webui/ui/images/iconpriority.png -------------------------------------------------------------------------------- /webui/ui/images/iconprogress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souche/vscode-mindmap/94b0b5a572cd7bf9971a52a82e0aa9ffb1354725/webui/ui/images/iconprogress.png -------------------------------------------------------------------------------- /webui/ui/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souche/vscode-mindmap/94b0b5a572cd7bf9971a52a82e0aa9ffb1354725/webui/ui/images/icons.png -------------------------------------------------------------------------------- /webui/ui/images/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/souche/vscode-mindmap/94b0b5a572cd7bf9971a52a82e0aa9ffb1354725/webui/ui/images/template.png -------------------------------------------------------------------------------- /webui/ui/kityminder.app.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor', [ 2 | 'ui.bootstrap', 3 | 'ui.codemirror', 4 | 'ui.colorpicker' 5 | ]) 6 | .config(function($sceDelegateProvider) { 7 | $sceDelegateProvider.resourceUrlWhitelist([ 8 | // Allow same origin resource loads. 9 | 'self', 10 | // Allow loading from our assets domain. Notice the difference between * and **. 11 | 'http://agroup.baidu.com:8910/**', 12 | 'http://cq01-fe-rdtest01.vm.baidu.com:8910/**', 13 | 'http://agroup.baidu.com:8911/**' 14 | ]); 15 | }); -------------------------------------------------------------------------------- /webui/ui/service/commandBinder.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor').service('commandBinder', function() { 2 | return { 3 | bind: function(minder, command, scope) { 4 | 5 | minder.on('interactchange', function() { 6 | scope.commandDisabled = minder.queryCommandState(command) === -1; 7 | scope.commandValue = minder.queryCommandValue(command); 8 | scope.$apply(); 9 | }); 10 | } 11 | }; 12 | }); -------------------------------------------------------------------------------- /webui/ui/service/config.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor').provider('config', function() { 2 | this.config = { 3 | // 右侧面板最小宽度 4 | ctrlPanelMin: 250, 5 | 6 | // 右侧面板宽度 7 | ctrlPanelWidth: 250, 8 | 9 | // 分割线宽度 10 | dividerWidth: 3, 11 | 12 | // 默认语言 13 | defaultLang: 'en', 14 | 15 | // 放大缩小比例 16 | zoom: [10, 20, 30, 50, 80, 100, 120, 150, 200], 17 | 18 | // 图片上传接口 19 | imageUpload: 'server/imageUpload.php', 20 | }; 21 | 22 | this.set = function(key, value) { 23 | var supported = Object.keys(this.config); 24 | var configObj = {}; 25 | 26 | // 支持全配置 27 | if (typeof key === 'object') { 28 | configObj = key; 29 | } else { 30 | configObj[key] = value; 31 | } 32 | 33 | for (var i in configObj) { 34 | if (configObj.hasOwnProperty(i) && supported.indexOf(i) !== -1) { 35 | this.config[i] = configObj[i]; 36 | } else { 37 | console.error( 38 | 'Unsupported config key: ', 39 | key, 40 | ', please choose in :', 41 | supported.join(', ') 42 | ); 43 | return false; 44 | } 45 | } 46 | 47 | return true; 48 | }; 49 | 50 | this.$get = function() { 51 | var me = this; 52 | 53 | return { 54 | get: function(key) { 55 | if (arguments.length === 0) { 56 | return me.config; 57 | } 58 | 59 | if (me.config.hasOwnProperty(key)) { 60 | return me.config[key]; 61 | } 62 | 63 | console.warn('Missing config key pair for : ', key); 64 | return ''; 65 | }, 66 | }; 67 | }; 68 | }); 69 | -------------------------------------------------------------------------------- /webui/ui/service/memory.service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * UI 状态的 LocalStorage 的存取文件,未来可能在离线编辑的时候升级 5 | * 6 | * @author: zhangbobell 7 | * @email : zhangbobell@163.com 8 | * 9 | * @copyright: Baidu FEX, 2015 10 | */ 11 | angular.module('kityminderEditor').service('memory', function() { 12 | function isQuotaExceeded(e) { 13 | var quotaExceeded = false; 14 | if (e) { 15 | if (e.code) { 16 | switch (e.code) { 17 | case 22: 18 | quotaExceeded = true; 19 | break; 20 | case 1014: 21 | // Firefox 22 | if (e.name === 'NS_ERROR_DOM_QUOTA_REACHED') { 23 | quotaExceeded = true; 24 | } 25 | break; 26 | } 27 | } else if (e.number === -2147024882) { 28 | // Internet Explorer 8 29 | quotaExceeded = true; 30 | } 31 | } 32 | return quotaExceeded; 33 | } 34 | 35 | return { 36 | get: function(key) { 37 | var value = window.vscode.getState()[key]; 38 | return null || JSON.parse(value); 39 | }, 40 | 41 | set: function(key, value) { 42 | try { 43 | // window.vscode.setState({[key]: JSON.stringify(value) }); 44 | return true; 45 | } catch (e) { 46 | if (isQuotaExceeded(e)) { 47 | return false; 48 | } 49 | } 50 | }, 51 | remove: function(key) { 52 | var value = window.vscode.getState()[key]; 53 | // window.vscode.setState({ 54 | // [key]: null, 55 | // }); 56 | return value; 57 | }, 58 | clear: function() { 59 | // var prevState = window.vscode.getState(); 60 | // for (var key in prevState) { 61 | // window.vscode.setState({ 62 | // [key]: null, 63 | // }); 64 | // } 65 | }, 66 | }; 67 | }); 68 | -------------------------------------------------------------------------------- /webui/ui/service/minder.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .service('minder.service', function() { 3 | 4 | var callbackQueue = []; 5 | 6 | function registerEvent(callback) { 7 | callbackQueue.push(callback); 8 | } 9 | 10 | function executeCallback() { 11 | callbackQueue.forEach(function(ele) { 12 | ele.apply(this, arguments); 13 | }) 14 | } 15 | 16 | return { 17 | registerEvent: registerEvent, 18 | executeCallback: executeCallback 19 | } 20 | }); -------------------------------------------------------------------------------- /webui/ui/service/resource.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .service('resourceService', ['$document', function($document) { 3 | var openScope = null; 4 | 5 | this.open = function( dropdownScope ) { 6 | if ( !openScope ) { 7 | $document.bind('click', closeDropdown); 8 | $document.bind('keydown', escapeKeyBind); 9 | } 10 | 11 | if ( openScope && openScope !== dropdownScope ) { 12 | openScope.resourceListOpen = false; 13 | } 14 | 15 | openScope = dropdownScope; 16 | }; 17 | 18 | this.close = function( dropdownScope ) { 19 | if ( openScope === dropdownScope ) { 20 | openScope = null; 21 | $document.unbind('click', closeDropdown); 22 | $document.unbind('keydown', escapeKeyBind); 23 | } 24 | }; 25 | 26 | var closeDropdown = function( evt ) { 27 | // This method may still be called during the same mouse event that 28 | // unbound this event handler. So check openScope before proceeding. 29 | //console.log(evt, openScope); 30 | if (!openScope) { return; } 31 | 32 | var toggleElement = openScope.getToggleElement(); 33 | if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) { 34 | return; 35 | } 36 | 37 | openScope.$apply(function() { 38 | console.log('to close the resourcelist'); 39 | openScope.resourceListOpen = false; 40 | }); 41 | }; 42 | 43 | var escapeKeyBind = function( evt ) { 44 | if ( evt.which === 27 ) { 45 | openScope.focusToggleElement(); 46 | closeDropdown(); 47 | } 48 | }; 49 | }]) -------------------------------------------------------------------------------- /webui/ui/service/revokeDialog.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor').service('revokeDialog', ['$modal', 'minder.service', function($modal, minderService) { 2 | 3 | minderService.registerEvent(function() { 4 | 5 | // 触发导入节点或导出节点对话框 6 | var minder = window.minder; 7 | var editor = window.editor; 8 | var parentFSM = editor.hotbox.getParentFSM(); 9 | 10 | 11 | minder.on('importNodeData', function() { 12 | parentFSM.jump('modal', 'import-text-modal'); 13 | 14 | var importModal = $modal.open({ 15 | animation: true, 16 | templateUrl: 'ui/dialog/imExportNode/imExportNode.tpl.html', 17 | controller: 'imExportNode.ctrl', 18 | size: 'md', 19 | resolve: { 20 | title: function() { 21 | return '导入节点'; 22 | }, 23 | defaultValue: function() { 24 | return ''; 25 | }, 26 | type: function() { 27 | return 'import'; 28 | } 29 | } 30 | }); 31 | 32 | importModal.result.then(function(result) { 33 | try{ 34 | minder.Text2Children(minder.getSelectedNode(), result); 35 | } catch(e) { 36 | alert(e); 37 | } 38 | parentFSM.jump('normal', 'import-text-finish'); 39 | editor.receiver.selectAll(); 40 | }, function() { 41 | parentFSM.jump('normal', 'import-text-finish'); 42 | editor.receiver.selectAll(); 43 | }); 44 | }); 45 | 46 | minder.on('exportNodeData', function() { 47 | parentFSM.jump('modal', 'export-text-modal'); 48 | 49 | var exportModal = $modal.open({ 50 | animation: true, 51 | templateUrl: 'ui/dialog/imExportNode/imExportNode.tpl.html', 52 | controller: 'imExportNode.ctrl', 53 | size: 'md', 54 | resolve: { 55 | title: function() { 56 | return '导出节点'; 57 | }, 58 | defaultValue: function() { 59 | var selectedNode = minder.getSelectedNode(), 60 | Node2Text = window.kityminder.data.getRegisterProtocol('text').Node2Text; 61 | 62 | return Node2Text(selectedNode); 63 | }, 64 | type: function() { 65 | return 'export'; 66 | } 67 | } 68 | }); 69 | 70 | exportModal.result.then(function(result) { 71 | parentFSM.jump('normal', 'export-text-finish'); 72 | editor.receiver.selectAll(); 73 | }, function() { 74 | parentFSM.jump('normal', 'export-text-finish'); 75 | editor.receiver.selectAll(); 76 | }); 77 | }); 78 | 79 | }); 80 | 81 | return {}; 82 | }]); -------------------------------------------------------------------------------- /webui/ui/service/server.service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 与后端交互的服务 5 | * 6 | * @author: zhangbobell 7 | * @email : zhangbobell@163.com 8 | * 9 | * @copyright: Baidu FEX, 2015 10 | */ 11 | angular.module('kityminderEditor') 12 | .service('server', ['config', '$http', function(config, $http) { 13 | 14 | return { 15 | uploadImage: function(file) { 16 | var url = config.get('imageUpload'); 17 | var fd = new FormData(); 18 | fd.append('upload_file', file); 19 | 20 | return $http.post(url, fd, { 21 | transformRequest: angular.identity, 22 | headers: {'Content-Type': undefined} 23 | }); 24 | } 25 | } 26 | }]); -------------------------------------------------------------------------------- /webui/ui/service/valueTransfer.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .service('valueTransfer', function() { 3 | return {}; 4 | }); --------------------------------------------------------------------------------