增加百度脑图功能集成
This commit is contained in:
parent
5d76d6cbb6
commit
eede8c4f68
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"directory": "bower_components",
|
||||
"allow_root": true,
|
||||
"registry": "https://registry.bower.io"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
.idea
|
||||
.DS_Store
|
||||
bower_components/
|
||||
node_modules/
|
||||
dist/
|
||||
ui/templates.js
|
||||
.tmp/
|
||||
upload/
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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']);
|
||||
};
|
|
@ -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.
|
||||
|
|
@ -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
|
|
@ -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 |
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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";
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.upload-image {
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
.do-group {
|
||||
width: 38px;
|
||||
}
|
||||
|
||||
.undo {
|
||||
.km-btn-icon {
|
||||
background-position: 0 -1240px;
|
||||
}
|
||||
}
|
||||
|
||||
.redo {
|
||||
.km-btn-icon {
|
||||
background-position: 0 -1220px;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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 |
|
@ -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);
|
|
@ -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!');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
});
|
|
@ -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');
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
define(function(require, exports, module) {
|
||||
return module.exports = window.HotBox;
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
define(function(require, exports, module) {
|
||||
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
define(function(require, exports, module) {
|
||||
return module.exports = window.kityminder.Minder;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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处理成为Array,ie8及以下会有问题
|
||||
* @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下会莫名其妙的加上 影响后续判断,干掉!
|
||||
/**
|
||||
* FF下的wholeText会导致如下问题:
|
||||
* |123| -> 在一个节点中输入一段字符,此时TextNode为[#Text 123]
|
||||
* 提交并重新编辑,在后面追加几个字符
|
||||
* |123abc| -> 此时123为一个TextNode为[#Text 123, #Text abc],但是对这两个任意取值wholeText均为全部内容123abc
|
||||
* 上述BUG仅存在在FF中,故将wholeText更改为textContent
|
||||
*/
|
||||
str = str.textContent.replace(" ", " ");
|
||||
|
||||
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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
|
||||
});
|
|
@ -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;
|
||||
|
||||
});
|
|
@ -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 = ' ';
|
||||
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;
|
||||
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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, '<').replace(/>/g, '>').replace(/\n/g, '<br>');
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
});
|
|
@ -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;
|
||||
});
|
|
@ -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();
|
||||
};
|
||||
|
||||
});
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}]);
|
|
@ -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>选择文件…</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>
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}]);
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}]);
|
|
@ -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>
|
|
@ -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';
|
||||
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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>
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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>
|
|
@ -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 || '');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}]);
|
|
@ -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>
|
|
@ -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 || '');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}]);
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}]);
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}]);
|
|
@ -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>
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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>
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}]);
|
|
@ -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>
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
}]);
|
|
@ -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>
|
|
@ -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
Loading…
Reference in New Issue