增加百度脑图功能集成

This commit is contained in:
jerrylizilong 2019-05-22 11:15:37 +08:00
parent 5d76d6cbb6
commit eede8c4f68
153 changed files with 14295 additions and 3 deletions

View File

@ -1,6 +1,6 @@
from flask import Flask
from flask_bootstrap import Bootstrap
from app.view import user,uitest,utils,apinew
from app.view import user,uitest,utils,apinew,minder,minderfiles
app = Flask(__name__)
app.config.from_object('config')
@ -9,4 +9,6 @@ app.register_blueprint(user.mod)
app.register_blueprint(uitest.mod)
app.register_blueprint(utils.mod)
app.register_blueprint(apinew.mod)
app.register_blueprint(minder.mod)
app.register_blueprint(minderfiles.mod)
from app import views

View File

@ -0,0 +1,126 @@
from app import useDB,log
import string
class test_minder_manage:
def __init__(self):
self.status = 0
self.name = ''
def new_minder(self,module,name,description, content='{}'):
import random, time
batchId = str(random.randint(10000, 99999)) + str(time.time())
content = str(content).replace('"','\\"')
sql = string.Template('insert into test_minder (module,name,description,content,batchId) values ("$module","$name","$description","$content","$batchId");')
sql = sql.substitute(name = name, module = module, description=description, content=content,batchId=batchId)
print(sql)
useDB.useDB().insert(sql)
minders = test_minder_manage().show_test_minders(conditionList=['batchId'],
valueList=[batchId],
rows=1)
result = {}
if len(minders):
result['id'] = minders[0]['id']
result['code']=1
else:
result['code']=0
result['id'] =''
return result
def copy_test_minder(self,id):
# module, name, steps, description, isPublic
searchresult = self.show_test_minders(['id'],[id],1)
result ={'code':0}
if len(searchresult):
searchresult=searchresult[0]
result = self.new_minder(name = searchresult['name'], module = searchresult['module'], description=searchresult['description'], content=searchresult['content'])
return result
def update_test_minder(self,id,fieldlist,valueList):
update_value = '%s = "%s"' %(fieldlist[0],str(valueList[0]).replace('"','\\"'))
for i in range(1,len(fieldlist)):
print(fieldlist[i])
# if fieldlist[i]=='content':
update_value += ', %s = "%s"' %(fieldlist[i],str(valueList[i]).replace('"','\\"'))
# else:
# update_value += ', %s = "%s"' %(fieldlist[i],valueList[i])
sql = string.Template('update test_minder set $field where id = "$id";')
sql = sql.substitute(field = update_value, id = id)
useDB.useDB().insert(sql)
def show_test_minders(self,conditionList, valueList, rows):
fieldlist = ['id', 'module', 'name', 'description','content']
search_value = fieldlist[0]
for i in range(1,len(fieldlist)):
search_value = search_value + ','+fieldlist[i]
condition = ''
for i in range(len(conditionList)):
if valueList[i] !='':
condition += ' and %s = "%s"' %(conditionList[i],valueList[i])
results = []
sql = 'select ' + search_value + ' from test_minder where status = 1 ' + str(condition) + ' order by id desc limit '+ str(rows)+';'
print(sql)
cases = useDB.useDB().search(sql)
log.log().logger.info('cases : %s'%cases)
for i in range(len(cases)):
result = {}
result['id'] = cases[i][0]
result['module'] = cases[i][1]
result['name'] = cases[i][2]
result['description'] = cases[i][3]
result['content'] = cases[i][4]
results.append(result)
return results
def show_test_cases_unattach(self,test_suite_id,conditionList, valueList, fieldlist,rows):
fieldlist = ['id', 'module', 'name', 'steps', 'description','isPublicFunction']
search_value = fieldlist[0]
for i in range(1,len(fieldlist)):
search_value = search_value + ','+fieldlist[i]
results = []
log.log().logger.info('%s, %s, %s, %s, %s' %(test_suite_id,conditionList, valueList, fieldlist,rows))
condition = ''
for i in range(len(conditionList)):
if i == 0:
if conditionList[i]=='module':
log.log().logger.info(valueList[i])
moduleList = ''
for j in range(len(valueList[i])):
if j :
moduleList += ','
moduleList += '"'+valueList[i][j]+'"'
condition += str(conditionList[i]) + ' in (' + str(moduleList) + ')'
else:
condition += str(conditionList[i]) +' like "%'+str(valueList[i])+'%"'
else:
if conditionList[i] == 'module':
log.log().logger.info(valueList[i])
moduleList = ''
for j in range(len(valueList[i])):
if j :
moduleList += ','
moduleList += '"'+valueList[i][j]+'"'
condition += ' and ' + str(conditionList[i]) + ' in (' + str(moduleList) + ')'
else:
condition += ' and '+str(conditionList[i]) +' like "%'+str(valueList[i])+'%"'
if condition !='':
condition += ' and '
sql = 'select ' + search_value + ' from test_case where status = 1 and isPublicFunction=0 and '+ str(condition) +' id not in (select distinct test_case_id from test_batch where test_suite_id = '+test_suite_id+' ) order by module desc;'
cases = useDB.useDB().search(sql)
log.log().logger.info('cases : %s'%cases)
for i in range(len(cases)):
result = {}
result['id'] = cases[i][0]
result['module'] = cases[i][1]
result['name'] = cases[i][2]
result['steps'] = cases[i][3]
result['description'] = cases[i][4]
result['isPublic'] = cases[i][5]
results.append(result)
return results

View File

@ -0,0 +1,5 @@
{
"directory": "bower_components",
"allow_root": true,
"registry": "https://registry.bower.io"
}

8
app/static/minder/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.idea
.DS_Store
bower_components/
node_modules/
dist/
ui/templates.js
.tmp/
upload/

103
app/static/minder/.jscsrc Normal file
View File

@ -0,0 +1,103 @@
/**
* FEX Style Guide (Javascript)
*
* TODO:
*
* 1. 找不到选项:每行只允许一个语句
* 2. 找不到选项:块状代码需要用大括号括起来
*/
{
// 缩进「MUST」使用 4 个空格
"validateIndentation": 4,
// 大括号块状代码前「MUST」使用空格
"requireSpaceBeforeBlockStatements": true,
// 下列关键字「MUST」使用空格
"requireSpaceAfterKeywords": ["if", "else", "for", "while",
"do", "try", "catch", "finally"
],
// `,` 和 `;` 前面不允许「MUST NOT」使用空格。
"requireLeftStickedOperators": [",", ";"],
// 二元运算符前后「MUST」使用空格
"requireSpaceBeforeBinaryOperators": [
"+",
"-",
"*",
"/",
"=",
"==",
"===",
"!=",
"!==",
"|",
"||",
"&",
"&&"
],
"requireSpaceAfterBinaryOperators": [
"+",
"-",
"*",
"/",
"=",
"==",
"===",
"!=",
"!==",
"|",
"||",
"&",
"&&",
":"
],
// 一元运算符与操作对象间「MUST NOT」使用空格
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
// 函数参数小括号前「MUST NOT」使用空格
"disallowSpacesInFunctionExpression": {
"beforeOpeningRoundBrace": true
},
// 小括号里面「MUST NOT」使用空格
"disallowSpacesInsideParentheses": true,
// 行尾「MUST NOT」使用空格
"disallowTrailingWhitespace": true,
// 每行「MUST NOT」超过 120 个字符
"maximumLineLength": 120,
// 一下操作符「MUST NOT」放在一行的最前面需要放在上一行的后面
"requireOperatorBeforeLineBreak": [
"?",
"+",
"-",
"/",
"*",
"=",
"==",
"===",
"!=",
"!==",
">",
">=",
"<",
"<=",
",",
";",
"&&",
"&",
"||",
"|"
],
// 字符串统一「MUST」使用单引号
"validateQuoteMarks": "'",
// 「MUST NOT」使用多行字符串
"disallowMultipleLineStrings": true
}

View File

@ -0,0 +1,16 @@
{
"undef" : true,
"unused" : false,
"strict" : false,
"curly" : false,
"newcap" : true,
"trailing" : true,
"white": false,
"quotmark": false,
"browser": true,
"boss": true,
"indent": 4,
"predef" : [
"define"
]
}

View File

@ -0,0 +1,202 @@
/* global require, module */
var path = require('path');
module.exports = function(grunt) {
'use strict';
// Load grunt tasks automatically
require('load-grunt-tasks')(grunt);
grunt.loadNpmTasks('grunt-browser-sync');
grunt.loadNpmTasks('grunt-contrib-watch');
var pkg = grunt.file.readJSON('package.json');
var appConfig = {
app: require('./bower.json').appPath || 'app',
dist: 'dist'
};
var banner = '/*!\n' +
' * ====================================================\n' +
' * <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
'<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' +
' * GitHub: <%= pkg.repository.url %> \n' +
' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n' +
' * ====================================================\n' +
' */\n\n';
var expose = '\nuse(\'expose-editor\');\n';
// Project configuration.
grunt.initConfig({
// Metadata.
pkg: pkg,
yeoman: appConfig,
clean: {
last: [
'.tmp',
'dist/*.js',
'dist/*.css',
'dist/*.css.map'
],
clstmp: ['.tmp']
},
// resolve dependence
dependence: {
options: {
base: 'src',
entrance: 'expose-editor'
},
merge: {
files: [{
src: [
'src/**/*.js'
],
dest: '.tmp/scripts/kityminder.editor.logic.js'
}]
}
},
// browser sync for dev
browserSync: {
bsFiles: {
dist: 'dist/css/*.css',
src: 'src/**'
},
options: {
server: {
baseDir: './',
index: 'index.html',
watchTask: true
}
}
},
// concat
concat: {
closure: {
options: {
banner: banner + '(function () {\n',
footer: expose + '})();'
},
files: {
'dist/kityminder.editor.js': [
'.tmp/scripts/kityminder.editor.logic.js',
'.tmp/scripts/kityminder.app.annotated.js',
'.tmp/scripts/templates.annotated.js',
'.tmp/scripts/service/*.js',
'.tmp/scripts/filter/*.js',
'.tmp/scripts/dialog/**/*.js',
'.tmp/scripts/directive/**/*.js'
]
}
}
},
uglify: {
options: {
banner: banner
},
minimize: {
files: [{
src: 'dist/kityminder.editor.js',
dest: 'dist/kityminder.editor.min.js'
}]
}
},
less: {
compile: {
options: {
sourceMap: true,
sourceMapURL: 'kityminder.editor.css.map',
sourceMapFilename: 'dist/kityminder.editor.css.map'
},
files: [{
dest: 'dist/kityminder.editor.css',
src: 'less/editor.less'
}]
}
},
cssmin: {
dist: {
files: {
'dist/kityminder.editor.min.css': 'dist/kityminder.editor.css'
}
}
},
ngtemplates: {
kityminderEditor: {
src: ['ui/directive/**/*.html', 'ui/dialog/**/*.html'],
dest: 'ui/templates.js',
options: {
htmlmin: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
removeComments: true
}
}
}
},
// Automatically inject Bower components into the app
wiredep: {
dev: {
src: ['index.html'],
devDependencies: true
},
dist: {
src: ['dist/index.html']
}
},
// Copies remaining files to places other tasks can use
copy: {
dist: {
files: [{
expand: true,
cwd: 'ui',
src: 'images/*',
dest: 'dist'
}]
}
},
// ng-annotate tries to make the code safe for minification automatically
// by using the Angular long form for dependency injection.
ngAnnotate: {
dist: {
files: [{
expand: true,
cwd: 'ui/',
src: '**/*.js',
ext: '.annotated.js',
extDot: 'last',
dest: '.tmp/scripts/'
}]
}
}
});
// Build task(s).
grunt.registerTask('build', ['clean:last',
//'wiredep:dist',
'ngtemplates', 'dependence', 'ngAnnotate', 'concat', 'uglify', 'less', 'cssmin', 'copy', 'clean:clstmp']);
grunt.registerTask('dev', ['clean:last',
//'wiredep:dev',
'ngtemplates', 'dependence', 'ngAnnotate', 'concat', 'uglify', 'less', 'cssmin', 'copy', 'clean:clstmp', 'browserSync', 'watch']);
};

340
app/static/minder/LICENSE Normal file
View File

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@ -0,0 +1,87 @@
KityMinder Editor
==========
## 简介
KityMinder Editor 是一款强大、简洁、体验优秀的脑图编辑工具,适合用于编辑树/图/网等结构的数据。
编辑器由百度 [FEX](https://github.com/fex-team) 基于 [kityminder-core](https://github.com/fex-team/kityminder-core) 搭建,并且在[百度脑图](http://naotu.baidu.com)中使用。
他们的区别与联系如下:
![KityMinder 联系](relations.png "KityMinder 联系")
- [kityminder-core](https://github.com/fex-team/kityminder-core) 是 kityminder 的核心部分,基于百度 [FEX](https://github.com/fex-team) 开发的矢量图形库 [kity](https://github.com/fex-team/kity)。包含了脑图数据的可视化展现,简单编辑功能等所有底层支持。
- [kityminder-editor](https://github.com/fex-team/kityminder-editor) 基于 kityminder-core 搭建,依赖于 AngularJS包含 UI 和热盒 [hotbox](https://github.com/fex-team/hotbox) 等方便用户输入的功能,简单来说,就是一款编辑器。
- [百度脑图](http://naotu.baidu.com) 基于 kityminder-editor加入了第三方格式导入导出 (FreeMind, XMind, MindManager) 、文件储存、用户认证、文件分享、历史版本等业务逻辑。
## 功能
- 基本操作:文本编辑,节点折叠、插入、删除、排序、归纳、复制、剪切、粘贴等
- 样式控制:字体、加粗、斜体、颜色、样式拷贝、样式粘贴等
- 图标:优先级、进度等
- 历史:撤销/重做
- 标签:多标签贴入
- 备注:支持 Markdown 格式备注
- 图片:支持本地/网络/搜索图片插入
- 超链接:支持 HTTP/HTTPS/MAIL/FTP 链接插入
- 布局:支持多种布局切换
- 主题:支持多种主题切换
- 数据导入导出:支持多种格式的导入,多种格式(包括图片)的导出
- 缩略图:支持缩略图查看/导航
## 开发使用
根目录下的 `index.html` 为开发环境,`dist` 目录下的 `index.html` 使用打包好的代码,适用于线上环境。
1. 安装 [nodejs](http://nodejs.org) 和 [npm](https://docs.npmjs.com/getting-started/installing-node)
2. 初始化:切到 kityminder-editor 根目录下运行 `npm run init`
3. 在 kityminder-editor 根目录下运行 `grunt dev` 即可启动项目
4. 你可以基于根目录的 `index.html` 开发,或者查看 `dist` 目录下用于生产环境的 `index.html`Enjoy it!
另外kityminder-editor 还提供了 bower 包,方便开发者直接使用。你可以在需要用到 kityminder-editor 的工程目录下
运行 `bower install kityminder-editor`,接着手动引入 kityminder-editor 所依赖的 css 和 js 文件,具体文件见
`dist` 目录下的 `index.html`,推荐使用 npm 包 [wireDep](https://www.npmjs.com/package/wiredep) 自动进行,
可参考根目录下 `Gruntfile.js`
## 构建
运行 `grunt build`,完成后 `dist` 目录里就是可用运行的 kityminder-editor, 双击 `index.html` 即可打开运行示例
## 初始化配置
用户可以根据需要,配置 `kityminder-editor`, 具体使用方法如下:
```
angular.module('kityminderDemo', ['kityminderEditor'])
.config(function (configProvider) {
configProvider.set('imageUpload', 'path/to/image/upload/handler');
});
```
## 数据导入导出
由于 kityminder-editor 是基于 kityminder-core 搭建的,而 kityminder-core 内置了五种常见
格式的导入或导出,在创建编辑器实例之后,可以使用四个接口进行数据的导入导出。
* `editor.minder.exportJson()` - 导出脑图数据为 JSON 对象
* `editor.minder.importJson(json)` - 导入 JSON 对象为当前脑图数据
* `editor.minder.exportData(protocol, option)` - 导出脑图数据为指定的数据格式,返回一个 Promise其值为导出的结果
* `editor.minder.importData(protocol, data, option)` - 导入指定格式的数据为脑图数据,返回一个 Promise其值为转换之后的脑图 Json 数据
目前支持的数据格式包括:
* `json` - JSON 字符串,支持导入和导出
* `text` - 纯文本格式,支持导入和导出
* `markdown` - Markdown 格式,支持导入和导出
* `svg` - SVG 矢量格式,仅支持导出
* `png` - PNG 位图格式,仅支持导出
更多格式的支持,可以加载 [kityminder-protocol](https://github.com/fex-team/kityminder-protocol) 来扩展第三方格式支持。
数据格式的具体信息,可参考 [kityminder-core-wiki 的中的说明](https://github.com/fex-team/kityminder-core/wiki)。
## 联系我们
问题和建议反馈:
[Github issues](https://github.com/fex-team/kityminder-editor/issues)
邮件组kity@baidu.com
QQ 讨论群475962105

View File

@ -0,0 +1,67 @@
{
"name": "kityminder-editor",
"version": "1.0.61",
"authors": [
"fex<fex@baidu.com>"
],
"description": "Kity Minder Editor",
"main": [
"dist/kityminder.editor.js",
"dist/kityminder.editor.css"
],
"keywords": [
"kityminder",
"fex",
"ui",
"javascript",
"html5",
"svg"
],
"license": "BSD",
"homepage": "https://github.com/fex-team/kityminder-editor",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"less",
"ui",
"src",
"Gruntfile.js",
"package.json"
],
"devDependencies": {
"seajs": "~2.3.0"
},
"dependencies": {
"bootstrap": "~3.3.4",
"angular": "~1.3.15",
"angular-bootstrap": "~0.12.1",
"angular-ui-codemirror": "~0.2.3",
"codemirror": "~4.8.0",
"marked": "git://github.com/chjj/marked.git#master",
"hotbox": "~1.0.2",
"color-picker": "~1.0.2",
"kity": "^2.0.5",
"json-diff": "*"
},
"overrides": {
"codemirror": {
"main": [
"lib/codemirror.js",
"lib/codemirror.css",
"mode/xml/xml.js",
"mode/javascript/javascript.js",
"mode/css/css.js",
"mode/htmlmixed/htmlmixed.js",
"mode/markdown/markdown.js",
"addon/mode/overlay.js",
"mode/gfm/gfm.js"
]
}
},
"resolutions": {
"angular": "~1.3.8"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -0,0 +1,127 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>KityMinder Editor - Powered By FEX</title>
<link href="favicon.ico" type="image/x-icon" rel="shortcut icon">
<!-- bower:css -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="bower_components/codemirror/lib/codemirror.css" />
<link rel="stylesheet" href="bower_components/hotbox/hotbox.css" />
<link rel="stylesheet" href="node_modules/kityminder-core/dist/kityminder.core.css" />
<link rel="stylesheet" href="bower_components/color-picker/dist/color-picker.css" />
<!-- endbower -->
<link rel="stylesheet" href="dist/kityminder.editor.css">
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
h1.editor-title {
background: #393F4F;
color: white;
margin: 0;
height: 40px;
font-size: 14px;
line-height: 40px;
font-family: 'Hiragino Sans GB', 'Arial', 'Microsoft Yahei';
font-weight: normal;
padding: 0 20px;
}
</style>
</head>
<body ng-app="kityminderDemo" ng-controller="MainController">
<div style="height:60px">
<!-- <h1 class="editor-title">KityMinder Editor - Powered By FEX</h1> -->
<a> 脑图编辑</a>
<button id="exportData" onclick="exportData()">导出</button>
<button id="exportData" onclick="inportData()">导入</button>
</div>
<div>
<kityminder-editor on-init="initEditor(editor, minder)"></kityminder-editor>
</div>
</body>
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
<script src="bower_components/codemirror/lib/codemirror.js"></script>
<script src="bower_components/codemirror/mode/xml/xml.js"></script>
<script src="bower_components/codemirror/mode/javascript/javascript.js"></script>
<script src="bower_components/codemirror/mode/css/css.js"></script>
<script src="bower_components/codemirror/mode/htmlmixed/htmlmixed.js"></script>
<script src="bower_components/codemirror/mode/markdown/markdown.js"></script>
<script src="bower_components/codemirror/addon/mode/overlay.js"></script>
<script src="bower_components/codemirror/mode/gfm/gfm.js"></script>
<script src="bower_components/angular-ui-codemirror/ui-codemirror.js"></script>
<script src="bower_components/marked/lib/marked.js"></script>
<script src="node_modules/kity/dist/kity.js"></script>
<script src="bower_components/hotbox/hotbox.js"></script>
<script src="bower_components/json-diff/json-diff.js"></script>
<script src="node_modules/kityminder-core/dist/kityminder.core.js"></script>
<script src="bower_components/color-picker/dist/color-picker.js"></script>
<script src="bower_components/seajs/dist/sea.js"></script>
<!-- endbower -->
<script src="ui/kityminder.app.js"></script>
<script src="ui/service/commandBinder.service.js"></script>
<script src="ui/service/config.service.js"></script>
<script src="ui/service/memory.service.js"></script>
<script src="ui/service/lang.zh-cn.service.js"></script>
<script src="ui/service/valueTransfer.service.js"></script>
<script src="ui/service/minder.service.js"></script>
<script src="ui/service/resource.service.js"></script>
<script src="ui/service/revokeDialog.service.js"></script>
<script src="ui/service/server.service.js"></script>
<script src="ui/filter/lang.filter.js"></script>
<script src="ui/dialog/hyperlink/hyperlink.ctrl.js"></script>
<script src="ui/dialog/image/image.ctrl.js"></script>
<script src="ui/dialog/imExportNode/imExportNode.ctrl.js"></script>
<script src="ui/directive/topTab/topTab.directive.js"></script>
<script src="ui/directive/undoRedo/undoRedo.directive.js"></script>
<script src="ui/directive/appendNode/appendNode.directive.js"></script>
<script src="ui/directive/arrange/arrange.directive.js"></script>
<script src="ui/directive/operation/operation.directive.js"></script>
<script src="ui/directive/hyperLink/hyperLink.directive.js"></script>
<script src="ui/directive/imageBtn/imageBtn.directive.js"></script>
<script src="ui/directive/noteBtn/noteBtn.directive.js"></script>
<script src="ui/directive/resourceEditor/resourceEditor.directive.js"></script>
<script src="ui/directive/priorityEditor/priorityEditor.directive.js"></script>
<script src="ui/directive/progressEditor/progressEditor.directive.js"></script>
<script src="ui/directive/noteEditor/noteEditor.directive.js"></script>
<script src="ui/directive/notePreviewer/notePreviewer.directive.js"></script>
<script src="ui/directive/kityminderEditor/kityminderEditor.directive.js"></script>
<script src="ui/directive/templateList/templateList.directive.js"></script>
<script src="ui/directive/themeList/themeList.directive.js"></script>
<script src="ui/directive/layout/layout.directive.js"></script>
<script src="ui/directive/styleOperator/styleOperator.directive.js"></script>
<script src="ui/directive/fontOperator/fontOperator.directive.js"></script>
<script src="ui/directive/expandLevel/expandLevel.directive.js"></script>
<script src="ui/directive/selectAll/selectAll.directive.js"></script>
<script src="ui/directive/colorPanel/colorPanel.directive.js"></script>
<script src="ui/directive/navigator/navigator.directive.js"></script>
<script src="ui/directive/searchBox/searchBox.directive.js"></script>
<script src="ui/directive/searchBtn/searchBtn.directive.js"></script>
<script src="src/data.js"></script>
<script>
angular.module('kityminderDemo', ['kityminderEditor'])
.controller('MainController', function($scope) {
$scope.initEditor = function(editor, minder) {
window.editor = editor;
window.minder = minder;
};
});
</script>
</html>

View File

@ -0,0 +1,127 @@
.nav-bar {
position: absolute;
width: 35px;
height: 240px;
padding: 5px 0;
left: 10px;
bottom: 10px;
background: #fc8383;
color: #fff;
border-radius: 4px;
z-index: 10;
box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.2);
transition: -webkit-transform .7s 0.1s ease;
transition: transform .7s 0.1s ease;
.nav-btn {
width: 35px;
height: 24px;
line-height: 24px;
text-align: center;
.icon {
background: url(images/icons.png);
width: 20px;
height: 20px;
margin: 2px auto;
display: block;
}
&.active {
background-color: #5A6378;
}
}
.zoom-in .icon {
background-position: 0 -730px;
}
.zoom-out .icon {
background-position: 0 -750px;
}
.hand .icon {
background-position: 0 -770px;
width: 25px;
height: 25px;
margin: 0 auto;
}
.camera .icon {
background-position: 0 -870px;
width: 25px;
height: 25px;
margin: 0 auto;
}
.nav-trigger .icon {
background-position: 0 -845px;
width: 25px;
height: 25px;
margin: 0 auto;
}
.zoom-pan {
width: 2px;
height: 70px;
box-shadow: 0 1px #E50000;
position: relative;
background: white;
margin: 3px auto;
overflow: visible;
.origin {
position: absolute;
width: 20px;
height: 8px;
left: -9px;
margin-top: -4px;
background: transparent;
&:after {
content: ' ';
display: block;
width: 6px;
height: 2px;
background: white;
left: 7px;
top: 3px;
position: absolute;
}
}
.indicator {
position: absolute;
width: 8px;
height: 8px;
left: -3px;
background: white;
border-radius: 100%;
margin-top: -4px;
}
}
}
.nav-previewer {
background: #fff;
width: 140px;
height: 120px;
position: absolute;
left: 45px;
bottom: 30px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
border-radius: 0 2px 2px 0;
padding: 1px;
z-index: 9;
cursor: crosshair;
transition: -webkit-transform .7s 0.1s ease;
transition: transform .7s 0.1s ease;
&.grab {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
}

View File

@ -0,0 +1,24 @@
.tool-group {
padding: 0;
&[disabled] {
opacity: 0.5;
}
.tool-group-item {
display: inline-block;
border-radius: 4px;
.tool-group-icon {
width: 20px;
height: 20px;
padding: 2px;
margin: 1px;
}
&:hover {background-color: @button-hover;}
&:active {background-color: @button-active;}
&.active {background-color: @button-active;}
}
}

View File

@ -0,0 +1,7 @@
@button-hover: hsl(222, 55%, 96%);
@button-active: hsl(222, 55%, 85%);
@button-pressed: hsl(222, 55%, 90%);
@tool-hover: #eff3fa;
@tool-active: #c4d0ee;
@tool-selected: #87a9da;

View File

@ -0,0 +1,135 @@
.km-editor {
overflow: hidden;
z-index: 2;
}
.km-editor > .mask {
display: block;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: transparent;
}
.km-editor > .receiver {
position: absolute;
background: white;
outline: none;
box-shadow: 0 0 20px fadeout(black, 50%);
left: 0;
top: 0;
padding: 3px 5px;
margin-left: -3px;
margin-top: -5px;
max-width: 300px;
width: auto;
overflow: hidden;
font-size: 14px;
line-height: 1.4em;
min-height: 1.4em;
box-sizing: border-box;
overflow: hidden;
word-break: break-all;
word-wrap: break-word;
border: none;
-webkit-user-select: text;
pointer-events: none;
opacity: 0;
z-index: -1000;
&.debug {
opacity: 1;
outline: 1px solid green;
background: none;
z-index: 0;
}
&.input {
pointer-events: all;
opacity: 1;
z-index: 999;
background: white;
outline: none;
}
}
div.minder-editor-container {
position: absolute;
top: 40px;
bottom: 0;
left: 0;
right: 0;
font-family: Arial, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
}
.minder-editor {
position: absolute;
top: 92px;
left: 0;
right: 0;
bottom: 0;
}
.minder-viewer {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.control-panel {
position: absolute;
top: 0;
right: 0;
width: 250px;
bottom: 0;
border-left: 1px solid #CCC;
}
.minder-divider {
position: absolute;
top: 0;
right: 250px;
bottom: 0;
width: 2px;
background-color: rgb(251, 251, 251);
cursor: ew-resize;
}
// @override bootstrap
.panel-body {
padding: 10px;
}
@import (less) "_vars.less";
@import (less) "imageDialog.less";
@import (less) "topTab/topTab.less";
@import (less) "topTab/idea/undoRedo.less";
@import (less) "topTab/idea/appendNode.less";
@import (less) "topTab/idea/arrange.less";
@import (less) "topTab/idea/operation.less";
@import (less) "topTab/idea/hyperlink.less";
@import (less) "topTab/idea/image.less";
@import (less) "topTab/idea/note.less";
@import (less) "topTab/idea/noteEditor.less";
@import (less) "topTab/idea/priority.less";
@import (less) "topTab/idea/progress.less";
@import (less) "topTab/idea/resource.less";
@import (less) "topTab/appearance/templatePanel.less";
@import (less) "topTab/appearance/themePanel.less";
@import (less) "topTab/appearance/layout.less";
@import (less) "topTab/appearance/styleOperator.less";
@import (less) "topTab/appearance/fontOperator.less";
@import (less) "topTab/appearance/colorPanel.less";
@import (less) "topTab/view/expand.less";
@import (less) "topTab/view/select.less";
@import (less) "topTab/view/search.less";
@import (less) "topTab/searchBox.less";
@import (less) "_tool_group.less";
@import (less) "_navigator.less";

View File

@ -0,0 +1,8 @@
.upload-image {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}

View File

@ -0,0 +1,77 @@
.bg-color-wrap {
display: inline-block;
width: 30px;
height: 22px;
margin: 3px 3px 0 0;
border: 1px #efefef solid;
vertical-align: middle;
font-size: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
&[disabled] {
opacity: 0.5;
}
.quick-bg-color {
display: inline-block;
width: 20px;
height: 16px;
font-size: 14px;
line-height: 16px;
vertical-align: top;
text-align: center;
cursor: default;
color: #000;
background: url(images/icons.png) no-repeat center -1260px;
&:hover {
background-color: @tool-hover;
}
&:active {
background-color: @tool-active;
}
&[disabled] {
opacity: 0.5;
}
}
.bg-color-preview {
display: inline-block;
width: 12px;
height: 2px;
margin: 0 4px 0;
background-color: #fff;
&[disabled] {
opacity: 0.5;
}
}
}
.bg-color {
display: inline-block;
width: 8px;
height: 16px;
&:hover {
background-color: @tool-hover;
}
&:active {
background-color: @tool-active;
}
&[disabled] {
opacity: 0.5;
}
.caret {
margin-left: -2px;
margin-top: 7px;
}
}

View File

@ -0,0 +1,160 @@
.font-operator {
width: 170px;
display: inline-block;
vertical-align: middle;
font-size: 12px;
padding: 0 5px;
.font-size-list {
display: inline-block;
border: 1px solid #eee;
padding: 2px 4px;
}
.font-family-list {
display: inline-block;
border: 1px solid #eee;
padding: 2px 4px;
}
}
.current-font-item a {
text-decoration: none;
display: inline-block;
}
.current-font-family {
width: 75px;
height: 18px;
overflow: hidden;
vertical-align: bottom;
}
.current-font-size {
width: 32px;
height: 18px;
overflow: hidden;
vertical-align: bottom;
}
.current-font-item[disabled] {
opacity: 0.5;
}
.font-item {
line-height: 1em;
text-align: left;
}
.font-item-selected {
background-color: @tool-selected;
}
.font-bold, .font-italics {
display: inline-block;
background: url(images/icons.png) no-repeat;
cursor: pointer;
margin: 0 3px;
&:hover {
background-color: @tool-hover;
}
&:active {
background-color: @tool-active;
}
&[disabled] {
opacity: 0.5;
}
}
.font-bold {
background-position: 0 -240px;
}
.font-italics {
background-position: 0 -260px;
}
.font-bold-selected, .font-italics-selected {
background-color: @tool-selected;
}
.font-color-wrap {
display: inline-block;
width: 30px;
height: 22px;
margin: 3px 3px 0 0;
border: 1px #efefef solid;
vertical-align: middle;
font-size: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
&[disabled] {
opacity: 0.5;
}
.quick-font-color {
display: inline-block;
width: 20px;
height: 16px;
font-size: 14px;
line-height: 16px;
vertical-align: top;
text-align: center;
cursor: default;
color: #000;
&:hover {
background-color: @tool-hover;
}
&:active {
background-color: @tool-active;
}
&[disabled] {
opacity: 0.5;
}
}
.font-color-preview {
display: inline-block;
width: 12px;
height: 2px;
margin: 0 4px 0;
background-color: #000;
&[disabled] {
opacity: 0.5;
}
}
}
.font-color {
display: inline-block;
width: 8px;
height: 16px;
&:hover {
background-color: @tool-hover;
}
&:active {
background-color: @tool-active;
}
&[disabled] {
opacity: 0.5;
}
.caret {
margin-left: -2px;
margin-top: 7px;
}
}

View File

@ -0,0 +1,65 @@
.readjust-layout {
display: inline-block;
vertical-align: middle;
padding: 0 10px 0 5px;
border-right: 1px dashed #eee;
}
.btn-icon {
width: 25px;
height: 25px;
margin-left: 12px;
display: block;
}
.btn-label {
font-size: 12px;
}
.btn-wrap {
width: 50px;
height: 42px;
cursor: pointer;
display: inline-block;
text-decoration: none;
&[disabled] span {
opacity: 0.5;
}
&[disabled] {
cursor: default;
}
&[disabled]:hover {
background-color: transparent;
}
&[disabled]:active {
background-color: transparent;
}
&:link {
text-decoration: none;
}
&:visited {
text-decoration: none;
}
&:hover {
background-color: @tool-hover;
text-decoration: none;
}
&:active {
background-color: @tool-active;
}
}
.reset-layout-icon {
background: url(images/icons.png) no-repeat;
background-position: 0 -150px;
}

View File

@ -0,0 +1,83 @@
.style-operator {
display: inline-block;
vertical-align: middle;
padding: 0 5px;
border-right: 1px dashed #eee;
.clear-style {
vertical-align: middle;
}
}
.clear-style-icon {
background: url(images/icons.png) no-repeat;
background-position: 0 -175px;;
}
.s-btn-group-vertical {
display: inline-block;
vertical-align: middle;
}
.s-btn-icon {
width: 20px;
height: 20px;
margin-right: 3px;
display: inline-block;
vertical-align: middle;
}
.s-btn-label {
font-size: 12px;
vertical-align: middle;
display: inline-block;
}
.s-btn-wrap {
// margin-bottom: 2px;
padding: 0 5px 0 3px;
display: inline-block;
text-decoration: none;
font-size: 0;
&[disabled] span {
opacity: 0.5;
}
&[disabled] {
cursor: default;
}
&[disabled]:hover {
background-color: transparent;
}
&[disabled]:active {
background-color: transparent;
}
&:hover {
background-color: @tool-hover;
text-decoration: none;
}
&:active {
background-color: @tool-active;
}
}
.copy-style-icon {
background: url(images/icons.png) no-repeat;
background-position: 0 -200px;
}
.paste-style-wrap {
display: block;
}
.paste-style-icon {
background: url(images/icons.png) no-repeat;
background-position: 0 -220px;
}

View File

@ -0,0 +1,71 @@
.temp-panel {
margin: 5px 5px 5px 10px;
border-right: 1px dashed #eee;
display: inline-block;
vertical-align: middle;
}
.temp-list {
min-width: 124px;
}
.temp-item-wrap {
width: 50px;
height: 40px;
padding: 0 2px;
margin: 5px;
display: inline-block;
}
.temp-item {
display: inline-block;
width: 50px;
height: 40px;
background-image: url(images/template.png);
background-repeat: no-repeat;
&.default {
background-position: 0 0;
}
&.structure {
background-position: -50px 0;
}
&.filetree {
background-position: -100px 0;
}
&.right {
background-position: -150px 0;
}
&.fish-bone {
background-position: -200px 0;
}
&.tianpan {
background-position: -250px 0;
}
}
.current-temp-item {
width: 74px;
padding: 0 0 0 5px;
border: 1px solid #fff;
&:hover {
background-color: @tool-hover;
}
&[disabled] {
opacity: 0.5;
}
.caret {
margin-left: 5px;
}
}
.temp-item-selected {
background-color: @tool-selected;
}

View File

@ -0,0 +1,53 @@
.theme-panel {
height: 42px;
margin: 5px;
padding: 0 5px 0 0;
border-right: 1px dashed #eee;
display: inline-block;
vertical-align: middle;
}
.theme-list {
min-width: 162px;
}
div a.theme-item {
display: inline-block;
width: 70px;
height: 30px;
text-align: center;
line-height: 30px;
padding: 0 5px;
font-size: 12px;
cursor: pointer;
text-decoration: none;
color: #000;
}
.theme-item-selected {
width: 100px;
padding: 6px 7px;
border: 1px solid #fff;
&:hover {
background-color: @tool-hover;
}
.caret {
margin-left: 5px;
}
&[disabled] {
opacity: 0.5;
}
}
.theme-item-wrap {
display: inline-block;
width: 80px;
height: 40px;
padding: 5px;
}
.theme-item-wrap:hover {
background-color: #eff3fa;
}

View File

@ -0,0 +1,21 @@
.append-group {
width: 212px;
}
.append-child-node {
.km-btn-icon {
background-position: 0 0;
}
}
.append-sibling-node {
.km-btn-icon {
background-position: 0 -20px;
}
}
.append-parent-node {
.km-btn-icon {
background-position: 0 -40px;
}
}

View File

@ -0,0 +1,15 @@
.arrange-group {
width: 64px;
}
.arrange-up {
.km-btn-icon {
background-position: 0 -280px;
}
}
.arrange-down {
.km-btn-icon {
background-position: 0 -300px;
}
}

View File

@ -0,0 +1,43 @@
.btn-group-vertical {
vertical-align: middle;
margin: 5px;
.hyperlink, .hyperlink-caption {
width: 40px;
margin: 0;
padding: 0;
border: none!important;
border-radius: 0!important;
&:hover {
background-color: @tool-hover;
}
&:active {
background-color: @tool-active;
}
&.active {
box-shadow: none;
background-color: @tool-hover;
}
}
.hyperlink {
height: 25px;
background: url(images/icons.png) no-repeat center -100px;
}
.hyperlink-caption {
height: 20px;
.caption {
font-size: 12px;
}
}
}
//override bootstrap
.open > .dropdown-toggle.btn-default {
background-color: @tool-hover;
}

View File

@ -0,0 +1,129 @@
.btn-group-vertical {
.image-btn, .image-btn-caption {
width: 40px;
margin: 0;
padding: 0;
border: none!important;
border-radius: 0!important;
&:hover {
background-color: @tool-hover;
}
&:active {
background-color: @tool-active;
}
&.active {
box-shadow: none;
background-color: @tool-hover;
}
}
.image-btn {
height: 25px;
background: url(images/icons.png) no-repeat center -125px;
}
.image-btn-caption {
height: 20px;
.caption {
font-size: 12px;
}
}
}
.image-preview {
display: block;
max-width: 50%;
}
.modal-body {
.tab-pane {
font-size: inherit;
padding-top: 15px;
}
}
.search-result {
margin-top: 15px;
height: 370px;
overflow: hidden;
ul {
margin: 0;
padding: 0;
list-style: none;
clear: both;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
li {
list-style: none;
float: left;
display: block;
width: 130px;
height: 130px;
line-height: 130px;
margin: 6px;
padding: 0;
font-size: 12px;
position: relative;
vertical-align: top;
text-align: center;
overflow: hidden;
cursor: pointer;
border: 2px solid #fcfcfc;
&.selected {
border: 2px solid #fc8383;
}
img {
max-width: 126px;
max-height: 130px;
vertical-align: middle;
}
span {
display: block;
position: absolute;
bottom: 0;
height: 20px;
background: rgba(0, 0, 0, 0.5);
left: 0;
right: 0;
color: white;
line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
opacity: 0;
-webkit-transform: translate(0, 20px);
-ms-transform: translate(0, 20px);
transform: translate(0, 20px);
-webkit-transition: all .2s ease;
transition: all .2s ease;
}
}
li:hover span {
opacity: 1;
-webkit-transform: translate(0, 0);
-ms-transform: translate(0, 0);
transform: translate(0, 0);
}
}
}
// 覆盖 bootstrap 样式
@media (min-width: 768px){
.form-inline .form-control {
width: 422px;
}
}

View File

@ -0,0 +1,48 @@
.btn-group-vertical {
vertical-align: top;
margin: 5px;
&.note-btn-group {
border-right: 1px dashed #eee;
padding-right: 5px;
}
.note-btn, .note-btn-caption {
width: 40px;
margin: 0;
padding: 0;
border: none!important;
border-radius: 0!important;
&:hover {
background-color: @tool-hover;
}
&:active {
background-color: @tool-active;
}
&.active {
box-shadow: none;
background-color: @tool-hover;
}
}
.note-btn {
height: 25px;
background: url(images/icons.png) no-repeat center -1150px;
}
.note-btn-caption {
height: 20px;
.caption {
font-size: 12px;
}
}
}
//override bootstrap
.open > .dropdown-toggle.btn-default {
background-color: @tool-hover;
}

View File

@ -0,0 +1,168 @@
.gfm-render {
font-size: 12px;
-webkit-user-select: text;
color: #333;
line-height: 1.8em;
blockquote, ul, table, p, pre, hr {
margin: 1em 0;
cursor: text;
&:first-child:last-child {
margin: 0;
}
}
img {
max-width: 100%;
}
a {
color: blue;
&:hover {
color: red;
}
}
blockquote {
display: block;
border-left: 4px solid #E4AD91;
color: darken(#E4AD91, 10%);
padding-left: 10px;
font-style: italic;
margin-left: 2em;
}
ul, ol {
padding-left: 3em;
}
table {
width: 100%;
border-collapse: collapse;
th, td {
border: 1px solid #666;
padding: 2px 4px;
}
th {
background: rgba(45, 141, 234, 0.2);
}
tr:nth-child(even) td {
background: rgba(45, 141, 234, 0.03);
}
margin: 1em 0;
}
em {
color: red;
}
del {
color: #999;
}
pre {
background: rgba(45, 141, 234, 0.1);
padding: 5px;
border-radius: 5px;
word-break: break-all;
word-wrap: break-word;
}
code {
background: rgba(45, 141, 234, 0.1);
/* display: inline-block; */
padding: 0 5px;
border-radius: 3px;
}
pre code {
background: none;
}
hr {
border: none;
border-top: 1px solid #CCC;
}
.highlight {
background: yellow;
color: red;
}
}
.km-note {
width: 300px;
border-left: 1px solid #babfcd;
padding: 5px 10px;
background: white;
position: absolute;
top: 92px;
right: 0;
bottom: 0;
left: auto;
z-index: 3;
&.panel {
margin: 0;
padding: 0;
.panel-heading {
h3 {
display: inline-block;
}
.close-note-editor {
width: 15px;
height: 15px;
display: inline-block;
float: right;
&:hover {
cursor: pointer;
}
}
}
.panel-body {
padding: 0;
}
}
.CodeMirror {
position: absolute;
top: 41px;
bottom: 0;
height: auto;
cursor: text;
font-size: 14px;
line-height: 1.3em;
font-family: consolas;
}
}
.km-note-tips {
color: #ccc;
padding: 3px 8px;
}
#previewer-content {
position: absolute;
background: #FFD;
padding: 5px 15px;
border-radius: 5px;
max-width: 400px;
max-height: 200px;
overflow: auto;
z-index: 10;
box-shadow: 0 0 15px rgba(0, 0, 0, .5);
word-break: break-all;
.gfm-render;
}
#previewer-content.ng-hide {
display: block!important;
left: -99999px!important;
top: -99999px!important;
}
.panel-body {
padding: 10px;
}

View File

@ -0,0 +1,15 @@
.operation-group {
width: 64px;
}
.edit-node {
.km-btn-icon {
background-position: 0 -60px;
}
}
.remove-node {
.km-btn-icon {
background-position: 0 -80px;
}
}

View File

@ -0,0 +1,26 @@
.priority-sprite(@count) when (@count >= 0) {
.priority-sprite(@count - 1);
&.priority-@{count} {
background-position: 0 (-20px * (@count - 1));
}
}
.tab-content .km-priority {
vertical-align: middle;
font-size: inherit;
display: inline-block;
width: 140px;
margin: 5px;
border-right: 1px dashed #eee;
.km-priority-item {
margin: 0 1px;
padding: 1px;
.km-priority-icon {
.priority-sprite(9);
background: url(images/iconpriority.png) repeat-y;
background-color: transparent;
}
}
}

View File

@ -0,0 +1,26 @@
.progress-sprite(@count) when (@count >= 0) {
.progress-sprite(@count - 1);
&.progress-@{count} {
background-position: 0 (-20px * (@count - 1));
}
}
.tab-content .km-progress {
vertical-align: middle;
font-size: inherit;
display: inline-block;
width: 140px;
margin: 5px;
border-right: 1px dashed #eee;
.km-progress-item {
margin: 0 1px;
padding: 1px;
.km-progress-icon {
.progress-sprite(9);
background: url(images/iconprogress.png) repeat-y;
background-color: transparent;
}
}
}

View File

@ -0,0 +1,83 @@
.resource-editor {
vertical-align: middle;
display: inline-block;
margin: 5px;
.input-group, .km-resource {
font-size: 12px;
}
.input-group {
height: 20px;
width: 168px;
}
.resource-dropdown {
position: relative;
width: 168px;
border: 1px solid #ccc;
margin-top: -1px;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
.km-resource {
position: absolute;
width: 154px;
margin-bottom: 3px;
padding: 0;
list-style-type: none;
overflow: scroll;
max-height: 500px;
&.open {
z-index: 3;
background-color: #fff;
}
li {
display: inline-block;
padding: 1px 2px;
border-radius: 4px;
margin: 2px 3px;
&[disabled] {
opacity: 0.5;
}
}
}
.resource-caret {
display: block;
float: right;
vertical-align: middle;
width: 12px;
height: 24px;
padding: 8px 1px;
&:hover {background-color: @button-hover;}
&:active {background-color: @button-active;}
}
}
// 覆盖 bootstrap
input.form-control, .btn {
font-size: 12px;
}
input.form-control {
padding: 2px 4px;
height: 24px;
border-bottom-left-radius: 0;
}
.input-group-btn {
line-height: 24px;
.btn {
padding: 2px 4px;
height: 24px;
border-bottom-right-radius: 0;
}
}
}

View File

@ -0,0 +1,15 @@
.do-group {
width: 38px;
}
.undo {
.km-btn-icon {
background-position: 0 -1240px;
}
}
.redo {
.km-btn-icon {
background-position: 0 -1220px;
}
}

View File

@ -0,0 +1,59 @@
.search-box {
float: right;
background-color: #fff;
border: 1px solid #dbdbdb;
position: relative;
top: 0;
z-index: 3;
width: 360px;
height: 40px;
padding: 3px 6px;
opacity: 1;
.search-input-wrap, .prev-and-next-btn {
float: left;
}
.close-search {
float: right;
height: 16px;
width: 16px;
padding: 1px;
border-radius: 100%;
margin-top: 6px;
margin-right: 10px;
.glyphicon {
top: -1px
}
&:hover {
background-color: #efefef;
}
&:active {
background-color: #999;
}
}
.search-input-wrap {
width: 240px;
}
.prev-and-next-btn {
margin-left: 5px;
.btn:focus {
outline: none;
}
}
.search-input {
//border-right: none;
}
.search-addon {
background-color: #fff;
}
}

View File

@ -0,0 +1,89 @@
.top-tab {
.nav-tabs {
background-color: #e1e1e1;
border: 0;
height: 32px;
li {
margin: 0;
a {
margin: 0;
border: 0;
padding: 6px 15px;
border-radius: 0;
vertical-align: middle;
&:hover, &:focus {
background: inherit;
border: 0;
}
}
&.active a {
border: 0;
background-color: #fff;
&:hover, &:focus {
border: 0;
}
}
}
}
.tab-content {
height: 60px;
background-color: #fff;
border-bottom: 1px solid #dbdbdb;
}
.tab-pane {
font-size: 0;
}
}
.km-btn-group {
display: inline-block;
margin: 5px 0;
padding: 0 5px;
vertical-align: middle;
border-right: 1px dashed #eee;
}
.km-btn-item {
display: inline-block;
margin: 0 3px;
font-size: 0;
cursor: default;
&[disabled] {
opacity: 0.5;
&:hover, &:active {
background-color: #fff;
}
}
.km-btn-icon {
display: inline-block;
background: url(images/icons.png) no-repeat;
background-position: 0 20px;
width: 20px;
height: 20px;
padding: 2px;
margin: 1px;
vertical-align: middle;
}
.km-btn-caption {
display: inline-block;
font-size: 12px;
vertical-align: middle;
}
&:hover {background-color: @button-hover;}
&:active {background-color: @button-active;}
}

View File

@ -0,0 +1,39 @@
.btn-group-vertical {
vertical-align: middle;
margin: 5px;
.expand, .expand-caption {
width: 40px;
margin: 0;
padding: 0;
border: none!important;
border-radius: 0!important;
&:hover {
background-color: @tool-hover;
}
&:active {
background-color: @tool-active;
}
&.active {
box-shadow: none;
background-color: @tool-hover;
}
}
.expand {
height: 25px;
background: url(images/icons.png) no-repeat 0 -995px;
background-position-x: 50%;
}
.expand-caption {
height: 20px;
.caption {
font-size: 12px;
}
}
}

View File

@ -0,0 +1,39 @@
.btn-group-vertical {
vertical-align: middle;
margin: 5px;
.search, .search-caption {
width: 40px;
margin: 0;
padding: 0;
border: none!important;
border-radius: 0!important;
&:hover {
background-color: @tool-hover;
}
&:active {
background-color: @tool-active;
}
&.active {
box-shadow: none;
background-color: @tool-hover;
}
}
.search {
height: 25px;
background: url(images/icons.png) no-repeat 0 -345px;
background-position-x: 50%;
}
.search-caption {
height: 20px;
.caption {
font-size: 12px;
}
}
}

View File

@ -0,0 +1,38 @@
.btn-group-vertical {
vertical-align: middle;
margin: 5px;
.select, .select-caption {
width: 40px;
margin: 0;
padding: 0;
border: none!important;
border-radius: 0!important;
&:hover {
background-color: @tool-hover;
}
&:active {
background-color: @tool-active;
}
&.active {
box-shadow: none;
background-color: @tool-hover;
}
}
.select {
height: 25px;
background: url(images/icons.png) no-repeat 7px -1175px;
}
.select-caption {
height: 20px;
.caption {
font-size: 12px;
}
}
}

4973
app/static/minder/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
{
"name": "kityminder-editor",
"version": "1.0.64",
"description": "A powerful mind map editor",
"main": "kityminder.editor.js",
"scripts": {
"init": "npm i -g wr && npm install -g less && npm install -g bower && bower install && npm install",
"build": "grunt build",
"dev": "grunt dev",
"watch": "wr --exec \"lessc --source-map less/editor.less dist/kityminder.editor.css && grunt build\" less ui",
"postinstall": "bower install"
},
"repository": {
"type": "git",
"url": "https://github.com/fex-team/kityminder-editor"
},
"keywords": [
"kityminder",
"editor",
"html5",
"js",
"mindmap"
],
"author": "fex <fex@baidu.com>",
"license": "GPL-2.0",
"bugs": {
"url": "https://github.com/fex-team/kityminder-editor/issues"
},
"homepage": "https://github.com/fex-team/kityminder-editor",
"devDependencies": {
"cz-conventional-changelog": "^1.1.5",
"grunt": "~0.4.1",
"grunt-angular-templates": "~0.5.0",
"grunt-browser-sync": "^2.2.0",
"grunt-contrib-clean": "^0.5.0",
"grunt-contrib-concat": "~0.5.0",
"grunt-contrib-copy": "^0.5.0",
"grunt-contrib-cssmin": "^0.12.0",
"grunt-contrib-less": "^1.0.0",
"grunt-contrib-uglify": "^3.3.0",
"grunt-contrib-watch": "^1.0.0",
"grunt-module-dependence": "~0.2.0",
"grunt-ng-annotate": "^0.9.2",
"grunt-replace": "~0.8.0",
"grunt-wiredep": "^2.0.0",
"jshint-stylish": "^1.0.0",
"load-grunt-tasks": "^3.1.0",
"uglify-js": "^2.8.29"
},
"dependencies": {
"kityminder-core": "^1.4.50"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

View File

@ -0,0 +1,67 @@
<?php
/**
* @fileOverview: 本文件用于 DEMO 用途,用于提供图片上传后端的接口,接收前端的上传文件请求,返回上传成功后的 URL绝对地址
*
* 原理:
* 1. 返回的接口结构为 {errno: <错误号, 无错误则为 0>, msg: <错误信息>, data: {url: <返回的 URL>}}
* 2. 由于要兼容两种情况的上传:通过对话框选择本地文件上传和直接 Ctrl + V多见于截图后因此本文件分别进行了判断
*
*
* 注意:
* 1. 本文件的路径可以进行配置,详见 README.md 中「初始化配置」部分。
* 2. 由于使用场景不同,请根据实际场景编写上传文件的处理。
* 3. 本文件并没有做任何的安全方面的防护,请勿用于生产环境。
*
* @author: zhangbobell
*
* @date: 2016.07.06
*
*/
// 返回给前端的地址是绝对地址,这里是前缀
$HTTP_PREFIX = 'http://localhost/kityminder-editor/';
$errno = 0;
$msg = 'ok';
$url = '';
if ((($_FILES["upload_file"]["type"] == "image/gif")
|| ($_FILES["upload_file"]["type"] == "image/jpeg")
|| ($_FILES["upload_file"]["type"] == "image/jpg")
|| ($_FILES["upload_file"]["type"] == "image/png"))
&& ($_FILES["upload_file"]["size"] < 1 * 1000 * 1000)) {
if ($_FILES["upload_file"]["error"] > 0) {
$errno = 414;
$msg = $_FILES["upload_file"]["error"];
} else {
// 分为两种情况 `Ctrl + V` 和普通上传
if ($_FILES["upload_file"]["name"] === 'blob') {
$ext_name = 'png';
} else {
$ext_name = array_pop(explode('.', $_FILES["upload_file"]["name"]));
}
$sha1_name = sha1_file($_FILES["upload_file"]["tmp_name"]) . '.' . $ext_name;
move_uploaded_file($_FILES["upload_file"]["tmp_name"], "upload/" . $sha1_name);
$url = $HTTP_PREFIX . "server/upload/" . $sha1_name;
}
} else {
$errno = 416;
$msg = 'File is invalid';
}
$result = array(
'errno' => $errno,
'msg' => $msg,
'data' => array(
'url' => $url
)
);
echo json_encode($result);

View File

@ -0,0 +1,171 @@
function exportData(){
var jsonData=editor.minder.exportJson();
jsonData = JSON.stringify(jsonData);
console.log(jsonData);
var minder_id = document.getElementById('minderId').value;
$.ajax(
{
url: "/save_test_minder_content.json",
data:{"content":jsonData,"id":minder_id},
type: "post",
dataType:"json",
beforeSend:function()
{
return true;
},
success:function(data)
{
if(data)
{
// 解析json数据
var data = data;
if(data.code==200){
alert('success!');
// alert
}else{
alert('code is :'+data.code+' and message is :'+data.msg);
}
}
else
{
// $("#tip").html("<span style='color:red'>失败,请重试</span>");
alert('操作失败');
}
},
error:function()
{
alert('请求出错');
},
complete:function()
{
// $('#tips').hide();
}
});
}
function init(minder_id,refreshJson){
// localStorage.removeItem('__dev_minder_content');
document.getElementById('minderId').value = minder_id;
if (minder_id != ""){
$.ajax(
{
url: "/get_minders.json",
data:{"id":minder_id},
type: "get",
dataType:"json",
beforeSend:function()
{
return true;
},
success:function(data)
{
if(data)
{
// 解析json数据
var data = data;
if(data.code==200){
// alert('success!');
console.log(JSON.parse(data.content));
// editor.minder.importJson(JSON.parse(data.content));
if (data.content!='{}'){
if (refreshJson==1){
editor.minder.importJson(JSON.parse(data.content));
alert('刷新成功!');
}else {
window.localStorage.__dev_minder_content=data.content;
}
}else{
localStorage.removeItem('__dev_minder_content');
if (refreshJson==1){
alert('刷新成功!');
}
}
// document.location.reload();
// alert
}else{
alert('code is :'+data.code+' and message is :'+data.msg);
}
}
else
{
// $("#tip").html("<span style='color:red'>失败,请重试</span>");
alert('操作失败');
}
},
error:function()
{
alert('请求出错');
},
complete:function()
{
// $('#tips').hide();
}
});
}else{
alert('id is null!');
}
}
function copy_minder(){
var minder_id = document.getElementById('minderId').value;
// alert(minder_id);
if (minder_id != ""){
$.ajax(
{
url: "/copy_test_minder.json",
data:{"id":minder_id},
type: "post",
dataType:"json",
beforeSend:function()
{
return true;
},
success:function(data)
{
if(data)
{
// 解析json数据
var data = data;
if(data.code==200){
alert('success!');
window.location.href=('/edit_minder_json?id='+data.id);
// document.location.reload();
// alert
}else{
alert('code is :'+data.code+' and message is :'+data.msg);
}
}
else
{
// $("#tip").html("<span style='color:red'>失败,请重试</span>");
alert('操作失败');
}
},
error:function()
{
alert('请求出错');
},
complete:function()
{
// $('#tips').hide();
}
});
}else{
alert('id is null!');
}
}

View File

@ -0,0 +1,40 @@
define(function(require, exports, module) {
/**
* 运行时
*/
var runtimes = [];
function assemble(runtime) {
runtimes.push(runtime);
}
function KMEditor(selector) {
this.selector = selector;
for (var i = 0; i < runtimes.length; i++) {
if (typeof runtimes[i] == 'function') {
runtimes[i].call(this, this);
}
}
}
KMEditor.assemble = assemble;
assemble(require('./runtime/container'));
assemble(require('./runtime/fsm'));
assemble(require('./runtime/minder'));
assemble(require('./runtime/receiver'));
assemble(require('./runtime/hotbox'));
assemble(require('./runtime/input'));
assemble(require('./runtime/clipboard-mimetype'));
assemble(require('./runtime/clipboard'));
assemble(require('./runtime/drag'));
assemble(require('./runtime/node'));
assemble(require('./runtime/history'));
assemble(require('./runtime/jumping'));
assemble(require('./runtime/priority'));
assemble(require('./runtime/progress'));
return module.exports = KMEditor;
});

View File

@ -0,0 +1,11 @@
/**
* @fileOverview
*
* 打包暴露
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define('expose-editor', function(require, exports, module) {
return module.exports = kityminder.Editor = require('./editor');
});

View File

@ -0,0 +1,3 @@
define(function(require, exports, module) {
return module.exports = window.HotBox;
});

View File

@ -0,0 +1,3 @@
define(function(require, exports, module) {
});

View File

@ -0,0 +1,3 @@
define(function(require, exports, module) {
return module.exports = window.kityminder.Minder;
});

View File

@ -0,0 +1,123 @@
/**
* @Desc: 新增一个用于处理系统ctrl+c ctrl+v等方式导入导出节点的MIMETYPE处理如系统不支持clipboardEvent或者是FF则不初始化改class
* @Editor: Naixor
* @Date: 2015.9.21
*/
define(function(require, exports, module) {
function MimeType() {
/**
* 私有变量
*/
var SPLITOR = '\uFEFF';
var MIMETYPE = {
'application/km': '\uFFFF'
};
var SIGN = {
'\uFEFF': 'SPLITOR',
'\uFFFF': 'application/km'
};
/**
* 用于将一段纯文本封装成符合其数据格式的文本
* @method process private
* @param {MIMETYPE} mimetype 数据格式
* @param {String} text 原始文本
* @return {String} 符合该数据格式下的文本
* @example
* var str = "123";
* str = process('application/km', str); // 返回的内容再经过MimeType判断会读取出其数据格式为application/km
* process('text/plain', str); // 若接受到一个非纯文本信息,则会将其转换为新的数据格式
*/
function process(mimetype, text) {
if (!this.isPureText(text)) {
var _mimetype = this.whichMimeType(text);
if (!_mimetype) {
throw new Error('unknow mimetype!');
};
text = this.getPureText(text);
};
if (mimetype === false) {
return text;
};
return mimetype + SPLITOR + text;
}
/**
* 注册数据类型的标识
* @method registMimeTypeProtocol public
* @param {String} type 数据类型
* @param {String} sign 标识
*/
this.registMimeTypeProtocol = function(type, sign) {
if (sign && SIGN[sign]) {
throw new Error('sing has registed!');
}
if (type && !!MIMETYPE[type]) {
throw new Error('mimetype has registed!');
};
SIGN[sign] = type;
MIMETYPE[type] = sign;
}
/**
* 获取已注册数据类型的协议
* @method getMimeTypeProtocol public
* @param {String} type 数据类型
* @param {String} text|undefiend 文本内容或不传入
* @return {String|Function}
* @example
* text若不传入则直接返回对应数据格式的处理(process)方法
* 若传入文本则直接调用对应的process方法进行处理此时返回处理后的内容
* var m = new MimeType();
* var kmprocess = m.getMimeTypeProtocol('application/km');
* kmprocess("123") === m.getMimeTypeProtocol('application/km', "123");
*
*/
this.getMimeTypeProtocol = function(type, text) {
var mimetype = MIMETYPE[type] || false;
if (text === undefined) {
return process.bind(this, mimetype);
};
return process(mimetype, text);
}
this.getSpitor = function() {
return SPLITOR;
}
this.getMimeType = function(sign) {
if (sign !== undefined) {
return SIGN[sign] || null;
};
return MIMETYPE;
}
}
MimeType.prototype.isPureText = function(text) {
return !(~text.indexOf(this.getSpitor()));
}
MimeType.prototype.getPureText = function(text) {
if (this.isPureText(text)) {
return text;
};
return text.split(this.getSpitor())[1];
}
MimeType.prototype.whichMimeType = function(text) {
if (this.isPureText(text)) {
return null;
};
return this.getMimeType(text.split(this.getSpitor())[0]);
}
function MimeTypeRuntime() {
if (this.minder.supportClipboardEvent && !kity.Browser.gecko) {
this.MimeType = new MimeType();
};
}
return module.exports = MimeTypeRuntime;
});

View File

@ -0,0 +1,188 @@
/**
* @Desc: 处理editor的clipboard事件只在支持ClipboardEvent并且不是FF的情况下工作
* @Editor: Naixor
* @Date: 2015.9.21
*/
define(function(require, exports, module) {
function ClipboardRuntime () {
var minder = this.minder;
var Data = window.kityminder.data;
if (!minder.supportClipboardEvent || kity.Browser.gecko) {
return;
};
var fsm = this.fsm;
var receiver = this.receiver;
var MimeType = this.MimeType;
var kmencode = MimeType.getMimeTypeProtocol('application/km'),
decode = Data.getRegisterProtocol('json').decode;
var _selectedNodes = [];
/*
* 增加对多节点赋值粘贴的处理
*/
function encode (nodes) {
var _nodes = [];
for (var i = 0, l = nodes.length; i < l; i++) {
_nodes.push(minder.exportNode(nodes[i]));
}
return kmencode(Data.getRegisterProtocol('json').encode(_nodes));
}
var beforeCopy = function (e) {
if (document.activeElement == receiver.element) {
var clipBoardEvent = e;
var state = fsm.state();
switch (state) {
case 'input': {
break;
}
case 'normal': {
var nodes = [].concat(minder.getSelectedNodes());
if (nodes.length) {
// 这里由于被粘贴复制的节点的id信息也都一样故做此算法
// 这里有个疑问使用node.getParent()或者node.parent会离奇导致出现非选中节点被渲染成选中节点因此使用isAncestorOf而没有使用自行回溯的方式
if (nodes.length > 1) {
var targetLevel;
nodes.sort(function(a, b) {
return a.getLevel() - b.getLevel();
});
targetLevel = nodes[0].getLevel();
if (targetLevel !== nodes[nodes.length-1].getLevel()) {
var plevel, pnode,
idx = 0, l = nodes.length, pidx = l-1;
pnode = nodes[pidx];
while (pnode.getLevel() !== targetLevel) {
idx = 0;
while (idx < l && nodes[idx].getLevel() === targetLevel) {
if (nodes[idx].isAncestorOf(pnode)) {
nodes.splice(pidx, 1);
break;
}
idx++;
}
pidx--;
pnode = nodes[pidx];
}
};
};
var str = encode(nodes);
clipBoardEvent.clipboardData.setData('text/plain', str);
}
e.preventDefault();
break;
}
}
}
}
var beforeCut = function (e) {
if (document.activeElement == receiver.element) {
if (minder.getStatus() !== 'normal') {
e.preventDefault();
return;
};
var clipBoardEvent = e;
var state = fsm.state();
switch (state) {
case 'input': {
break;
}
case 'normal': {
var nodes = minder.getSelectedNodes();
if (nodes.length) {
clipBoardEvent.clipboardData.setData('text/plain', encode(nodes));
minder.execCommand('removenode');
}
e.preventDefault();
break;
}
}
};
}
var beforePaste = function(e) {
if (document.activeElement == receiver.element) {
if (minder.getStatus() !== 'normal') {
e.preventDefault();
return;
};
var clipBoardEvent = e;
var state = fsm.state();
var textData = clipBoardEvent.clipboardData.getData('text/plain');
switch (state) {
case 'input': {
// input状态下如果格式为application/km则不进行paste操作
if (!MimeType.isPureText(textData)) {
e.preventDefault();
return;
};
break;
}
case 'normal': {
/*
* 针对normal状态下通过对选中节点粘贴导入子节点文本进行单独处理
*/
var sNodes = minder.getSelectedNodes();
if (MimeType.whichMimeType(textData) === 'application/km') {
var nodes = decode(MimeType.getPureText(textData));
var _node;
sNodes.forEach(function(node) {
// 由于粘贴逻辑中为了排除子节点重新排序导致逆序,因此复制的时候倒过来
for (var i = nodes.length-1; i >= 0; i--) {
_node = minder.createNode(null, node);
minder.importNode(_node, nodes[i]);
_selectedNodes.push(_node);
node.appendChild(_node);
}
});
minder.select(_selectedNodes, true);
_selectedNodes = [];
minder.refresh();
}
else if (clipBoardEvent.clipboardData && clipBoardEvent.clipboardData.items[0].type.indexOf('image') > -1) {
var imageFile = clipBoardEvent.clipboardData.items[0].getAsFile();
var serverService = angular.element(document.body).injector().get('server');
return serverService.uploadImage(imageFile).then(function (json) {
var resp = json.data;
if (resp.errno === 0) {
minder.execCommand('image', resp.data.url);
}
});
}
else {
sNodes.forEach(function(node) {
minder.Text2Children(node, textData);
});
}
e.preventDefault();
break;
}
}
}
}
/**
* 由editor的receiver统一处理全部事件包括clipboard事件
* @Editor: Naixor
* @Date: 2015.9.24
*/
document.addEventListener('copy', beforeCopy);
document.addEventListener('cut', beforeCut);
document.addEventListener('paste', beforePaste);
}
return module.exports = ClipboardRuntime;
});

View File

@ -0,0 +1,33 @@
/**
* @fileOverview
*
* 初始化编辑器的容器
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
/**
* 最先执行的 Runtime初始化编辑器容器
*/
function ContainerRuntime() {
var container;
if (typeof(this.selector) == 'string') {
container = document.querySelector(this.selector);
} else {
container = this.selector;
}
if (!container) throw new Error('Invalid selector: ' + this.selector);
// 这个类名用于给编辑器添加样式
container.classList.add('km-editor');
// 暴露容器给其他运行时使用
this.container = container;
}
return module.exports = ContainerRuntime;
});

View File

@ -0,0 +1,140 @@
/**
* @fileOverview
*
* 用于拖拽节点时屏蔽键盘事件
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
var Hotbox = require('../hotbox');
var Debug = require('../tool/debug');
var debug = new Debug('drag');
function DragRuntime() {
var fsm = this.fsm;
var minder = this.minder;
var hotbox = this.hotbox;
var receiver = this.receiver;
var receiverElement = receiver.element;
// setup everything to go
setupFsm();
// listen the fsm changes, make action.
function setupFsm() {
// when jumped to drag mode, enter
fsm.when('* -> drag', function() {
// now is drag mode
});
fsm.when('drag -> *', function(exit, enter, reason) {
if (reason == 'drag-finish') {
// now exit drag mode
}
});
}
var downX, downY;
var MOUSE_HAS_DOWN = 0;
var MOUSE_HAS_UP = 1;
var BOUND_CHECK = 20;
var flag = MOUSE_HAS_UP;
var maxX, maxY, osx, osy, containerY;
var freeHorizen = false, freeVirtical = false;
var frame;
function move(direction, speed) {
if (!direction) {
freeHorizen = freeVirtical = false;
frame && kity.releaseFrame(frame);
frame = null;
return;
}
if (!frame) {
frame = kity.requestFrame((function (direction, speed, minder) {
return function (frame) {
switch (direction) {
case 'left':
minder._viewDragger.move({x: -speed, y: 0}, 0);
break;
case 'top':
minder._viewDragger.move({x: 0, y: -speed}, 0);
break;
case 'right':
minder._viewDragger.move({x: speed, y: 0}, 0);
break;
case 'bottom':
minder._viewDragger.move({x: 0, y: speed}, 0);
break;
default:
return;
}
frame.next();
};
})(direction, speed, minder));
}
}
minder.on('mousedown', function(e) {
flag = MOUSE_HAS_DOWN;
var rect = minder.getPaper().container.getBoundingClientRect();
downX = e.originEvent.clientX;
downY = e.originEvent.clientY;
containerY = rect.top;
maxX = rect.width;
maxY = rect.height;
});
minder.on('mousemove', function(e) {
if (fsm.state() === 'drag' && flag == MOUSE_HAS_DOWN && minder.getSelectedNode()
&& (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK
|| Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK)) {
osx = e.originEvent.clientX;
osy = e.originEvent.clientY - containerY;
if (osx < BOUND_CHECK) {
move('right', BOUND_CHECK - osx);
} else if (osx > maxX - BOUND_CHECK) {
move('left', BOUND_CHECK + osx - maxX);
} else {
freeHorizen = true;
}
if (osy < BOUND_CHECK) {
move('bottom', osy);
} else if (osy > maxY - BOUND_CHECK) {
move('top', BOUND_CHECK + osy - maxY);
} else {
freeVirtical = true;
}
if (freeHorizen && freeVirtical) {
move(false);
}
}
if (fsm.state() !== 'drag'
&& flag === MOUSE_HAS_DOWN
&& minder.getSelectedNode()
&& (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK
|| Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK)) {
if (fsm.state() === 'hotbox') {
hotbox.active(Hotbox.STATE_IDLE);
}
return fsm.jump('drag', 'user-drag');
}
});
window.addEventListener('mouseup', function () {
flag = MOUSE_HAS_UP;
if (fsm.state() === 'drag') {
move(false);
return fsm.jump('normal', 'drag-finish');
}
}, false);
}
return module.exports = DragRuntime;
});

View File

@ -0,0 +1,121 @@
/**
* @fileOverview
*
* 编辑器状态机
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
var Debug = require('../tool/debug');
var debug = new Debug('fsm');
function handlerConditionMatch(condition, when, exit, enter) {
if (condition.when != when) return false;
if (condition.enter != '*' && condition.enter != enter) return false;
if (condition.exit != '*' && condition.exit != exit) return;
return true;
}
function FSM(defaultState) {
var currentState = defaultState;
var BEFORE_ARROW = ' - ';
var AFTER_ARROW = ' -> ';
var handlers = [];
/**
* 状态跳转
*
* 会通知所有的状态跳转监视器
*
* @param {string} newState 新状态名称
* @param {any} reason 跳转的原因可以作为参数传递给跳转监视器
*/
this.jump = function(newState, reason) {
if (!reason) throw new Error('Please tell fsm the reason to jump');
var oldState = currentState;
var notify = [oldState, newState].concat([].slice.call(arguments, 1));
var i, handler;
// 跳转前
for (i = 0; i < handlers.length; i++) {
handler = handlers[i];
if (handlerConditionMatch(handler.condition, 'before', oldState, newState)) {
if (handler.apply(null, notify)) return;
}
}
currentState = newState;
debug.log('[{0}] {1} -> {2}', reason, oldState, newState);
// 跳转后
for (i = 0; i < handlers.length; i++) {
handler = handlers[i];
if (handlerConditionMatch(handler.condition, 'after', oldState, newState)) {
handler.apply(null, notify);
}
}
return currentState;
};
/**
* 返回当前状态
* @return {string}
*/
this.state = function() {
return currentState;
};
/**
* 添加状态跳转监视器
*
* @param {string} condition
* 监视的时机
* "* => *" 默认
*
* @param {Function} handler
* 监视函数当状态跳转的时候会接收三个参数
* * from - 跳转前的状态
* * to - 跳转后的状态
* * reason - 跳转的原因
*/
this.when = function(condition, handler) {
if (arguments.length == 1) {
handler = condition;
condition = '* -> *';
}
var when, resolved, exit, enter;
resolved = condition.split(BEFORE_ARROW);
if (resolved.length == 2) {
when = 'before';
} else {
resolved = condition.split(AFTER_ARROW);
if (resolved.length == 2) {
when = 'after';
}
}
if (!when) throw new Error('Illegal fsm condition: ' + condition);
exit = resolved[0];
enter = resolved[1];
handler.condition = {
when: when,
exit: exit,
enter: enter
};
handlers.push(handler);
};
}
function FSMRumtime() {
this.fsm = new FSM('normal');
}
return module.exports = FSMRumtime;
});

View File

@ -0,0 +1,133 @@
/**
* @fileOverview
*
* 历史管理
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
var jsonDiff = require('../tool/jsondiff');
function HistoryRuntime() {
var minder = this.minder;
var hotbox = this.hotbox;
var MAX_HISTORY = 100;
var lastSnap;
var patchLock;
var undoDiffs;
var redoDiffs;
function reset() {
undoDiffs = [];
redoDiffs = [];
lastSnap = minder.exportJson();
}
function makeUndoDiff() {
var headSnap = minder.exportJson();
var diff = jsonDiff(headSnap, lastSnap);
if (diff.length) {
undoDiffs.push(diff);
while (undoDiffs.length > MAX_HISTORY) {
undoDiffs.shift();
}
lastSnap = headSnap;
return true;
}
}
function makeRedoDiff() {
var revertSnap = minder.exportJson();
redoDiffs.push(jsonDiff(revertSnap, lastSnap));
lastSnap = revertSnap;
}
function undo() {
patchLock = true;
var undoDiff = undoDiffs.pop();
if (undoDiff) {
minder.applyPatches(undoDiff);
makeRedoDiff();
}
patchLock = false;
}
function redo() {
patchLock = true;
var redoDiff = redoDiffs.pop();
if (redoDiff) {
minder.applyPatches(redoDiff);
makeUndoDiff();
}
patchLock = false;
}
function changed() {
if (patchLock) return;
if (makeUndoDiff()) redoDiffs = [];
}
function hasUndo() {
return !!undoDiffs.length;
}
function hasRedo() {
return !!redoDiffs.length;
}
function updateSelection(e) {
if (!patchLock) return;
var patch = e.patch;
switch (patch.express) {
case 'node.add':
minder.select(patch.node.getChild(patch.index), true);
break;
case 'node.remove':
case 'data.replace':
case 'data.remove':
case 'data.add':
minder.select(patch.node, true);
break;
}
}
this.history = {
reset: reset,
undo: undo,
redo: redo,
hasUndo: hasUndo,
hasRedo: hasRedo
};
reset();
minder.on('contentchange', changed);
minder.on('import', reset);
minder.on('patch', updateSelection);
var main = hotbox.state('main');
main.button({
position: 'top',
label: '撤销',
key: 'Ctrl + Z',
enable: hasUndo,
action: undo,
next: 'idle'
});
main.button({
position: 'top',
label: '重做',
key: 'Ctrl + Y',
enable: hasRedo,
action: redo,
next: 'idle'
});
}
window.diff = jsonDiff;
return module.exports = HistoryRuntime;
});

View File

@ -0,0 +1,56 @@
/**
* @fileOverview
*
* 热盒 Runtime
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
var Hotbox = require('../hotbox');
function HotboxRuntime() {
var fsm = this.fsm;
var minder = this.minder;
var receiver = this.receiver;
var container = this.container;
var hotbox = new Hotbox(container);
hotbox.setParentFSM(fsm);
fsm.when('normal -> hotbox', function(exit, enter, reason) {
var node = minder.getSelectedNode();
var position;
if (node) {
var box = node.getRenderBox();
position = {
x: box.cx,
y: box.cy
};
}
hotbox.active('main', position);
});
fsm.when('normal -> normal', function(exit, enter, reason, e) {
if (reason == 'shortcut-handle') {
var handleResult = hotbox.dispatch(e);
if (handleResult) {
e.preventDefault();
} else {
minder.dispatchKeyEvent(e);
}
}
});
fsm.when('modal -> normal', function(exit, enter, reason, e) {
if (reason == 'import-text-finish') {
receiver.element.focus();
}
});
this.hotbox = hotbox;
}
return module.exports = HotboxRuntime;
});

View File

@ -0,0 +1,395 @@
/**
* @fileOverview
*
* 文本输入支持
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
require('../tool/innertext');
var Debug = require('../tool/debug');
var debug = new Debug('input');
function InputRuntime() {
var fsm = this.fsm;
var minder = this.minder;
var hotbox = this.hotbox;
var receiver = this.receiver;
var receiverElement = receiver.element;
var isGecko = window.kity.Browser.gecko;
// setup everything to go
setupReciverElement();
setupFsm();
setupHotbox();
// expose editText()
this.editText = editText;
// listen the fsm changes, make action.
function setupFsm() {
// when jumped to input mode, enter
fsm.when('* -> input', enterInputMode);
// when exited, commit or exit depends on the exit reason
fsm.when('input -> *', function(exit, enter, reason) {
switch (reason) {
case 'input-cancel':
return exitInputMode();
case 'input-commit':
default:
return commitInputResult();
}
});
// lost focus to commit
receiver.onblur(function (e) {
if (fsm.state() == 'input') {
fsm.jump('normal', 'input-commit');
}
});
minder.on('beforemousedown', function () {
if (fsm.state() == 'input') {
fsm.jump('normal', 'input-commit');
}
});
minder.on('dblclick', function() {
if (minder.getSelectedNode() && minder._status !== 'readonly') {
editText();
}
});
}
// let the receiver follow the current selected node position
function setupReciverElement() {
if (debug.flaged) {
receiverElement.classList.add('debug');
}
receiverElement.onmousedown = function(e) {
e.stopPropagation();
};
minder.on('layoutallfinish viewchange viewchanged selectionchange', function(e) {
// viewchange event is too frequenced, lazy it
if (e.type == 'viewchange' && fsm.state() != 'input') return;
updatePosition();
});
updatePosition();
}
// edit entrance in hotbox
function setupHotbox() {
hotbox.state('main').button({
position: 'center',
label: '编辑',
key: 'F2',
enable: function() {
return minder.queryCommandState('text') != -1;
},
action: editText
});
}
/**
* 增加对字体的鉴别以保证用户在编辑状态ctrl/cmd + b/i所触发的加粗斜体与显示一致
* @editor Naixor
* @Date 2015-12-2
*/
// edit for the selected node
function editText() {
var node = minder.getSelectedNode();
if (!node) {
return;
}
var textContainer = receiverElement;
receiverElement.innerText = "";
if (node.getData('font-weight') === 'bold') {
var b = document.createElement('b');
textContainer.appendChild(b);
textContainer = b;
}
if (node.getData('font-style') === 'italic') {
var i = document.createElement('i');
textContainer.appendChild(i);
textContainer = i;
}
textContainer.innerText = minder.queryCommandValue('text');
if (isGecko) {
receiver.fixFFCaretDisappeared();
};
fsm.jump('input', 'input-request');
receiver.selectAll();
}
/**
* 增加对字体的鉴别以保证用户在编辑状态ctrl/cmd + b/i所触发的加粗斜体与显示一致
* @editor Naixor
* @Date 2015-12-2
*/
function enterInputMode() {
var node = minder.getSelectedNode();
if (node) {
var fontSize = node.getData('font-size') || node.getStyle('font-size');
receiverElement.style.fontSize = fontSize + 'px';
receiverElement.style.minWidth = 0;
receiverElement.style.minWidth = receiverElement.clientWidth + 'px';
receiverElement.style.fontWeight = node.getData('font-weight') || '';
receiverElement.style.fontStyle = node.getData('font-style') || '';
receiverElement.classList.add('input');
receiverElement.focus();
}
}
/**
* 按照文本提交操作处理
* @Desc: 从其他节点复制文字到另一个节点时部分浏览器(chrome)会自动包裹一个span标签这样试用一下逻辑出来的就不是text节点二是span节点因此导致undefined的情况发生
* @Warning: 下方代码使用[].slice.call来将HTMLDomCollection处理成为Arrayie8及以下会有问题
* @Editor: Naixor
* @Date: 2015.9.16
*/
function commitInputText (textNodes) {
var text = '';
var TAB_CHAR = '\t',
ENTER_CHAR = '\n',
STR_CHECK = /\S/,
SPACE_CHAR = '\u0020',
// 针对FF,SG,BD,LB,IE等浏览器下SPACE的charCode存在为32和160的情况做处理
SPACE_CHAR_REGEXP = new RegExp('(\u0020|' + String.fromCharCode(160) + ')'),
BR = document.createElement('br');
var isBold = false,
isItalic = false;
for (var str,
_divChildNodes,
space_l, space_num, tab_num,
i = 0, l = textNodes.length; i < l; i++) {
str = textNodes[i];
switch (Object.prototype.toString.call(str)) {
// 正常情况处理
case '[object HTMLBRElement]': {
text += ENTER_CHAR;
break;
}
case '[object Text]': {
// SG下会莫名其妙的加上&nbsp;影响后续判断,干掉!
/**
* FF下的wholeText会导致如下问题
* |123| -> 在一个节点中输入一段字符此时TextNode为[#Text 123]
* 提交并重新编辑在后面追加几个字符
* |123abc| -> 此时123为一个TextNode为[#Text 123, #Text abc]但是对这两个任意取值wholeText均为全部内容123abc
* 上述BUG仅存在在FF中故将wholeText更改为textContent
*/
str = str.textContent.replace("&nbsp;", " ");
if (!STR_CHECK.test(str)) {
space_l = str.length;
while (space_l--) {
if (SPACE_CHAR_REGEXP.test(str[space_l])) {
text += SPACE_CHAR;
} else if (str[space_l] === TAB_CHAR) {
text += TAB_CHAR;
}
}
} else {
text += str;
}
break;
}
// ctrl + b/i 会给字体加上<b>/<i>标签来实现黑体和斜体
case '[object HTMLElement]': {
switch (str.nodeName) {
case "B": {
isBold = true;
break;
}
case "I": {
isItalic = true;
break;
}
default: {}
}
[].splice.apply(textNodes, [i, 1].concat([].slice.call(str.childNodes)));
l = textNodes.length;
i--;
break;
}
// 被增加span标签的情况会被处理成正常情况并会推交给上面处理
case '[object HTMLSpanElement]': {
[].splice.apply(textNodes, [i, 1].concat([].slice.call(str.childNodes)));
l = textNodes.length;
i--;
break;
}
// 若标签为image标签则判断是否为合法url是将其加载进来
case '[object HTMLImageElement]': {
if (str.src) {
if (/http(|s):\/\//.test(str.src)) {
minder.execCommand("Image", str.src, str.alt);
} else {
// data:image协议情况
}
};
break;
}
// 被增加div标签的情况会被处理成正常情况并会推交给上面处理
case '[object HTMLDivElement]': {
_divChildNodes = [];
for (var di = 0, l = str.childNodes.length; di < l; di++) {
_divChildNodes.push(str.childNodes[di]);
}
_divChildNodes.push(BR);
[].splice.apply(textNodes, [i, 1].concat(_divChildNodes));
l = textNodes.length;
i--;
break;
}
default: {
if (str && str.childNodes.length) {
_divChildNodes = [];
for (var di = 0, l = str.childNodes.length; di < l; di++) {
_divChildNodes.push(str.childNodes[di]);
}
_divChildNodes.push(BR);
[].splice.apply(textNodes, [i, 1].concat(_divChildNodes));
l = textNodes.length;
i--;
} else {
if (str && str.textContent !== undefined) {
text += str.textContent;
} else {
text += "";
}
}
// // 其他带有样式的节点被粘贴进来则直接取textContent若取不出来则置空
}
}
};
text = text.replace(/^\n*|\n*$/g, '');
text = text.replace(new RegExp('(\n|\r|\n\r)(\u0020|' + String.fromCharCode(160) + '){4}', 'g'), '$1\t');
minder.getSelectedNode().setText(text);
if (isBold) {
minder.queryCommandState('bold') || minder.execCommand('bold');
} else {
minder.queryCommandState('bold') && minder.execCommand('bold');
}
if (isItalic) {
minder.queryCommandState('italic') || minder.execCommand('italic');
} else {
minder.queryCommandState('italic') && minder.execCommand('italic');
}
exitInputMode();
return text;
}
/**
* 判断节点的文本信息是否是
* @Desc: 从其他节点复制文字到另一个节点时部分浏览器(chrome)会自动包裹一个span标签这样使用以下逻辑出来的就不是text节点二是span节点因此导致undefined的情况发生
* @Notice: 此处逻辑应该拆分到 kityminder-core/core/data中去单独增加一个对某个节点importJson的事件
* @Editor: Naixor
* @Date: 2015.9.16
*/
function commitInputNode(node, text) {
try {
minder.decodeData('text', text).then(function(json) {
function importText(node, json, minder) {
var data = json.data;
node.setText(data.text || '');
var childrenTreeData = json.children || [];
for (var i = 0; i < childrenTreeData.length; i++) {
var childNode = minder.createNode(null, node);
importText(childNode, childrenTreeData[i], minder);
}
return node;
}
importText(node, json, minder);
minder.fire("contentchange");
minder.getRoot().renderTree();
minder.layout(300);
});
} catch (e) {
minder.fire("contentchange");
minder.getRoot().renderTree();
// 无法被转换成脑图节点则不处理
if (e.toString() !== 'Error: Invalid local format') {
throw e;
}
}
}
function commitInputResult() {
/**
* @Desc: 进行如下处理
* 根据用户的输入判断是否生成新的节点
* fix #83 https://github.com/fex-team/kityminder-editor/issues/83
* @Editor: Naixor
* @Date: 2015.9.16
*/
var textNodes = [].slice.call(receiverElement.childNodes);
/**
* @Desc: 增加setTimeout的原因ie下receiverElement.innerHTML=""会导致后
* 面commitInputText中使用textContent报错不要问我什么原因
* @Editor: Naixor
* @Date: 2015.12.14
*/
setTimeout(function () {
// 解决过大内容导致SVG窜位问题
receiverElement.innerHTML = "";
}, 0);
var node = minder.getSelectedNode();
textNodes = commitInputText(textNodes);
commitInputNode(node, textNodes);
if (node.type == 'root') {
var rootText = minder.getRoot().getText();
minder.fire('initChangeRoot', {text: rootText});
}
}
function exitInputMode() {
receiverElement.classList.remove('input');
receiver.selectAll();
}
function updatePosition() {
var planed = updatePosition;
var focusNode = minder.getSelectedNode();
if (!focusNode) return;
if (!planed.timer) {
planed.timer = setTimeout(function() {
var box = focusNode.getRenderBox('TextRenderer');
receiverElement.style.left = Math.round(box.x) + 'px';
receiverElement.style.top = (debug.flaged ? Math.round(box.bottom + 30) : Math.round(box.y)) + 'px';
//receiverElement.focus();
planed.timer = 0;
});
}
}
}
return module.exports = InputRuntime;
});

View File

@ -0,0 +1,174 @@
/**
* @fileOverview
*
* 根据按键控制状态机的跳转
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
var Hotbox = require('../hotbox');
// Nice: http://unixpapa.com/js/key.html
function isIntendToInput(e) {
if (e.ctrlKey || e.metaKey || e.altKey) return false;
// a-zA-Z
if (e.keyCode >= 65 && e.keyCode <= 90) return true;
// 0-9 以及其上面的符号
if (e.keyCode >= 48 && e.keyCode <= 57) return true;
// 小键盘区域 (除回车外)
if (e.keyCode != 108 && e.keyCode >= 96 && e.keyCode <= 111) return true;
// 小键盘区域 (除回车外)
// @yinheli from pull request
if (e.keyCode != 108 && e.keyCode >= 96 && e.keyCode <= 111) return true;
// 输入法
if (e.keyCode == 229 || e.keyCode === 0) return true;
return false;
}
/**
* @Desc: 下方使用receiver.enable()和receiver.disable()通过
* 修改div contenteditable属性的hack来解决开启热核后依然无法屏蔽浏览器输入的bug;
* 特别: win下FF对于此种情况必须要先blur在focus才能解决但是由于这样做会导致用户
* 输入法状态丢失因此对FF暂不做处理
* @Editor: Naixor
* @Date: 2015.09.14
*/
function JumpingRuntime() {
var fsm = this.fsm;
var minder = this.minder;
var receiver = this.receiver;
var container = this.container;
var receiverElement = receiver.element;
var hotbox = this.hotbox;
// normal -> *
receiver.listen('normal', function(e) {
// 为了防止处理进入edit模式而丢失处理的首字母,此时receiver必须为enable
receiver.enable();
// normal -> hotbox
if (e.is('Space')) {
e.preventDefault();
// safari下Space触发hotbox,然而这时Space已在receiver上留下作案痕迹,因此抹掉
if (kity.Browser.safari) {
receiverElement.innerHTML = '';
}
return fsm.jump('hotbox', 'space-trigger');
}
/**
* check
* @editor Naixor
* @Date 2015-12-2
*/
switch (e.type) {
case 'keydown': {
if (minder.getSelectedNode()) {
if (isIntendToInput(e)) {
return fsm.jump('input', 'user-input');
};
} else {
receiverElement.innerHTML = '';
}
// normal -> normal shortcut
fsm.jump('normal', 'shortcut-handle', e);
break;
}
case 'keyup': {
break;
}
default: {}
}
});
// hotbox -> normal
receiver.listen('hotbox', function(e) {
receiver.disable();
e.preventDefault();
var handleResult = hotbox.dispatch(e);
if (hotbox.state() == Hotbox.STATE_IDLE && fsm.state() == 'hotbox') {
return fsm.jump('normal', 'hotbox-idle');
}
});
// input => normal
receiver.listen('input', function(e) {
receiver.enable();
if (e.type == 'keydown') {
if (e.is('Enter')) {
e.preventDefault();
return fsm.jump('normal', 'input-commit');
}
if (e.is('Esc')) {
e.preventDefault();
return fsm.jump('normal', 'input-cancel');
}
if (e.is('Tab') || e.is('Shift + Tab')) {
e.preventDefault();
}
} else if (e.type == 'keyup' && e.is('Esc')) {
e.preventDefault();
return fsm.jump('normal', 'input-cancel');
}
});
//////////////////////////////////////////////
/// 右键呼出热盒
/// 判断的标准是:按下的位置和结束的位置一致
//////////////////////////////////////////////
var downX, downY;
var MOUSE_RB = 2; // 右键
container.addEventListener('mousedown', function(e) {
if (e.button == MOUSE_RB) {
e.preventDefault();
}
if (fsm.state() == 'hotbox') {
hotbox.active(Hotbox.STATE_IDLE);
fsm.jump('normal', 'blur');
} else if (fsm.state() == 'normal' && e.button == MOUSE_RB) {
downX = e.clientX;
downY = e.clientY;
}
}, false);
container.addEventListener('mousewheel', function(e) {
if (fsm.state() == 'hotbox') {
hotbox.active(Hotbox.STATE_IDLE);
fsm.jump('normal', 'mousemove-blur');
}
}, false);
container.addEventListener('contextmenu', function(e) {
e.preventDefault();
});
container.addEventListener('mouseup', function(e) {
if (fsm.state() != 'normal') {
return;
}
if (e.button != MOUSE_RB || e.clientX != downX || e.clientY != downY) {
return;
}
if (!minder.getSelectedNode()) {
return;
}
fsm.jump('hotbox', 'content-menu');
}, false);
// 阻止热盒事件冒泡,在热盒正确执行前导致热盒关闭
hotbox.$element.addEventListener('mousedown', function(e) {
e.stopPropagation();
});
}
return module.exports = JumpingRuntime;
});

View File

@ -0,0 +1,31 @@
/**
* @fileOverview
*
* 脑图示例运行时
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
var Minder = require('../minder');
function MinderRuntime() {
// 不使用 kityminder 的按键处理,由 ReceiverRuntime 统一处理
var minder = new Minder({
enableKeyReceiver: false,
enableAnimation: true
});
// 渲染,初始化
minder.renderTo(this.selector);
minder.setTheme(null);
minder.select(minder.getRoot(), true);
minder.execCommand('text', '中心主题');
// 导出给其它 Runtime 使用
this.minder = minder;
}
return module.exports = MinderRuntime;
});

View File

@ -0,0 +1,112 @@
define(function(require, exports, module) {
function NodeRuntime() {
var runtime = this;
var minder = this.minder;
var hotbox = this.hotbox;
var fsm = this.fsm;
var main = hotbox.state('main');
var buttons = [
'前移:Alt+Up:ArrangeUp',
'下级:Tab|Insert:AppendChildNode',
'同级:Enter:AppendSiblingNode',
'后移:Alt+Down:ArrangeDown',
'删除:Delete|Backspace:RemoveNode',
'上级:Shift+Tab|Shift+Insert:AppendParentNode'
//'全选:Ctrl+A:SelectAll'
];
var AppendLock = 0;
buttons.forEach(function(button) {
var parts = button.split(':');
var label = parts.shift();
var key = parts.shift();
var command = parts.shift();
main.button({
position: 'ring',
label: label,
key: key,
action: function() {
if (command.indexOf('Append') === 0) {
AppendLock++;
minder.execCommand(command, '分支主题');
// provide in input runtime
function afterAppend () {
if (!--AppendLock) {
runtime.editText();
}
minder.off('layoutallfinish', afterAppend);
}
minder.on('layoutallfinish', afterAppend);
} else {
minder.execCommand(command);
fsm.jump('normal', 'command-executed');
}
},
enable: function() {
return minder.queryCommandState(command) != -1;
}
});
});
main.button({
position: 'bottom',
label: '导入节点',
key: 'Alt + V',
enable: function() {
var selectedNodes = minder.getSelectedNodes();
return selectedNodes.length == 1;
},
action: importNodeData,
next: 'idle'
});
main.button({
position: 'bottom',
label: '导出节点',
key: 'Alt + C',
enable: function() {
var selectedNodes = minder.getSelectedNodes();
return selectedNodes.length == 1;
},
action: exportNodeData,
next: 'idle'
});
function importNodeData() {
minder.fire('importNodeData');
}
function exportNodeData() {
minder.fire('exportNodeData');
}
//main.button({
// position: 'ring',
// key: '/',
// action: function(){
// if (!minder.queryCommandState('expand')) {
// minder.execCommand('expand');
// } else if (!minder.queryCommandState('collapse')) {
// minder.execCommand('collapse');
// }
// },
// enable: function() {
// return minder.queryCommandState('expand') != -1 || minder.queryCommandState('collapse') != -1;
// },
// beforeShow: function() {
// if (!minder.queryCommandState('expand')) {
// this.$button.children[0].innerHTML = '展开';
// } else {
// this.$button.children[0].innerHTML = '收起';
// }
// }
//})
}
return module.exports = NodeRuntime;
});

View File

@ -0,0 +1,51 @@
define(function(require, exports, module){
function PriorityRuntime() {
var minder = this.minder;
var hotbox = this.hotbox;
var main = hotbox.state('main');
main.button({
position: 'top',
label: '优先级',
key: 'P',
next: 'priority',
enable: function() {
return minder.queryCommandState('priority') != -1;
}
});
var priority = hotbox.state('priority');
'123456789'.replace(/./g, function(p) {
priority.button({
position: 'ring',
label: 'P' + p,
key: p,
action: function() {
minder.execCommand('Priority', p);
}
});
});
priority.button({
position: 'center',
label: '移除',
key: 'Del',
action: function() {
minder.execCommand('Priority', 0);
}
});
priority.button({
position: 'top',
label: '返回',
key: 'esc',
next: 'back'
});
}
return module.exports = PriorityRuntime;
});

View File

@ -0,0 +1,52 @@
define(function(require, exports, module){
function ProgressRuntime() {
var minder = this.minder;
var hotbox = this.hotbox;
var main = hotbox.state('main');
main.button({
position: 'top',
label: '进度',
key: 'G',
next: 'progress',
enable: function() {
return minder.queryCommandState('progress') != -1;
}
});
var progress = hotbox.state('progress');
'012345678'.replace(/./g, function(p) {
progress.button({
position: 'ring',
label: 'G' + p,
key: p,
action: function() {
minder.execCommand('Progress', parseInt(p) + 1);
}
});
});
progress.button({
position: 'center',
label: '移除',
key: 'Del',
action: function() {
minder.execCommand('Progress', 0);
}
});
progress.button({
position: 'top',
label: '返回',
key: 'esc',
next: 'back'
});
}
return module.exports = ProgressRuntime;
});

View File

@ -0,0 +1,144 @@
/**
* @fileOverview
*
* 键盘事件接收/分发器
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
var key = require('../tool/key');
var hotbox = require('hotbox');
function ReceiverRuntime() {
var fsm = this.fsm;
var minder = this.minder;
var me = this;
// 接收事件的 div
var element = document.createElement('div');
element.contentEditable = true;
/**
* @Desc: 增加tabindex属性使得element的contenteditable不管是trur还是false都能有focus和blur事件
* @Editor: Naixor
* @Date: 2015.09.14
*/
element.setAttribute("tabindex", -1);
element.classList.add('receiver');
element.onkeydown = element.onkeypress = element.onkeyup = dispatchKeyEvent;
this.container.appendChild(element);
// receiver 对象
var receiver = {
element: element,
selectAll: function() {
// 保证有被选中的
if (!element.innerHTML) element.innerHTML = '&nbsp;';
var range = document.createRange();
var selection = window.getSelection();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
element.focus();
},
/**
* @Desc: 增加enable和disable方法用于解决热核态的输入法屏蔽问题
* @Editor: Naixor
* @Date: 2015.09.14
*/
enable: function() {
element.setAttribute("contenteditable", true);
},
disable: function() {
element.setAttribute("contenteditable", false);
},
/**
* @Desc: hack FF下div contenteditable的光标丢失BUG
* @Editor: Naixor
* @Date: 2015.10.15
*/
fixFFCaretDisappeared: function() {
element.removeAttribute("contenteditable");
element.setAttribute("contenteditable", "true");
element.blur();
element.focus();
},
/**
* 以此事件代替通过mouse事件来判断receiver丢失焦点的事件
* @editor Naixor
* @Date 2015-12-2
*/
onblur: function (handler) {
element.onblur = handler;
}
};
receiver.selectAll();
minder.on('beforemousedown', receiver.selectAll);
minder.on('receiverfocus', receiver.selectAll);
minder.on('readonly', function() {
// 屏蔽minder的事件接受删除receiver和hotbox
minder.disable();
editor.receiver.element.parentElement.removeChild(editor.receiver.element);
editor.hotbox.$container.removeChild(editor.hotbox.$element);
});
// 侦听器,接收到的事件会派发给所有侦听器
var listeners = [];
// 侦听指定状态下的事件,如果不传 state侦听所有状态
receiver.listen = function(state, listener) {
if (arguments.length == 1) {
listener = state;
state = '*';
}
listener.notifyState = state;
listeners.push(listener);
};
function dispatchKeyEvent(e) {
e.is = function(keyExpression) {
var subs = keyExpression.split('|');
for (var i = 0; i < subs.length; i++) {
if (key.is(this, subs[i])) return true;
}
return false;
};
var listener, jumpState;
for (var i = 0; i < listeners.length; i++) {
listener = listeners[i];
// 忽略不在侦听状态的侦听器
if (listener.notifyState != '*' && listener.notifyState != fsm.state()) {
continue;
}
/**
*
* 对于所有的侦听器只允许一种处理方式跳转状态
* 如果侦听器确定要跳转则返回要跳转的状态
* 每个事件只允许一个侦听器进行状态跳转
* 跳转动作由侦听器自行完成因为可能需要在跳转时传递 reason返回跳转结果即可
* 比如
*
* ```js
* receiver.listen('normal', function(e) {
* if (isSomeReasonForJumpState(e)) {
* return fsm.jump('newstate', e);
* }
* });
* ```
*/
if (listener.call(null, e)) {
return;
}
}
}
this.receiver = receiver;
}
return module.exports = ReceiverRuntime;
});

View File

@ -0,0 +1,47 @@
/**
* @fileOverview
*
* 支持各种调试后门
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
var format = require('./format');
function noop() {}
function stringHash(str) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash += str.charCodeAt(i);
}
return hash;
}
/* global console */
function Debug(flag) {
var debugMode = this.flaged = window.location.search.indexOf(flag) != -1;
if (debugMode) {
var h = stringHash(flag) % 360;
var flagStyle = format(
'background: hsl({0}, 50%, 80%); ' +
'color: hsl({0}, 100%, 30%); ' +
'padding: 2px 3px; ' +
'margin: 1px 3px 0 0;' +
'border-radius: 2px;', h);
var textStyle = 'background: none; color: black;';
this.log = function() {
var output = format.apply(null, arguments);
console.log(format('%c{0}%c{1}', flag, output), flagStyle, textStyle);
};
} else {
this.log = noop;
}
}
return module.exports = Debug;
});

View File

@ -0,0 +1,11 @@
define(function(require, exports, module) {
function format(template, args) {
if (typeof(args) != 'object') {
args = [].slice.call(arguments, 1);
}
return String(template).replace(/\{(\w+)\}/ig, function(match, $key) {
return args[$key] || $key;
});
}
return module.exports = format;
});

View File

@ -0,0 +1,54 @@
/**
* @fileOverview
*
* innerText polyfill
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
if ((!('innerText' in document.createElement('a'))) && ('getSelection' in window)) {
HTMLElement.prototype.__defineGetter__('innerText', function() {
var selection = window.getSelection(),
ranges = [],
str, i;
// Save existing selections.
for (i = 0; i < selection.rangeCount; i++) {
ranges[i] = selection.getRangeAt(i);
}
// Deselect everything.
selection.removeAllRanges();
// Select `el` and all child nodes.
// 'this' is the element .innerText got called on
selection.selectAllChildren(this);
// Get the string representation of the selected nodes.
str = selection.toString();
// Deselect everything. Again.
selection.removeAllRanges();
// Restore all formerly existing selections.
for (i = 0; i < ranges.length; i++) {
selection.addRange(ranges[i]);
}
// Oh look, this is what we wanted.
// String representation of the element, close to as rendered.
return str;
});
HTMLElement.prototype.__defineSetter__('innerText', function(text) {
/**
* @Desc: 解决FireFox节点内容删除后text为null出现报错的问题
* @Editor: Naixor
* @Date: 2015.9.16
*/
this.innerHTML = (text || '').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>');
});
}
});

View File

@ -0,0 +1,91 @@
/**
* @fileOverview
*
*
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
/*!
* https://github.com/Starcounter-Jack/Fast-JSON-Patch
* json-patch-duplex.js 0.5.0
* (c) 2013 Joachim Wester
* MIT license
*/
var _objectKeys = (function () {
if (Object.keys)
return Object.keys;
return function (o) {
var keys = [];
for (var i in o) {
if (o.hasOwnProperty(i)) {
keys.push(i);
}
}
return keys;
};
})();
function escapePathComponent(str) {
if (str.indexOf('/') === -1 && str.indexOf('~') === -1)
return str;
return str.replace(/~/g, '~0').replace(/\//g, '~1');
}
function deepClone(obj) {
if (typeof obj === "object") {
return JSON.parse(JSON.stringify(obj));
} else {
return obj;
}
}
// Dirty check if obj is different from mirror, generate patches and update mirror
function _generate(mirror, obj, patches, path) {
var newKeys = _objectKeys(obj);
var oldKeys = _objectKeys(mirror);
var changed = false;
var deleted = false;
for (var t = oldKeys.length - 1; t >= 0; t--) {
var key = oldKeys[t];
var oldVal = mirror[key];
if (obj.hasOwnProperty(key)) {
var newVal = obj[key];
if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null) {
_generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key));
} else {
if (oldVal != newVal) {
changed = true;
patches.push({ op: "replace", path: path + "/" + escapePathComponent(key), value: deepClone(newVal) });
}
}
} else {
patches.push({ op: "remove", path: path + "/" + escapePathComponent(key) });
deleted = true; // property has been deleted
}
}
if (!deleted && newKeys.length == oldKeys.length) {
return;
}
for (var t = 0; t < newKeys.length; t++) {
var key = newKeys[t];
if (!mirror.hasOwnProperty(key)) {
patches.push({ op: "add", path: path + "/" + escapePathComponent(key), value: deepClone(obj[key]) });
}
}
}
function compare(tree1, tree2) {
var patches = [];
_generate(tree1, tree2, patches, '');
return patches;
}
return module.exports = compare;
});

View File

@ -0,0 +1,69 @@
define(function(require, exports, module) {
var keymap = require('./keymap');
var CTRL_MASK = 0x1000;
var ALT_MASK = 0x2000;
var SHIFT_MASK = 0x4000;
function hash(unknown) {
if (typeof(unknown) == 'string') {
return hashKeyExpression(unknown);
}
return hashKeyEvent(unknown);
}
function is(a, b) {
return a && b && hash(a) == hash(b);
}
exports.hash = hash;
exports.is = is;
function hashKeyEvent(keyEvent) {
var hashCode = 0;
if (keyEvent.ctrlKey || keyEvent.metaKey) {
hashCode |= CTRL_MASK;
}
if (keyEvent.altKey) {
hashCode |= ALT_MASK;
}
if (keyEvent.shiftKey) {
hashCode |= SHIFT_MASK;
}
// Shift, Control, Alt KeyCode ignored.
if ([16, 17, 18, 91].indexOf(keyEvent.keyCode) === -1) {
/**
* 解决浏览器输入法状态下对keyDown的keyCode判断不准确的问题,使用keyIdentifier,
* 可以解决chrome和safari下的各种问题,其他浏览器依旧有问题,然而那并不影响我们对特
* 需判断的按键进行判断(比如Space在safari输入法态下就是229,其他的就不是)
* @editor Naixor
* @Date 2015-12-2
*/
if (keyEvent.keyCode === 229 && keyEvent.keyIdentifier) {
return hashCode |= parseInt(keyEvent.keyIdentifier.substr(2), 16);
}
hashCode |= keyEvent.keyCode;
}
return hashCode;
}
function hashKeyExpression(keyExpression) {
var hashCode = 0;
keyExpression.toLowerCase().split(/\s*\+\s*/).forEach(function(name) {
switch(name) {
case 'ctrl':
case 'cmd':
hashCode |= CTRL_MASK;
break;
case 'alt':
hashCode |= ALT_MASK;
break;
case 'shift':
hashCode |= SHIFT_MASK;
break;
default:
hashCode |= keymap[name];
}
});
return hashCode;
}
});

View File

@ -0,0 +1,82 @@
define(function(require, exports, module) {
var keymap = {
'Shift': 16,
'Control': 17,
'Alt': 18,
'CapsLock': 20,
'BackSpace': 8,
'Tab': 9,
'Enter': 13,
'Esc': 27,
'Space': 32,
'PageUp': 33,
'PageDown': 34,
'End': 35,
'Home': 36,
'Insert': 45,
'Left': 37,
'Up': 38,
'Right': 39,
'Down': 40,
'Direction': {
37: 1,
38: 1,
39: 1,
40: 1
},
'Del': 46,
'NumLock': 144,
'Cmd': 91,
'CmdFF': 224,
'F1': 112,
'F2': 113,
'F3': 114,
'F4': 115,
'F5': 116,
'F6': 117,
'F7': 118,
'F8': 119,
'F9': 120,
'F10': 121,
'F11': 122,
'F12': 123,
'`': 192,
'=': 187,
'-': 189,
'/': 191,
'.': 190
};
// 小写适配
for (var key in keymap) {
if (keymap.hasOwnProperty(key)) {
keymap[key.toLowerCase()] = keymap[key];
}
}
var aKeyCode = 65;
var aCharCode = 'a'.charCodeAt(0);
// letters
'abcdefghijklmnopqrstuvwxyz'.split('').forEach(function(letter) {
keymap[letter] = aKeyCode + (letter.charCodeAt(0) - aCharCode);
});
// numbers
var n = 9;
do {
keymap[n.toString()] = n + 48;
} while (--n);
module.exports = keymap;
});

View File

@ -0,0 +1,47 @@
angular.module('kityminderEditor')
.controller('hyperlink.ctrl', function ($scope, $modalInstance, link) {
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]*)?$';
$scope.R_URL = new RegExp(urlRegex, 'i');
$scope.url = link.url || '';
$scope.title = link.title || '';
setTimeout(function() {
var $linkUrl = $('#link-url');
$linkUrl.focus();
$linkUrl[0].setSelectionRange(0, $scope.url.length);
}, 30);
$scope.shortCut = function(e) {
e.stopPropagation();
if (e.keyCode == 13) {
$scope.ok();
} else if (e.keyCode == 27) {
$scope.cancel();
}
};
$scope.ok = function () {
if($scope.R_URL.test($scope.url)) {
$modalInstance.close({
url: $scope.url,
title: $scope.title
});
} else {
$scope.urlPassed = false;
var $linkUrl = $('#link-url');
$linkUrl.focus();
$linkUrl[0].setSelectionRange(0, $scope.url.length);
}
editor.receiver.selectAll();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
editor.receiver.selectAll();
};
});

View File

@ -0,0 +1,26 @@
<div class="modal-header">
<h3 class="modal-title">链接</h3>
</div>
<div class="modal-body">
<form>
<div class="form-group" id="link-url-wrap" ng-class="{true: 'has-success', false: 'has-error'}[urlPassed]">
<label for="link-url">链接地址:</label>
<input type="text"
class="form-control"
ng-model="url"
ng-blur="urlPassed = R_URL.test(url)"
ng-focus="this.value = url"
ng-keydown="shortCut($event)"
id="link-url"
placeholder="必填:以 http(s):// 或 ftp:// 开头">
</div>
<div class="form-group" ng-class="{'has-success' : titlePassed}">
<label for="link-title">提示文本:</label>
<input type="text" class="form-control" ng-model="title" ng-blur="titlePassed = true" id="link-title" placeholder="选填:鼠标在链接上悬停时提示的文本">
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">确定</button>
<button class="btn btn-warning" ng-click="cancel()">取消</button>
</div>

View File

@ -0,0 +1,95 @@
angular.module('kityminderEditor')
.controller('imExportNode.ctrl', function ($scope, $modalInstance, title, defaultValue, type) {
$scope.title = title;
$scope.value = defaultValue;
$scope.type = type;
$scope.ok = function () {
if ($scope.value == '') {
return;
}
$modalInstance.close($scope.value);
editor.receiver.selectAll();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
editor.receiver.selectAll();
};
setTimeout(function() {
$('.single-input').focus();
$('.single-input')[0].setSelectionRange(0, defaultValue.length);
}, 30);
$scope.shortCut = function(e) {
e.stopPropagation();
//if (e.keyCode == 13 && e.shiftKey == false) {
// $scope.ok();
//}
if (e.keyCode == 27) {
$scope.cancel();
}
// tab 键屏蔽默认事件 和 backspace 键屏蔽默认事件
if (e.keyCode == 8 && type == 'export') {
e.preventDefault();
}
if (e.keyCode == 9) {
e.preventDefault();
var $textarea = e.target;
var pos = getCursortPosition($textarea);
var str = $textarea.value;
$textarea.value = str.substr(0, pos) + '\t' + str.substr(pos);
setCaretPosition($textarea, pos + 1);
}
};
/*
* 获取 textarea 的光标位置
* @Author: Naixor
* @date: 2015.09.23
* */
function getCursortPosition (ctrl) {
var CaretPos = 0; // IE Support
if (document.selection) {
ctrl.focus ();
var Sel = document.selection.createRange ();
Sel.moveStart ('character', -ctrl.value.length);
CaretPos = Sel.text.length;
}
// Firefox support
else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
CaretPos = ctrl.selectionStart;
}
return (CaretPos);
}
/*
* 设置 textarea 的光标位置
* @Author: Naixor
* @date: 2015.09.23
* */
function setCaretPosition(ctrl, pos){
if(ctrl.setSelectionRange) {
ctrl.focus();
ctrl.setSelectionRange(pos,pos);
} else if (ctrl.createTextRange) {
var range = ctrl.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
}
});

View File

@ -0,0 +1,16 @@
<div class="modal-header">
<h3 class="modal-title">{{ title }}</h3>
</div>
<div class="modal-body">
<textarea type="text"
class="form-control single-input"
rows="8"
ng-keydown="shortCut($event);"
ng-model="value"
ng-readonly="type === 'export'">
</textarea>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()" ng-disabled="type === 'import' && value == ''">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>

View File

@ -0,0 +1,114 @@
angular.module('kityminderEditor')
.controller('image.ctrl', ['$http', '$scope', '$modalInstance', 'image', 'server', function($http, $scope, $modalInstance, image, server) {
$scope.data = {
list: [],
url: image.url || '',
title: image.title || '',
R_URL: /^https?\:\/\/\w+/
};
setTimeout(function() {
var $imageUrl = $('#image-url');
$imageUrl.focus();
$imageUrl[0].setSelectionRange(0, $scope.data.url.length);
}, 300);
// 搜索图片按钮点击事件
$scope.searchImage = function() {
$scope.list = [];
getImageData()
.success(function(json) {
if(json && json.data) {
for(var i = 0; i < json.data.length; i++) {
if(json.data[i].objURL) {
$scope.list.push({
title: json.data[i].fromPageTitleEnc,
src: json.data[i].middleURL,
url: json.data[i].middleURL
});
}
}
}
})
.error(function() {
});
};
// 选择图片的鼠标点击事件
$scope.selectImage = function($event) {
var targetItem = $('#img-item'+ (this.$index));
var targetImg = $('#img-'+ (this.$index));
targetItem.siblings('.selected').removeClass('selected');
targetItem.addClass('selected');
$scope.data.url = targetImg.attr('src');
$scope.data.title = targetImg.attr('alt');
};
// 自动上传图片,后端需要直接返回图片 URL
$scope.uploadImage = function() {
var fileInput = $('#upload-image');
if (!fileInput.val()) {
return;
}
if (/^.*\.(jpg|JPG|jpeg|JPEG|gif|GIF|png|PNG)$/.test(fileInput.val())) {
var file = fileInput[0].files[0];
return server.uploadImage(file).then(function (json) {
var resp = json.data;
if (resp.errno === 0) {
$scope.data.url = resp.data.url;
}
});
} else {
alert("后缀只能是 jpg、gif 及 png");
}
};
$scope.shortCut = function(e) {
e.stopPropagation();
if (e.keyCode == 13) {
$scope.ok();
} else if (e.keyCode == 27) {
$scope.cancel();
}
};
$scope.ok = function () {
if($scope.data.R_URL.test($scope.data.url)) {
$modalInstance.close({
url: $scope.data.url,
title: $scope.data.title
});
} else {
$scope.urlPassed = false;
var $imageUrl = $('#image-url');
if ($imageUrl) {
$imageUrl.focus();
$imageUrl[0].setSelectionRange(0, $scope.data.url.length);
}
}
editor.receiver.selectAll();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
editor.receiver.selectAll();
};
function getImageData() {
var key = $scope.data.searchKeyword2;
var currentTime = new Date();
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';
return $http.jsonp(url);
}
}]);

View File

@ -0,0 +1,69 @@
<div class="modal-header">
<h3 class="modal-title">图片</h3>
</div>
<div class="modal-body">
<tabset>
<tab heading="图片搜索">
<form class="form-inline">
<div class="form-group">
<label for="search-keyword">关键词:</label>
<input type="text" class="form-control" ng-model="data.searchKeyword2" id="search-keyword" placeholder="请输入搜索的关键词">
</div>
<button class="btn btn-primary" ng-click="searchImage()">百度一下</button>
</form>
<div class="search-result" id="search-result">
<ul>
<li ng-repeat="image in list" id="{{ 'img-item' + $index }}" ng-class="{'selected' : isSelected}" ng-click="selectImage($event)">
<img id="{{ 'img-' + $index }}" ng-src="{{ image.src || '' }}" alt="{{ image.title }}" onerror="this.parentNode.removeChild(this)" />
<span>{{ image.title }}</span>
</li>
</ul>
</div>
</tab>
<tab heading="外链图片">
<form>
<div class="form-group" ng-class="{true: 'has-success', false: 'has-error'}[urlPassed]">
<label for="image-url">链接地址:</label>
<input type="text"
class="form-control"
ng-model="data.url"
ng-blur="urlPassed = data.R_URL.test(data.url)"
ng-focus="this.value = data.url"
ng-keydown="shortCut($event)"
id="image-url"
placeholder="必填:以 http(s):// 开头">
</div>
<div class="form-group" ng-class="{'has-success' : titlePassed}">
<label for="image-title">提示文本:</label>
<input type="text" class="form-control" ng-model="data.title" ng-blur="titlePassed = true" id="image-title" placeholder="选填:鼠标在图片上悬停时提示的文本">
</div>
<div class="form-group">
<label for="image-preview">图片预览:</label>
<img class="image-preview" id="image-preview" ng-src="{{ data.url }}" alt="{{ data.title }}"/>
</div>
</form>
</tab>
<tab heading="上传图片" active="true">
<form>
<div class="form-group">
<!-- 这里不能用 ng-change -->
<input type="file" name="upload-image" id="upload-image" class="upload-image" accept=".jpg,.JPG,jpeg,JPEG,.png,.PNG,.gif,.GIF" onchange="angular.element(this).scope().uploadImage()"/>
<label for="upload-image" class="btn btn-primary"><span>选择文件&hellip;</span></label>
</div>
<div class="form-group" ng-class="{'has-success' : titlePassed}">
<label for="image-title">提示文本:</label>
<input type="text" class="form-control" ng-model="data.title" ng-blur="titlePassed = true" id="image-title" placeholder="选填:鼠标在图片上悬停时提示的文本">
</div>
<div class="form-group">
<label for="image-preview">图片预览:</label>
<img class="image-preview" id="image-preview" ng-src="{{ data.url }}" title="{{ data.title }}" alt="{{ data.title }}"/>
</div>
</form>
</tab>
</tabset>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">确定</button>
<button class="btn btn-warning" ng-click="cancel()">取消</button>
</div>

View File

@ -0,0 +1,31 @@
angular.module('kityminderEditor')
.directive('appendNode', ['commandBinder', function(commandBinder) {
return {
restrict: 'E',
templateUrl: 'ui/directive/appendNode/appendNode.html',
scope: {
minder: '='
},
replace: true,
link: function($scope) {
var minder = $scope.minder;
commandBinder.bind(minder, 'appendchildnode', $scope)
$scope.execCommand = function(command) {
minder.execCommand(command, '分支主题');
editText();
};
function editText() {
var receiverElement = editor.receiver.element;
var fsm = editor.fsm;
var receiver = editor.receiver;
receiverElement.innerText = minder.queryCommandValue('text');
fsm.jump('input', 'input-request');
receiver.selectAll();
}
}
}
}]);

View File

@ -0,0 +1,23 @@
<div class="km-btn-group append-group">
<div class="km-btn-item append-child-node"
ng-disabled="minder.queryCommandState('AppendChildNode') === -1"
ng-click="minder.queryCommandState('AppendChildNode') === -1 || execCommand('AppendChildNode')"
title="{{ 'appendchildnode' | lang:'ui/command' }}">
<i class="km-btn-icon"></i>
<span class="km-btn-caption">{{ 'appendchildnode' | lang:'ui/command' }}</span>
</div>
<div class="km-btn-item append-parent-node"
ng-disabled="minder.queryCommandState('AppendParentNode') === -1"
ng-click="minder.queryCommandState('AppendParentNode') === -1 || execCommand('AppendParentNode')"
title="{{ 'appendparentnode' | lang:'ui/command' }}">
<i class="km-btn-icon"></i>
<span class="km-btn-caption">{{ 'appendparentnode' | lang:'ui/command' }}</span>
</div>
<div class="km-btn-item append-sibling-node"
ng-disabled="minder.queryCommandState('AppendSiblingNode') === -1"
ng-click="minder.queryCommandState('AppendSiblingNode') === -1 ||execCommand('AppendSiblingNode')"
title="{{ 'appendsiblingnode' | lang:'ui/command' }}">
<i class="km-btn-icon"></i>
<span class="km-btn-caption">{{ 'appendsiblingnode' | lang:'ui/command' }}</span>
</div>
</div>

View File

@ -0,0 +1,16 @@
angular.module('kityminderEditor')
.directive('arrange', ['commandBinder', function(commandBinder) {
return {
restrict: 'E',
templateUrl: 'ui/directive/arrange/arrange.html',
scope: {
minder: '='
},
replace: true,
link: function($scope) {
var minder = $scope.minder;
//commandBinder.bind(minder, 'priority', $scope);
}
}
}]);

View File

@ -0,0 +1,16 @@
<div class="km-btn-group arrange-group">
<div class="km-btn-item arrange-up"
ng-disabled="minder.queryCommandState('ArrangeUp') === -1"
ng-click="minder.queryCommandState('ArrangeUp') === -1 || minder.execCommand('ArrangeUp')"
title="{{ 'arrangeup' | lang:'ui/command' }}">
<i class="km-btn-icon"></i>
<span class="km-btn-caption">{{ 'arrangeup' | lang:'ui/command' }}</span>
</div>
<div class="km-btn-item arrange-down"
ng-disabled="minder.queryCommandState('ArrangeDown') === -1"
ng-click="minder.queryCommandState('ArrangeDown') === -1 || minder.execCommand('ArrangeDown');"
title="{{ 'arrangedown' | lang:'ui/command' }}">
<i class="km-btn-icon"></i>
<span class="km-btn-caption">{{ 'arrangedown' | lang:'ui/command' }}</span>
</div>
</div>

View File

@ -0,0 +1,33 @@
angular.module('kityminderEditor')
.directive('colorPanel', function() {
return {
restrict: 'E',
templateUrl: 'ui/directive/colorPanel/colorPanel.html',
scope: {
minder: '='
},
replace: true,
link: function(scope) {
var minder = scope.minder;
var currentTheme = minder.getThemeItems();
scope.$on('colorPicked', function(event, color) {
event.stopPropagation();
scope.bgColor = color;
minder.execCommand('background', color);
});
scope.setDefaultBg = function() {
var currentNode = minder.getSelectedNode();
var bgColor = minder.getNodeStyle(currentNode, 'background');
// 有可能是 kity 的颜色类
return typeof bgColor === 'object' ? bgColor.toHEX() : bgColor;
};
scope.bgColor = scope.setDefaultBg() || '#fff';
}
}
});

View File

@ -0,0 +1,16 @@
<div class="bg-color-wrap">
<span class="quick-bg-color"
ng-click="minder.queryCommandState('background') === -1 || minder.execCommand('background', bgColor)"
ng-disabled="minder.queryCommandState('background') === -1"></span>
<span color-picker
class="bg-color"
set-color="setDefaultBg()"
ng-disabled="minder.queryCommandState('background') === -1">
<span class="caret"></span>
</span>
<span class="bg-color-preview"
ng-style="{ 'background-color': bgColor }"
ng-click="minder.queryCommandState('background') === -1 || minder.execCommand('background', bgColor)"
ng-disabled="minder.queryCommandState('background') === -1"></span>
</div>

View File

@ -0,0 +1,15 @@
angular.module('kityminderEditor')
.directive('expandLevel', function() {
return {
restrict: 'E',
templateUrl: 'ui/directive/expandLevel/expandLevel.html',
scope: {
minder: '='
},
replace: true,
link: function($scope) {
$scope.levels = [1, 2, 3, 4, 5, 6];
}
}
});

View File

@ -0,0 +1,21 @@
<div class="btn-group-vertical" dropdown is-open="isopen">
<button type="button"
class="btn btn-default expand"
title="{{ 'expandtoleaf' | lang:'ui' }}"
ng-class="{'active': isopen}"
ng-click="minder.execCommand('ExpandToLevel', 9999)"></button>
<button type="button"
class="btn btn-default expand-caption dropdown-toggle"
title="{{ 'expandtoleaf' | lang:'ui' }}"
dropdown-toggle>
<span class="caption">{{ 'expandtoleaf' | lang:'ui' }}</span>
<span class="caret"></span>
<span class="sr-only">{{ 'expandtoleaf' | lang:'ui' }}</span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="level in levels">
<a href
ng-click="minder.execCommand('ExpandToLevel', level)">{{ 'expandtolevel' + level | lang:'ui/command' }}</a>
</li>
</ul>
</div>

View File

@ -0,0 +1,83 @@
angular.module('kityminderEditor')
.directive('fontOperator', function() {
return {
restrict: 'E',
templateUrl: 'ui/directive/fontOperator/fontOperator.html',
scope: {
minder: '='
},
replace: true,
link: function(scope) {
var minder = scope.minder;
var currentTheme = minder.getThemeItems();
scope.fontSizeList = [10, 12, 16, 18, 24, 32, 48];
scope.fontFamilyList = [{
name: '宋体',
val: '宋体,SimSun'
}, {
name: '微软雅黑',
val: '微软雅黑,Microsoft YaHei'
}, {
name: '楷体',
val: '楷体,楷体_GB2312,SimKai'
}, {
name: '黑体',
val: '黑体, SimHei'
}, {
name: '隶书',
val: '隶书, SimLi'
}, {
name: 'Andale Mono',
val: 'andale mono'
}, {
name: 'Arial',
val: 'arial,helvetica,sans-serif'
}, {
name: 'arialBlack',
val: 'arial black,avant garde'
}, {
name: 'Comic Sans Ms',
val: 'comic sans ms'
}, {
name: 'Impact',
val: 'impact,chicago'
}, {
name: 'Times New Roman',
val: 'times new roman'
}, {
name: 'Sans-Serif',
val: 'sans-serif'
}];
scope.$on('colorPicked', function(event, color) {
event.stopPropagation();
scope.foreColor = color;
minder.execCommand('forecolor', color);
});
scope.setDefaultColor = function() {
var currentNode = minder.getSelectedNode();
var fontColor = minder.getNodeStyle(currentNode, 'color');
// 有可能是 kity 的颜色类
return typeof fontColor === 'object' ? fontColor.toHEX() : fontColor;
};
scope.foreColor = scope.setDefaultColor() || '#000';
scope.getFontfamilyName = function(val) {
var fontName = '';
scope.fontFamilyList.forEach(function(ele, idx, arr) {
if (ele.val === val) {
fontName = ele.name;
return '';
}
});
return fontName;
}
}
}
});

View File

@ -0,0 +1,51 @@
<div class="font-operator">
<div class="dropdown font-family-list" dropdown>
<div class="dropdown-toggle current-font-item" dropdown-toggle ng-disabled="minder.queryCommandState('fontfamily') === -1">
<a href class="current-font-family" title="{{ 'fontfamily' | lang: 'ui' }}">{{ getFontfamilyName(minder.queryCommandValue('fontfamily')) || '字体' }}</a>
<span class="caret"></span>
</div>
<ul class="dropdown-menu font-list">
<li ng-repeat="f in fontFamilyList" class="font-item-wrap">
<a ng-click="minder.execCommand('fontfamily', f.val)" class="font-item" ng-class="{ 'font-item-selected' : f == minder.queryCommandValue('fontfamily') }" ng-style="{'font-family': f.val }">{{ f.name }}</a>
</li>
</ul>
</div>
<div class="dropdown font-size-list" dropdown>
<div class="dropdown-toggle current-font-item" dropdown-toggle ng-disabled="minder.queryCommandState('fontsize') === -1">
<a href class="current-font-size" title="{{ 'fontsize' | lang: 'ui' }}">{{ minder.queryCommandValue('fontsize') || '字号' }}</a>
<span class="caret"></span>
</div>
<ul class="dropdown-menu font-list">
<li ng-repeat="f in fontSizeList" class="font-item-wrap">
<a ng-click="minder.execCommand('fontsize', f)" class="font-item" ng-class="{ 'font-item-selected' : f == minder.queryCommandValue('fontsize') }" ng-style="{'font-size': f + 'px'}">{{ f }}</a>
</li>
</ul>
</div>
<span class="s-btn-icon font-bold"
ng-click="minder.queryCommandState('bold') === -1 || minder.execCommand('bold')"
ng-class="{'font-bold-selected' : minder.queryCommandState('bold') == 1}"
ng-disabled="minder.queryCommandState('bold') === -1"></span>
<span class="s-btn-icon font-italics"
ng-click="minder.queryCommandState('italic') === -1 || minder.execCommand('italic')"
ng-class="{'font-italics-selected' : minder.queryCommandState('italic') == 1}"
ng-disabled="minder.queryCommandState('italic') === -1">
</span>
<div class="font-color-wrap">
<span class="quick-font-color"
ng-click="minder.queryCommandState('forecolor') === -1 || minder.execCommand('forecolor', foreColor)"
ng-disabled="minder.queryCommandState('forecolor') === -1">A</span>
<span color-picker
class="font-color"
set-color="setDefaultColor()"
ng-disabled="minder.queryCommandState('forecolor') === -1">
<span class="caret"></span>
</span>
<span class="font-color-preview"
ng-style="{ 'background-color': foreColor }"
ng-click="minder.queryCommandState('forecolor') === -1 || minder.execCommand('forecolor', foreColor)"
ng-disabled="minder.queryCommandState('forecolor') === -1"></span>
</div>
<color-panel minder="minder" class="inline-directive"></color-panel>
</div>

View File

@ -0,0 +1,35 @@
angular.module('kityminderEditor')
.directive('hyperLink', ['$modal', function($modal) {
return {
restrict: 'E',
templateUrl: 'ui/directive/hyperLink/hyperLink.html',
scope: {
minder: '='
},
replace: true,
link: function($scope) {
var minder = $scope.minder;
$scope.addHyperlink = function() {
var link = minder.queryCommandValue('HyperLink');
var hyperlinkModal = $modal.open({
animation: true,
templateUrl: 'ui/dialog/hyperlink/hyperlink.tpl.html',
controller: 'hyperlink.ctrl',
size: 'md',
resolve: {
link: function() {
return link;
}
}
});
hyperlinkModal.result.then(function(result) {
minder.execCommand('HyperLink', result.url, result.title || '');
});
}
}
}
}]);

View File

@ -0,0 +1,27 @@
<div class="btn-group-vertical" dropdown is-open="isopen">
<button type="button"
class="btn btn-default hyperlink"
title="{{ 'link' | lang:'ui' }}"
ng-class="{'active': isopen}"
ng-click="addHyperlink()"
ng-disabled="minder.queryCommandState('HyperLink') === -1"></button>
<button type="button"
class="btn btn-default hyperlink-caption dropdown-toggle"
ng-disabled="minder.queryCommandState('HyperLink') === -1"
title="{{ 'link' | lang:'ui' }}"
dropdown-toggle>
<span class="caption">{{ 'link' | lang:'ui' }}</span>
<span class="caret"></span>
<span class="sr-only">{{ 'link' | lang:'ui' }}</span>
</button>
<ul class="dropdown-menu" role="menu">
<li>
<a href
ng-click="addHyperlink()">{{ 'insertlink' | lang:'ui' }}</a>
</li>
<li>
<a href
ng-click="minder.execCommand('HyperLink', null)">{{ 'removelink' | lang:'ui' }}</a>
</li>
</ul>
</div>

View File

@ -0,0 +1,35 @@
angular.module('kityminderEditor')
.directive('imageBtn', ['$modal', function($modal) {
return {
restrict: 'E',
templateUrl: 'ui/directive/imageBtn/imageBtn.html',
scope: {
minder: '='
},
replace: true,
link: function($scope) {
var minder = $scope.minder;
$scope.addImage = function() {
var image = minder.queryCommandValue('image');
var imageModal = $modal.open({
animation: true,
templateUrl: 'ui/dialog/image/image.tpl.html',
controller: 'image.ctrl',
size: 'md',
resolve: {
image: function() {
return image;
}
}
});
imageModal.result.then(function(result) {
minder.execCommand('image', result.url, result.title || '');
});
}
}
}
}]);

View File

@ -0,0 +1,27 @@
<div class="btn-group-vertical" dropdown is-open="isopen">
<button type="button"
class="btn btn-default image-btn"
title="{{ 'image' | lang:'ui' }}"
ng-class="{'active': isopen}"
ng-click="addImage()"
ng-disabled="minder.queryCommandState('Image') === -1"></button>
<button type="button"
class="btn btn-default image-btn-caption dropdown-toggle"
ng-disabled="minder.queryCommandState('Image') === -1"
title="{{ 'image' | lang:'ui' }}"
dropdown-toggle>
<span class="caption">{{ 'image' | lang:'ui' }}</span>
<span class="caret"></span>
<span class="sr-only">{{ 'image' | lang:'ui' }}</span>
</button>
<ul class="dropdown-menu" role="menu">
<li>
<a href
ng-click="addImage()">{{ 'insertimage' | lang:'ui' }}</a>
</li>
<li>
<a href
ng-click="minder.execCommand('Image', '')">{{ 'removeimage' | lang:'ui' }}</a>
</li>
</ul>
</div>

View File

@ -0,0 +1,71 @@
angular.module('kityminderEditor')
.directive('kityminderEditor', ['config', 'minder.service', 'revokeDialog', function(config, minderService, revokeDialog) {
return {
restrict: 'EA',
templateUrl: 'ui/directive/kityminderEditor/kityminderEditor.html',
replace: true,
scope: {
onInit: '&'
},
link: function(scope, element, attributes) {
var $minderEditor = element.children('.minder-editor')[0];
function onInit(editor, minder) {
scope.onInit({
editor: editor,
minder: minder
});
minderService.executeCallback();
}
if (typeof(seajs) != 'undefined') {
/* global seajs */
seajs.config({
base: './src'
});
define('demo', function(require) {
var Editor = require('editor');
var editor = window.editor = new Editor($minderEditor);
if (window.localStorage.__dev_minder_content) {
editor.minder.importJson(JSON.parse(window.localStorage.__dev_minder_content));
}
editor.minder.on('contentchange', function() {
window.localStorage.__dev_minder_content = JSON.stringify(editor.minder.exportJson());
});
window.minder = window.km = editor.minder;
scope.editor = editor;
scope.minder = minder;
scope.config = config.get();
//scope.minder.setDefaultOptions(scope.config);
scope.$apply();
onInit(editor, minder);
});
seajs.use('demo');
} else if (window.kityminder && window.kityminder.Editor) {
var editor = new kityminder.Editor($minderEditor);
window.editor = scope.editor = editor;
window.minder = scope.minder = editor.minder;
scope.config = config.get();
//scope.minder.setDefaultOptions(config.getConfig());
onInit(editor, editor.minder);
}
}
}
}]);

View File

@ -0,0 +1,8 @@
<div class="minder-editor-container">
<div class="top-tab" top-tab="minder" editor="editor" ng-if="minder" ></div>
<div search-box minder="minder" ng-if="minder"></div>
<div class="minder-editor"></div>
<div class="km-note" note-editor minder="minder" ng-if="minder"></div>
<div class="note-previewer" note-previewer ng-if="minder"></div>
<div class="navigator" navigator minder="minder" ng-if="minder"></div>
</div>

View File

@ -0,0 +1,34 @@
angular.module('kityminderEditor')
.directive('kityminderViewer', ['config', 'minder.service', function(config, minderService) {
return {
restrict: 'EA',
templateUrl: 'ui/directive/kityminderViewer/kityminderViewer.html',
replace: true,
scope: {
onInit: '&'
},
link: function(scope, element, attributes) {
var $minderEditor = element.children('.minder-viewer')[0];
function onInit(editor, minder) {
scope.onInit({
editor: editor,
minder: minder
});
minderService.executeCallback();
}
if (window.kityminder && window.kityminder.Editor) {
var editor = new kityminder.Editor($minderEditor);
window.editor = scope.editor = editor;
window.minder = scope.minder = editor.minder;
onInit(editor, editor.minder);
}
}
}
}]);

View File

@ -0,0 +1,5 @@
<div class="minder-editor-container">
<div class="minder-viewer"></div>
<div class="note-previewer" note-previewer ng-if="minder"></div>
<div class="navigator" navigator minder="minder" ng-if="minder"></div>
</div>

View File

@ -0,0 +1,14 @@
angular.module('kityminderEditor')
.directive('layout', function() {
return {
restrict: 'E',
templateUrl: 'ui/directive/layout/layout.html',
scope: {
minder: '='
},
replace: true,
link: function(scope) {
}
}
});

View File

@ -0,0 +1,6 @@
<div class="readjust-layout">
<a ng-click="minder.queryCommandState('resetlayout') === -1 || minder.execCommand('resetlayout')" class="btn-wrap" ng-disabled="minder.queryCommandState('resetlayout') === -1">
<span class="btn-icon reset-layout-icon"></span>
<span class="btn-label">{{ 'resetlayout' | lang: 'ui/command' }}</span>
</a>
</div>

View File

@ -0,0 +1,221 @@
/**
* @fileOverview
*
* 左下角的导航器
*
* @author: zhangbobell
* @email : zhangbobell@163.com
*
* @copyright: Baidu FEX, 2015 */
angular.module('kityminderEditor')
.directive('navigator', ['memory', 'config', function(memory, config) {
return {
restrict: 'A',
templateUrl: 'ui/directive/navigator/navigator.html',
scope: {
minder: '='
},
link: function(scope) {
minder.setDefaultOptions({zoom: config.get('zoom')});
scope.isNavOpen = !memory.get('navigator-hidden');
scope.getZoomRadio = function(value) {
var zoomStack = minder.getOption('zoom');
var minValue = zoomStack[0];
var maxValue = zoomStack[zoomStack.length - 1];
var valueRange = maxValue - minValue;
return (1 - (value - minValue) / valueRange);
};
scope.getHeight = function(value) {
var totalHeight = $('.zoom-pan').height();
return scope.getZoomRadio(value) * totalHeight;
};
// 初始的缩放倍数
scope.zoom = 100;
// 发生缩放事件时
minder.on('zoom', function(e) {
scope.zoom = e.zoom;
});
scope.toggleNavOpen = function() {
scope.isNavOpen = !scope.isNavOpen;
memory.set('navigator-hidden', !scope.isNavOpen);
if (scope.isNavOpen) {
bind();
updateContentView();
updateVisibleView();
} else{
unbind();
}
};
setTimeout(function() {
if (scope.isNavOpen) {
bind();
updateContentView();
updateVisibleView();
} else{
unbind();
}
}, 0);
function bind() {
minder.on('layout layoutallfinish', updateContentView);
minder.on('viewchange', updateVisibleView);
}
function unbind() {
minder.off('layout layoutallfinish', updateContentView);
minder.off('viewchange', updateVisibleView);
}
/** *
* */
var $previewNavigator = $('.nav-previewer');
// 画布,渲染缩略图
var paper = new kity.Paper($previewNavigator[0]);
// 用两个路径来挥之节点和连线的缩略图
var nodeThumb = paper.put(new kity.Path());
var connectionThumb = paper.put(new kity.Path());
// 表示可视区域的矩形
var visibleRect = paper.put(new kity.Rect(100, 100).stroke('red', '1%'));
var contentView = new kity.Box(), visibleView = new kity.Box();
/**
* 增加一个对天盘图情况缩略图的处理,
* @Editor: Naixor line 104~129
* @Date: 2015.11.3
*/
var pathHandler = getPathHandler(minder.getTheme());
// 主题切换事件
minder.on('themechange', function(e) {
pathHandler = getPathHandler(e.theme);
});
function getPathHandler(theme) {
switch (theme) {
case "tianpan":
case "tianpan-compact":
return function(nodePathData, x, y, width, height) {
var r = width >> 1;
nodePathData.push('M', x, y + r,
'a', r, r, 0, 1, 1, 0, 0.01,
'z');
}
default: {
return function(nodePathData, x, y, width, height) {
nodePathData.push('M', x, y,
'h', width, 'v', height,
'h', -width, 'z');
}
}
}
}
navigate();
function navigate() {
function moveView(center, duration) {
var box = visibleView;
center.x = -center.x;
center.y = -center.y;
var viewMatrix = minder.getPaper().getViewPortMatrix();
box = viewMatrix.transformBox(box);
var targetPosition = center.offset(box.width / 2, box.height / 2);
minder.getViewDragger().moveTo(targetPosition, duration);
}
var dragging = false;
paper.on('mousedown', function(e) {
dragging = true;
moveView(e.getPosition('top'), 200);
$previewNavigator.addClass('grab');
});
paper.on('mousemove', function(e) {
if (dragging) {
moveView(e.getPosition('top'));
}
});
$(window).on('mouseup', function() {
dragging = false;
$previewNavigator.removeClass('grab');
});
}
function updateContentView() {
var view = minder.getRenderContainer().getBoundaryBox();
contentView = view;
var padding = 30;
paper.setViewBox(
view.x - padding - 0.5,
view.y - padding - 0.5,
view.width + padding * 2 + 1,
view.height + padding * 2 + 1);
var nodePathData = [];
var connectionThumbData = [];
minder.getRoot().traverse(function(node) {
var box = node.getLayoutBox();
pathHandler(nodePathData, box.x, box.y, box.width, box.height);
if (node.getConnection() && node.parent && node.parent.isExpanded()) {
connectionThumbData.push(node.getConnection().getPathData());
}
});
paper.setStyle('background', minder.getStyle('background'));
if (nodePathData.length) {
nodeThumb
.fill(minder.getStyle('root-background'))
.setPathData(nodePathData);
} else {
nodeThumb.setPathData(null);
}
if (connectionThumbData.length) {
connectionThumb
.stroke(minder.getStyle('connect-color'), '0.5%')
.setPathData(connectionThumbData);
} else {
connectionThumb.setPathData(null);
}
updateVisibleView();
}
function updateVisibleView() {
visibleView = minder.getViewDragger().getView();
visibleRect.setBox(visibleView.intersect(contentView));
}
}
}
}]);

View File

@ -0,0 +1,42 @@
<div class="nav-bar">
<div class="nav-btn zoom-in"
ng-click="minder.execCommand('zoomIn')"
title="{{ 'zoom-in' | lang : 'ui' }}"
ng-class="{ 'active' : getZoomRadio(zoom) == 0 }">
<div class="icon"></div>
</div>
<div class="zoom-pan">
<div class="origin"
ng-style="{'transform': 'translate(0, ' + getHeight(100) + 'px)'}"
ng-click="minder.execCommand('zoom', 100);"></div>
<div class="indicator"
ng-style="{
'transform': 'translate(0, ' + getHeight(zoom) + 'px)',
'transition': 'transform 200ms'
}"></div>
</div>
<div class="nav-btn zoom-out"
ng-click="minder.execCommand('zoomOut')"
title="{{ 'zoom-out' | lang : 'ui' }}"
ng-class="{ 'active' : getZoomRadio(zoom) == 1 }">
<div class="icon"></div>
</div>
<div class="nav-btn hand"
ng-click="minder.execCommand('hand')"
title="{{ 'hand' | lang : 'ui' }}"
ng-class="{ 'active' : minder.queryCommandState('hand') == 1 }">
<div class="icon"></div>
</div>
<div class="nav-btn camera"
ng-click="minder.execCommand('camera', minder.getRoot(), 600);"
title="{{ 'camera' | lang : 'ui' }}">
<div class="icon"></div>
</div>
<div class="nav-btn nav-trigger"
ng-class="{'active' : isNavOpen}"
ng-click="toggleNavOpen()"
title="{{ 'navigator' | lang : 'ui' }}">
<div class="icon"></div>
</div>
</div>
<div class="nav-previewer" ng-show="isNavOpen"></div>

View File

@ -0,0 +1,18 @@
angular.module('kityminderEditor')
.directive('noteBtn', ['valueTransfer', function(valueTransfer) {
return {
restrict: 'E',
templateUrl: 'ui/directive/noteBtn/noteBtn.html',
scope: {
minder: '='
},
replace: true,
link: function($scope) {
var minder = $scope.minder;
$scope.addNote =function() {
valueTransfer.noteEditorOpen = true;
};
}
}
}]);

View File

@ -0,0 +1,27 @@
<div class="btn-group-vertical note-btn-group" dropdown is-open="isopen">
<button type="button"
class="btn btn-default note-btn"
title="{{ 'note' | lang:'ui' }}"
ng-class="{'active': isopen}"
ng-click="addNote()"
ng-disabled="minder.queryCommandState('note') === -1"></button>
<button type="button"
class="btn btn-default note-btn-caption dropdown-toggle"
ng-disabled="minder.queryCommandState('note') === -1"
title="{{ 'note' | lang:'ui' }}"
dropdown-toggle>
<span class="caption">{{ 'note' | lang:'ui' }}</span>
<span class="caret"></span>
<span class="sr-only">{{ 'note' | lang:'ui' }}</span>
</button>
<ul class="dropdown-menu" role="menu">
<li>
<a href
ng-click="addNote()">{{ 'insertnote' | lang:'ui' }}</a>
</li>
<li>
<a href
ng-click="minder.execCommand('note', null)">{{ 'removenote' | lang:'ui' }}</a>
</li>
</ul>
</div>

View File

@ -0,0 +1,76 @@
angular.module('kityminderEditor')
.directive('noteEditor', ['valueTransfer', function(valueTransfer) {
return {
restrict: 'A',
templateUrl: 'ui/directive/noteEditor/noteEditor.html',
scope: {
minder: '='
},
replace: true,
controller: function($scope) {
var minder = $scope.minder;
var isInteracting = false;
var cmEditor;
$scope.codemirrorLoaded = function(_editor) {
cmEditor = $scope.cmEditor = _editor;
_editor.setSize('100%', '100%');
};
function updateNote() {
var enabled = $scope.noteEnabled = minder.queryCommandState('note') != -1;
var noteValue = minder.queryCommandValue('note') || '';
if (enabled) {
$scope.noteContent = noteValue;
}
isInteracting = true;
$scope.$apply();
isInteracting = false;
}
$scope.$watch('noteContent', function(content) {
var enabled = minder.queryCommandState('note') != -1;
if (content && enabled && !isInteracting) {
minder.execCommand('note', content);
}
setTimeout(function() {
cmEditor.refresh();
});
});
var noteEditorOpen = function() {
return valueTransfer.noteEditorOpen;
};
// 监听面板状态变量的改变
$scope.$watch(noteEditorOpen, function(newVal, oldVal) {
if (newVal) {
setTimeout(function() {
cmEditor.refresh();
cmEditor.focus();
});
}
$scope.noteEditorOpen = valueTransfer.noteEditorOpen;
}, true);
$scope.closeNoteEditor = function() {
valueTransfer.noteEditorOpen = false;
editor.receiver.selectAll();
};
minder.on('interactchange', updateNote);
}
}
}]);

Some files were not shown because too many files have changed in this diff Show More