├── assets ├── styles │ ├── ui.less │ ├── base.less │ ├── scrollbar.less │ └── theme │ │ ├── android.less │ │ └── pc.less └── theme │ └── default │ ├── images │ ├── up.png │ ├── close.png │ ├── down.png │ └── open.png │ └── fui.min.css ├── dist ├── assets │ └── styles │ │ ├── ui.css │ │ ├── base.css │ │ ├── scrollbar.css │ │ └── theme │ │ ├── android.css │ │ └── pc.css └── resource │ ├── KF_AMS_BB.woff │ ├── KF_AMS_CAL.woff │ ├── KF_AMS_FRAK.woff │ ├── KF_AMS_MAIN.woff │ └── KF_AMS_ROMAN.woff ├── .babelrc ├── images ├── web.png └── android.png ├── dev-lib ├── dev-start.js ├── exports.js ├── npmEntry.js ├── start.js ├── jhtmls.min.js └── dev-define.js ├── resource ├── KF_AMS_BB.woff ├── KF_AMS_CAL.woff ├── KF_AMS_FRAK.woff ├── KF_AMS_MAIN.woff └── KF_AMS_ROMAN.woff ├── src ├── jquery.js ├── kf.js ├── kity.js ├── ui │ ├── ui-impl │ │ ├── def │ │ │ ├── item-type.js │ │ │ ├── box-type.js │ │ │ └── ele-type.js │ │ ├── ui.js │ │ ├── keyboard │ │ │ ├── const.js │ │ │ ├── footer │ │ │ │ └── index.js │ │ │ ├── menu │ │ │ │ └── index.js │ │ │ ├── page │ │ │ │ └── index.js │ │ │ ├── panel │ │ │ │ ├── index.js │ │ │ │ ├── pcConst.js │ │ │ │ ├── androidConst.js │ │ │ │ └── position.data.js │ │ │ └── keyboard.js │ │ ├── delimiter.js │ │ ├── drapdown-box.js │ │ ├── ui-utils.js │ │ ├── list.js │ │ └── button.js │ ├── def.js │ ├── header │ │ └── header.js │ ├── control │ │ └── zoom.js │ ├── keyboard │ │ └── keyboard.js │ └── toolbar │ │ └── toolbar.js ├── def │ └── group-type.js ├── kf-ext │ ├── def.js │ ├── extension.js │ ├── expression │ │ └── placeholder.js │ └── operator │ │ └── placeholder.js ├── bundle.js ├── base │ ├── component.js │ ├── utils.js │ ├── event │ │ ├── kfevent.js │ │ └── event.js │ └── common.js ├── parse │ ├── vgroup-def.js │ └── parser.js ├── sysconf.js ├── control │ ├── input-filter.js │ ├── controller.js │ ├── listener.js │ └── location.js ├── editor │ ├── Message.js │ ├── factory.js │ ├── command.md │ └── editor.js ├── print │ └── printer.js ├── syntax │ └── delete.js └── position │ └── position.js ├── config └── conf.js ├── .jshintrc ├── LICENSE ├── README.md ├── package.json ├── .gitignore ├── index.html └── Gruntfile.js /assets/styles/ui.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/assets/styles/ui.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "script", 3 | "presets": ["@babel/preset-env"] 4 | } 5 | -------------------------------------------------------------------------------- /images/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/images/web.png -------------------------------------------------------------------------------- /images/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/images/android.png -------------------------------------------------------------------------------- /dev-lib/dev-start.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 13-12-4. 3 | */ 4 | // 启动脚本 5 | inc.use( 'kf.start' ); -------------------------------------------------------------------------------- /resource/KF_AMS_BB.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/resource/KF_AMS_BB.woff -------------------------------------------------------------------------------- /resource/KF_AMS_CAL.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/resource/KF_AMS_CAL.woff -------------------------------------------------------------------------------- /resource/KF_AMS_FRAK.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/resource/KF_AMS_FRAK.woff -------------------------------------------------------------------------------- /resource/KF_AMS_MAIN.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/resource/KF_AMS_MAIN.woff -------------------------------------------------------------------------------- /dist/resource/KF_AMS_BB.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/dist/resource/KF_AMS_BB.woff -------------------------------------------------------------------------------- /resource/KF_AMS_ROMAN.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/resource/KF_AMS_ROMAN.woff -------------------------------------------------------------------------------- /dist/resource/KF_AMS_CAL.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/dist/resource/KF_AMS_CAL.woff -------------------------------------------------------------------------------- /dist/resource/KF_AMS_FRAK.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/dist/resource/KF_AMS_FRAK.woff -------------------------------------------------------------------------------- /dist/resource/KF_AMS_MAIN.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/dist/resource/KF_AMS_MAIN.woff -------------------------------------------------------------------------------- /dist/resource/KF_AMS_ROMAN.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/dist/resource/KF_AMS_ROMAN.woff -------------------------------------------------------------------------------- /src/jquery.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-3-31. 3 | */ 4 | 5 | define( function () { 6 | return window.jQuery; 7 | } ); -------------------------------------------------------------------------------- /assets/theme/default/images/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/assets/theme/default/images/up.png -------------------------------------------------------------------------------- /src/kf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-3-12. 3 | */ 4 | 5 | define( function () { 6 | 7 | return window.kf; 8 | 9 | } ); -------------------------------------------------------------------------------- /assets/theme/default/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/assets/theme/default/images/close.png -------------------------------------------------------------------------------- /assets/theme/default/images/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/assets/theme/default/images/down.png -------------------------------------------------------------------------------- /assets/theme/default/images/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SugarTurboS/Formula-Editor/HEAD/assets/theme/default/images/open.png -------------------------------------------------------------------------------- /src/kity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 数学公式Latex语法解析器 3 | */ 4 | 5 | define( function () { 6 | 7 | return window.kity; 8 | 9 | } ); 10 | 11 | -------------------------------------------------------------------------------- /src/ui/ui-impl/def/item-type.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 组元素类型定义 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | return { 8 | "BIG": 1, 9 | "SMALL": 2 10 | }; 11 | 12 | } ); -------------------------------------------------------------------------------- /dev-lib/exports.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 启动代码 3 | */ 4 | 5 | ( function ( global ) { 6 | 7 | // build环境中才含有use 8 | try { 9 | use( 'kf.start' ); 10 | } catch ( e ) { 11 | } 12 | 13 | } )( this ); 14 | -------------------------------------------------------------------------------- /src/def/group-type.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 组类型 3 | */ 4 | 5 | define( function () { 6 | 7 | return { 8 | 9 | "GROUP": "kf-editor-group", 10 | "VIRTUAL": "kf-editor-virtual-group" 11 | 12 | }; 13 | 14 | } ); -------------------------------------------------------------------------------- /src/ui/ui-impl/def/box-type.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * box类型定义 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | return { 8 | // 分离式 9 | "DETACHED": 1, 10 | // 重叠式 11 | "OVERLAP": 2 12 | }; 13 | 14 | } ); -------------------------------------------------------------------------------- /src/kf-ext/def.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-3-18. 3 | */ 4 | 5 | define( function () { 6 | 7 | return { 8 | selectColor: 'rgba(42, 106, 189, 0.6)', 9 | allSelectColor: 'rgba(42, 106, 189, 0.6)' 10 | }; 11 | 12 | } ); -------------------------------------------------------------------------------- /src/bundle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-26 11:05:42 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-04-26 11:24:53 6 | * @description: 注册window中三方npm的服务 7 | */ 8 | define(function () { 9 | return window.bundle; 10 | }); 11 | -------------------------------------------------------------------------------- /src/ui/ui-impl/def/ele-type.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * toolbar元素类型定义 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | return { 8 | "DRAPDOWN_BOX": 1, 9 | "AREA": 2, 10 | "DELIMITER": 3, 11 | "KEYBOARD": 4 12 | }; 13 | 14 | } ); -------------------------------------------------------------------------------- /src/base/component.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 组件抽象类,所有的组件都是该类的子类 3 | * @abstract 4 | */ 5 | 6 | define( function ( require ) { 7 | 8 | var kity = require( "kity" ); 9 | 10 | return kity.createClass( 'Component', { 11 | 12 | constructor: function () {} 13 | 14 | } ); 15 | 16 | } ); -------------------------------------------------------------------------------- /src/base/utils.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 基础工具包 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var Utils = {}, 8 | commonUtils = require( "base/common" ); 9 | 10 | commonUtils.extend( Utils, commonUtils, require( "base/event/event" ) ); 11 | 12 | return Utils; 13 | 14 | } ); 15 | -------------------------------------------------------------------------------- /src/ui/ui-impl/ui.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-3-31. 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | return { 8 | 9 | DrapdownBox: require( "ui/ui-impl/drapdown-box" ), 10 | Delimiter: require( "ui/ui-impl/delimiter" ), 11 | Area: require( "ui/ui-impl/area" ), 12 | Keyboard: require("ui/ui-impl/keyboard/keyboard") 13 | }; 14 | 15 | } ); -------------------------------------------------------------------------------- /src/base/event/kfevent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-3-17. 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | return { 8 | 9 | createEvent: function ( type, e ) { 10 | 11 | var evt = document.createEvent( 'Event' ); 12 | 13 | evt.initEvent( type, true, true ); 14 | 15 | return evt; 16 | 17 | } 18 | 19 | }; 20 | 21 | } ); -------------------------------------------------------------------------------- /src/ui/def.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * UI定义 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | return { 8 | // 视窗状态 9 | VIEW_STATE: { 10 | // 内容未超出画布 11 | NO_OVERFLOW: 0, 12 | // 内容溢出 13 | OVERFLOW: 1 14 | }, 15 | scrollbar: { 16 | step: 50, 17 | thumbMinSize: 50 18 | } 19 | }; 20 | 21 | } ); 22 | -------------------------------------------------------------------------------- /src/parse/vgroup-def.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 虚拟组列表 3 | */ 4 | 5 | define( function () { 6 | 7 | return { 8 | 9 | "radical": true, 10 | "fraction": true, 11 | "summation": true, 12 | "integration": true, 13 | "placeholder": true, 14 | "script": true, 15 | "superscript": true, 16 | "subscript": true, 17 | "brackets": true, 18 | "function": true 19 | 20 | }; 21 | 22 | } ); -------------------------------------------------------------------------------- /src/sysconf.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 系统配置文件 3 | */ 4 | 5 | define( function () { 6 | 7 | return { 8 | 9 | // 光标符号 10 | cursorCharacter: "\uF155", 11 | 12 | // 根占位符内容与颜色 13 | rootPlaceholder: { 14 | color: "#666", 15 | content: "在此处键入公式", 16 | fontsize: 16 17 | }, 18 | 19 | scrollbar: { 20 | padding: 5, 21 | step: 150 22 | } 23 | 24 | }; 25 | 26 | } ); -------------------------------------------------------------------------------- /config/conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-7-28. 3 | */ 4 | 5 | var KFE_UI_CONFIG = { 6 | 7 | clazz: 'Panel', 8 | className: 'kfe-editor', 9 | widgets: [ 10 | 11 | { 12 | clazz: 'Panel', 13 | className: 'kfe-toolbar', 14 | widgets: [ { 15 | 16 | } ] 17 | }, 18 | 19 | { 20 | clazz: 'Panel', 21 | className: 'kfe-edit-area' 22 | } 23 | 24 | ] 25 | 26 | }; -------------------------------------------------------------------------------- /dev-lib/npmEntry.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-26 11:07:17 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-05-08 16:37:17 6 | * @description: 将项目中用的npm模块从该处引入并挂载到window中 7 | */ 8 | require('@babel/polyfill'); 9 | var WebService = require('@sugarteam/eclass-web-service').default; 10 | var CustomWebService = require('@sugarteam/web-service').default; 11 | 12 | module.exports = window.bundle = { 13 | WebService: WebService, 14 | CustomWebService: CustomWebService, 15 | }; 16 | -------------------------------------------------------------------------------- /src/ui/ui-impl/keyboard/const.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-16 19:03:59 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-05-16 10:10:53 6 | */ 7 | /* 8 | * @Author: Demian 9 | * @Date: 2020-04-15 10:11:11 10 | * @LastEditor: Demian 11 | * @LastEditTime: 2020-04-16 19:02:30 12 | */ 13 | 14 | define(function (require) { 15 | const Constant = { 16 | Type: { 17 | Common: 'common', 18 | Algebra: 'algebra', 19 | Geometry: 'geometry', 20 | Unit: 'unit', 21 | Other: 'other', 22 | Letter: 'letter', 23 | }, 24 | }; 25 | return Constant; 26 | }); 27 | -------------------------------------------------------------------------------- /src/control/input-filter.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 输入过滤器 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | // 过滤列表, 其中的key对应于键盘事件的keycode, 带有s+字样的key,匹配的是shift+keycode 8 | var LIST = { 9 | 32: "\\,", 10 | "s+219": "\\{", 11 | "s+221": "\\}", 12 | "220": "\\backslash", 13 | "s+51": "\\#", 14 | "s+52": "\\$", 15 | "s+53": "\\%", 16 | "s+54": "\\^", 17 | "s+55": "\\&", 18 | "s+189": "\\_", 19 | "s+192": "\\~" 20 | }; 21 | 22 | return { 23 | 24 | getReplaceString: function ( key ) { 25 | return LIST[ key ] || null; 26 | } 27 | 28 | }; 29 | 30 | } ); -------------------------------------------------------------------------------- /src/control/controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-4-11. 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var kity = require( "kity" ), 8 | 9 | ListenerComponent = require( "control/listener" ), 10 | 11 | ControllerComponent = kity.createClass( 'ControllerComponent', { 12 | 13 | constructor: function ( kfEditor ) { 14 | 15 | this.kfEditor = kfEditor; 16 | 17 | this.components = {}; 18 | 19 | this.initComponents(); 20 | 21 | }, 22 | 23 | initComponents: function () { 24 | 25 | this.components.listener = new ListenerComponent( this, this.kfEditor ); 26 | 27 | } 28 | 29 | 30 | } ); 31 | 32 | return ControllerComponent; 33 | 34 | } ); -------------------------------------------------------------------------------- /dev-lib/start.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 启动模块 3 | */ 4 | 5 | define( 'kf.start', function ( require ) { 6 | 7 | var KFEditor = require( "editor/editor"), 8 | Factory = require( "editor/factory" ); 9 | 10 | // 注册组件 11 | KFEditor.registerComponents( "ui", require( "ui/ui" ) ); 12 | KFEditor.registerComponents( "parser", require( "parse/parser" ) ); 13 | KFEditor.registerComponents( "render", require( "render/render" ) ); 14 | KFEditor.registerComponents( "position", require( "position/position" ) ); 15 | KFEditor.registerComponents( "syntax", require( "syntax/syntax" ) ); 16 | KFEditor.registerComponents( "control", require( "control/controller" ) ); 17 | KFEditor.registerComponents( "print", require( "print/printer" ) ); 18 | 19 | kf.EditorFactory = Factory; 20 | 21 | } ); -------------------------------------------------------------------------------- /src/editor/Message.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-29 21:14:35 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-04-30 10:26:17 6 | */ 7 | define(function (require) { 8 | const kity = require('kity'); 9 | const Messager = kity.createClass('Messager', { 10 | constructor() {}, 11 | 12 | getCheckServiceType: function () { 13 | return 'common.requestFunctions'; 14 | }, 15 | 16 | // 实现webService如何去接收消息 17 | onReceiveMessage: function (messageHandler) { 18 | document.addEventListener('documentMessage', (e) => { 19 | e.detail && e.detail.headers && messageHandler(e.detail); 20 | }); 21 | }, 22 | 23 | // 实现webService如何去发送消息 24 | sendAction: function ({ type, headers, data }) { 25 | const event = new CustomEvent('documentMessage', { 26 | detail: { type, headers, data }, 27 | }); 28 | document.dispatchEvent(event); 29 | }, 30 | }); 31 | return Messager; 32 | }); 33 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxerr": 50, 3 | "boss": true, 4 | "bitwise": false, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": false, 8 | "forin": true, 9 | "strict": false, 10 | "freeze": true, 11 | "immed": true, 12 | "indent": 4, 13 | "latedef": false, 14 | "newcap": true, 15 | "noarg": true, 16 | "noempty": true, 17 | "nonbsp": true, 18 | "nonew": false, 19 | "plusplus": false, 20 | "undef": true, 21 | "trailing": true, 22 | "debug": false, 23 | "funcscope": true, 24 | "multistr": false, 25 | "proto": true, 26 | "smarttabs": false, 27 | "shadow": true, 28 | "scripturl": false, 29 | "laxcomma": false, 30 | "loopfunc": true, 31 | "onevar": false, 32 | "unused": "vars", 33 | "maxdepth": 5, 34 | "maxstatements": 40, 35 | // "maxlen": 80, 36 | "evil": false, 37 | "funcscope": false, 38 | "lastsemic": false, 39 | "laxbreak": false, 40 | "expr": true, 41 | "quotmark": false, 42 | "predef": [ "define", "unescape", "console" ], 43 | "browser": true 44 | } -------------------------------------------------------------------------------- /src/control/listener.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-4-11. 3 | */ 4 | 5 | define( function ( require, exports, module ) { 6 | 7 | var kity = require( "kity" ), 8 | 9 | // 光标定位 10 | LocationComponent = require( "control/location" ), 11 | 12 | // 输入控制组件 13 | InputComponent = require( "control/input" ), 14 | 15 | // 选区 16 | SelectionComponent = require( "control/selection" ); 17 | 18 | return kity.createClass( "MoveComponent", { 19 | 20 | constructor: function ( parentComponent, kfEditor ) { 21 | 22 | this.parentComponent = parentComponent; 23 | this.kfEditor = kfEditor; 24 | 25 | this.components = {}; 26 | 27 | this.initComponents(); 28 | 29 | }, 30 | 31 | initComponents: function () { 32 | 33 | this.components.location= new LocationComponent( this, this.kfEditor ); 34 | this.components.selection = new SelectionComponent( this, this.kfEditor ); 35 | this.components.input = new InputComponent( this, this.kfEditor ); 36 | 37 | } 38 | 39 | } ); 40 | 41 | } ); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 SugarTeam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dist/assets/styles/base.css: -------------------------------------------------------------------------------- 1 | .kf-editor { 2 | width: 100%; 3 | height: 100%; 4 | border: 1px solid #e0e0e0; 5 | overflow: hidden; 6 | z-index: 2; 7 | background-color: #eee; 8 | } 9 | .kf-loading { 10 | width: 864px; 11 | height: 580px; 12 | text-align: center; 13 | line-height: 580px; 14 | position: fixed; 15 | top: 0; 16 | left: 0; 17 | } 18 | .kf-editor-edit-area { 19 | position: relative; 20 | top: 0; 21 | left: 0; 22 | z-index: 1; 23 | height: 0; 24 | background-color: white; 25 | /*background-color: white;*/ 26 | /*background-size: 21px 21px;*/ 27 | /*background-position: 0 0,10px 10px;*/ 28 | /*background-image: -webkit-linear-gradient(45deg,#efefef 25%,transparent 25%,transparent 75%,#efefef 75%,#efefef),-webkit-linear-gradient(45deg,#efefef 25%,transparent 25%,transparent 75%,#efefef 75%,#efefef);*/ 29 | /*background-image: linear-gradient(45deg,#efefef 25%,transparent 25%,transparent 75%,#efefef 75%,#efefef),linear-gradient(45deg,#efefef 25%,transparent 25%,transparent 75%,#efefef 75%,#efefef);*/ 30 | } 31 | .kf-editor-canvas-container { 32 | width: 100%; 33 | height: 100%; 34 | } 35 | .kf-editor-input-box { 36 | position: fixed; 37 | top: 0; 38 | left: -99999999px; 39 | z-index: 999999; 40 | } 41 | -------------------------------------------------------------------------------- /dist/assets/styles/scrollbar.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * 滚动条 3 | **/ 4 | .kf-editor-edit-scrollbar { 5 | width: 100%; 6 | height: 8px; 7 | position: absolute; 8 | left: 0; 9 | z-index: 994; 10 | } 11 | .kf-editor-ui-left-button { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | width: 5px; 16 | height: 100%; 17 | background: white; 18 | } 19 | .kf-editor-ui-right-button { 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | width: 5px; 24 | height: 100%; 25 | background: white; 26 | } 27 | .kf-editor-ui-track { 28 | position: absolute; 29 | top: 0; 30 | left: 5px; 31 | width: 0; 32 | height: 100%; 33 | } 34 | .kf-editor-ui-thumb { 35 | position: absolute; 36 | top: 0; 37 | left: 0; 38 | width: 0; 39 | height: 100%; 40 | background: rgba(0, 0, 0, 0.15); 41 | border-radius: 6px; 42 | } 43 | .kf-editor-ui-thumb-left { 44 | width: 5px; 45 | height: 100%; 46 | position: absolute; 47 | top: 0; 48 | left: 0; 49 | z-index: 1; 50 | } 51 | .kf-editor-ui-thumb-right { 52 | width: 5px; 53 | height: 100%; 54 | position: absolute; 55 | top: 0; 56 | right: 0; 57 | } 58 | .kf-editor-ui-thumb-body { 59 | position: absolute; 60 | top: 0; 61 | left: 5px; 62 | width: 0; 63 | height: 100%; 64 | } 65 | -------------------------------------------------------------------------------- /src/ui/ui-impl/delimiter.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 分割符 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var kity = require( "kity" ), 8 | 9 | PREFIX = "kf-editor-ui-", 10 | 11 | // UiUitls 12 | $$ = require( "ui/ui-impl/ui-utils" ), 13 | 14 | Delimiter = kity.createClass( "Delimiter", { 15 | 16 | constructor: function ( doc ) { 17 | 18 | this.doc = doc; 19 | this.element = this.createDilimiter(); 20 | 21 | }, 22 | 23 | setToolbar: function ( toolbar ) { 24 | // do nothing 25 | }, 26 | 27 | createDilimiter: function () { 28 | 29 | var dilimiterNode = $$.ele( this.doc, "div", { 30 | className: PREFIX + "delimiter" 31 | } ); 32 | 33 | dilimiterNode.appendChild( $$.ele( this.doc, "div", { 34 | className: PREFIX + "delimiter-line" 35 | } ) ); 36 | 37 | return dilimiterNode; 38 | 39 | }, 40 | 41 | attachTo: function ( container ) { 42 | 43 | container.appendChild( this.element ); 44 | 45 | } 46 | 47 | }); 48 | 49 | return Delimiter; 50 | 51 | } ); -------------------------------------------------------------------------------- /src/editor/factory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 编辑器工厂方法 3 | * 用于创建编辑器 4 | */ 5 | 6 | define( function ( require ) { 7 | 8 | var kity = require( "kity" ), 9 | KFEditor = require( "editor/editor" ); 10 | 11 | /* ------------------------------- 编辑器装饰对象 */ 12 | function EditorWrapper ( container, options ) { 13 | 14 | var _self = this; 15 | this._callbacks = []; 16 | 17 | this.editor = new KFEditor( container, options ); 18 | 19 | this.editor.ready( function () { 20 | _self._trigger(); 21 | } ); 22 | 23 | } 24 | 25 | EditorWrapper.prototype._trigger = function () { 26 | 27 | var editor = this.editor; 28 | 29 | kity.Utils.each( this._callbacks, function ( cb ) { 30 | cb.call( editor, editor ); 31 | } ); 32 | 33 | }; 34 | 35 | EditorWrapper.prototype.ready = function ( cb ) { 36 | 37 | if ( this.editor.isReady() ) { 38 | cb.call( this.editor, this.editor ); 39 | } else { 40 | this._callbacks.push( cb ); 41 | } 42 | 43 | }; 44 | 45 | return { 46 | create: function ( container, options ) { 47 | 48 | return new EditorWrapper( container, options ); 49 | 50 | } 51 | }; 52 | 53 | } ); -------------------------------------------------------------------------------- /assets/styles/base.less: -------------------------------------------------------------------------------- 1 | .kf-editor { 2 | width: 100%; 3 | height: 100%; 4 | border: 1px solid #e0e0e0; 5 | overflow: hidden; 6 | z-index: 2; 7 | background-color: #eee; 8 | } 9 | 10 | .kf-loading { 11 | width: 864px; 12 | height: 580px; 13 | text-align: center; 14 | line-height: 580px; 15 | position: fixed; 16 | top: 0; 17 | left: 0; 18 | } 19 | 20 | .kf-editor-edit-area { 21 | position: relative; 22 | top: 0; 23 | left: 0; 24 | 25 | z-index: 1; 26 | height: 0; 27 | background-color: white; 28 | /*background-color: white;*/ 29 | /*background-size: 21px 21px;*/ 30 | /*background-position: 0 0,10px 10px;*/ 31 | /*background-image: -webkit-linear-gradient(45deg,#efefef 25%,transparent 25%,transparent 75%,#efefef 75%,#efefef),-webkit-linear-gradient(45deg,#efefef 25%,transparent 25%,transparent 75%,#efefef 75%,#efefef);*/ 32 | /*background-image: linear-gradient(45deg,#efefef 25%,transparent 25%,transparent 75%,#efefef 75%,#efefef),linear-gradient(45deg,#efefef 25%,transparent 25%,transparent 75%,#efefef 75%,#efefef);*/ 33 | } 34 | 35 | .kf-editor-canvas-container { 36 | width: 100%; 37 | height: 100%; 38 | } 39 | 40 | .kf-editor-input-box { 41 | position: fixed; 42 | top: 0; 43 | left: -99999999px; 44 | z-index: 999999; 45 | } 46 | -------------------------------------------------------------------------------- /assets/styles/scrollbar.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * 滚动条 3 | **/ 4 | 5 | .kf-editor-edit-scrollbar { 6 | width: 100%; 7 | height: 8px; 8 | position: absolute; 9 | left: 0; 10 | z-index: 994; 11 | } 12 | 13 | .kf-editor-ui-left-button { 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | width: 5px; 18 | height: 100%; 19 | background: white; 20 | } 21 | 22 | .kf-editor-ui-right-button { 23 | position: absolute; 24 | top: 0; 25 | right: 0; 26 | width: 5px; 27 | height: 100%; 28 | background: white; 29 | } 30 | 31 | .kf-editor-ui-track { 32 | position: absolute; 33 | top: 0; 34 | left: 5px; 35 | width: 0; 36 | height: 100%; 37 | } 38 | 39 | .kf-editor-ui-thumb { 40 | position: absolute; 41 | top: 0; 42 | left: 0; 43 | width: 0; 44 | height: 100%; 45 | background: rgba(0,0,0,0.15); 46 | border-radius: 6px; 47 | } 48 | 49 | .kf-editor-ui-thumb-left { 50 | width: 5px; 51 | height: 100%; 52 | position: absolute; 53 | top: 0; 54 | left: 0; 55 | z-index: 1; 56 | } 57 | 58 | .kf-editor-ui-thumb-right { 59 | width: 5px; 60 | height: 100%; 61 | position: absolute; 62 | top: 0; 63 | right: 0; 64 | } 65 | 66 | .kf-editor-ui-thumb-body { 67 | position: absolute; 68 | top: 0; 69 | left: 5px; 70 | width: 0; 71 | height: 100%; 72 | } -------------------------------------------------------------------------------- /dev-lib/jhtmls.min.js: -------------------------------------------------------------------------------- 1 | var jhtmls="undefined"==typeof exports?jhtmls||{}:exports;void function(e){"use strict";function n(e){return String(e).replace(/["<>& ]/g,function(e){return"&"+u[e]+";"})}function t(e){var n=[];return n.push("with(this){"),n.push(e.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/g,function(e){return['!#{unescape("',escape(e),'")}'].join("")}).replace(/[\r\n]+/g,"\n").replace(/^\n+|\s+$/gm,"").replace(/^([ \w\t_$]*([^&\^?|\n\w\/'"{}\[\]+\-():; \t=\.$_]|:\/\/).*$|^(?!\s*(else|do|try|finally|void|typeof\s[\w$_]*)\s*$)[^'":;{}()\n|=&\/^?]+$)\s?/gm,function(e){return e=e.replace(/&none;/g,"").replace(/["'\\]/g,"\\$&").replace(/\n/g,"\\n").replace(/(!?#)\{(.*?)\}|(!?\$)([a-z_]+\w*(?:\.[a-z_]+\w*)*)/g,function(e,n,t,r,u){if(r&&(n=r,t=u),!t)return"";t=t.replace(/\\n/g,"\n").replace(/\\([\\'"])/g,"$1");var o=/^[a-z$][\w+$]+$/i.test(t)&&!/^(true|false|NaN|null|this)$/.test(t);return["',",o?["typeof ",t,"==='undefined'?'':"].join(""):"","#"===n||"$"===n?"_encode_":"","(",t,"),'"].join("")}),e=["'",e,"'"].join("").replace(/^'',|,''$/g,""),e?["_output_.push(",e,");"].join(""):""})),n.push("}"),new Function("_output_","_encode_","helper","jhtmls",n.join(""))}function r(r,u,o){"function"==typeof r&&(r=String(r).replace(/^[^\{]*\{\s*\/\*!?[ \f\t\v]*\n?|[ \f\t\v]*\*\/[;|\s]*\}$/g,""));var i=t(r),s=function(t,r){r=r||e;var u=[];return i.call(t,u,n,r,e),u.join("")};return arguments.length<=1?s:s(u,o)}var u={'"':"quot","<":"lt",">":"gt","&":"amp"," ":"nbsp"};e.render=r}(jhtmls); 2 | //# sourceMappingURL=dist/jhtmls.min.js.map -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to formula-editor 👋

2 | 3 | > 基于百度 kityformula-editor 开发的公式编辑器,有 android 和 web 两种模式 4 | 5 | ## 安装依赖 6 | 7 | ```sh 8 | npm install 9 | npm install -g anywhere // 随启随用的静态服务器 10 | ``` 11 | 12 | ## 使用 13 | 14 | ```sh 15 | grunt build 16 | anywhere -p {port} // 这里anywhere是为了开静态服务器,预览index.html 17 | ``` 18 | 19 | 以 web 为例,url 中传入?device=pc&protocol=documentEvent,然后运行项目。点击键盘中的字符,即可生成对应 latex 公式,点击”确定“按钮导出公式,控制台可以通过以下代码拿到最终的 latex 值和相应的 base64 图片。 20 | 21 | ```js 22 | document.addEventListener('documentMessage', (e) => { 23 | const { type } = e?.detail; 24 | const msg = e?.detail?.data?.body; 25 | if (type !== 'common.setFormula') return; 26 | console.log('msg', msg.formula, msg.formulaSrc); // msg 123 data:image/png;...... 27 | }); 28 | ``` 29 | 30 | 其他信令请参照下方的信令详情 31 | 32 | ## 信令 33 | 34 | 公式编辑器支持 webview, iframe, documentEvent3 种通信方式,详细信令参照: 35 | [信令详情](./src/editor/command.md) 36 | 37 | ## 特性 38 | 39 | 设备类型:device - pc/android 40 | 41 | ```sh 42 | {ip地址}:{port}?device=android 43 | ``` 44 | 45 | 协议类型:protocol - iframe/webview/documentEvent 46 | 47 | ```sh 48 | {ip地址}:{port}?protocol=webview 49 | ``` 50 | 51 | 设备宽度:width 52 | 53 | ```sh 54 | {ip地址}:{port}?width=1920 55 | ``` 56 | 57 | ## 样式 58 | 59 | #### 安卓: 60 | 61 | ```sh 62 | {ip地址}:{port}?device=android&protocol=webview&width=1920 63 | ``` 64 | 65 | ![android](./images/android.png) 66 | 67 | #### web: 68 | 69 | ```sh 70 | {ip地址}:{port}?device=pc&protocol=documentEvent&width=1920 71 | ``` 72 | 73 | ![web](./images/web.png) 74 | 75 | 老铁,走过路过给个 ⭐️ 76 | 77 | 点个 ⭐️,不迷路 78 | -------------------------------------------------------------------------------- /src/kf-ext/extension.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 公式扩展接口 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var kf = require( "kf" ), 8 | SELECT_COLOR = require( "kf-ext/def" ).selectColor, 9 | ALL_SELECT_COLOR = require( "kf-ext/def" ).allSelectColor; 10 | 11 | function ext ( parser ) { 12 | 13 | kf.PlaceholderExpression = require( "kf-ext/expression/placeholder" ); 14 | 15 | kf.Expression.prototype.select = function () { 16 | 17 | this.box.fill( SELECT_COLOR ); 18 | 19 | }; 20 | 21 | kf.Expression.prototype.selectAll = function () { 22 | this.box.fill( ALL_SELECT_COLOR ); 23 | }; 24 | 25 | kf.Expression.prototype.unselect = function () { 26 | 27 | this.box.fill( "transparent" ); 28 | 29 | }; 30 | 31 | // 扩展解析和逆解析 32 | parser.getKFParser().expand( { 33 | 34 | parse: { 35 | "placeholder": { 36 | name: "placeholder", 37 | handler: function ( info ) { 38 | 39 | delete info.handler; 40 | info.operand = []; 41 | 42 | return info; 43 | 44 | }, 45 | sign: false 46 | } 47 | }, 48 | 49 | reverse: { 50 | 51 | "placeholder": function () { 52 | 53 | return "\\placeholder "; 54 | 55 | } 56 | 57 | } 58 | 59 | } ); 60 | 61 | } 62 | 63 | return { 64 | ext: ext 65 | }; 66 | 67 | } ); 68 | -------------------------------------------------------------------------------- /src/ui/header/header.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-22 09:53:01 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-05-06 14:37:39 6 | */ 7 | /* 8 | * @Author: Demian 9 | * @Date: 2020-04-14 16:31:36 10 | * @LastEditor: Demian 11 | * @LastEditTime: 2020-04-21 17:25:16 12 | */ 13 | define(function (require) { 14 | var kity = require('kity'), 15 | $$ = require('ui/ui-impl/ui-utils'), 16 | Header = kity.createClass('Header', { 17 | constructor: function (uiComponent, kfEditor) { 18 | this.prefix = 'kf-editor-header-container'; 19 | this.kfEditor = kfEditor; 20 | this.uiComponent = uiComponent; 21 | this.initKeyboardElements(); 22 | this.initEvent(); 23 | }, 24 | 25 | initEvent: function () { 26 | $$.delegate(this.uiComponent.header, '.' + this.prefix + '-close', 'click', () => { 27 | this.kfEditor.eclassWebService.send({ 28 | type: 'common.closeModal', 29 | }); 30 | }); 31 | }, 32 | 33 | initKeyboardElements: function () { 34 | var doc = this.uiComponent.header.ownerDocument; 35 | 36 | var ele = this.createHeader(doc, this.kfEditor); 37 | this.uiComponent.header.appendChild(ele); 38 | }, 39 | createHeader: function (doc, kfEditor) { 40 | return $$.ele(doc, 'div', { 41 | className: this.prefix, 42 | content: ` 43 |
插入公式
44 |
45 | `, 46 | }); 47 | }, 48 | }); 49 | 50 | return Header; 51 | }); 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sugarteam/formula-editor", 3 | "title": "Formula Editor", 4 | "description": "基于百度kityformula-editor的公式编辑器", 5 | "version": "1.0.7-test-1", 6 | "homepage": "https://github.com/SugarTurboS/Formula-Editor", 7 | "author": { 8 | "name": "sugarteam" 9 | }, 10 | "keywords": [ 11 | "formula", 12 | "svg", 13 | "library" 14 | ], 15 | "licenses": [ 16 | { 17 | "type": "MIT", 18 | "url": "https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt" 19 | } 20 | ], 21 | "repository": { 22 | "type": "git", 23 | "url": "git@github.com:SugarTurboS/Formula-Editor.git" 24 | }, 25 | "dependencies": { 26 | "@babel/polyfill": "^7.8.7", 27 | "@sugarteam/eclass-web-service": "^1.0.5", 28 | "@sugarteam/web-service": "^1.0.5", 29 | "@types/chai": "^4.2.11" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.9.0", 33 | "@babel/plugin-transform-typeof-symbol": "^7.8.4", 34 | "@babel/preset-env": "^7.9.5", 35 | "babel-plugin-transform-remove-strict-mode": "^0.0.2", 36 | "babel-preset-es2015": "^6.24.1", 37 | "grunt": "~0.4.1", 38 | "grunt-babel": "^8.0.0", 39 | "grunt-browserify": "^5.3.0", 40 | "grunt-contrib-clean": "~0.5.0", 41 | "grunt-contrib-concat": "~0.4.0", 42 | "grunt-contrib-copy": "^1.0.0", 43 | "grunt-contrib-cssmin": "^3.0.0", 44 | "grunt-contrib-jshint": "^2.1.0", 45 | "grunt-contrib-less": "^2.0.0", 46 | "grunt-contrib-uglify": "~0.2.6", 47 | "grunt-module-dependence": "~0.1.1" 48 | }, 49 | "files": [ 50 | "dist" 51 | ], 52 | "publishConfig": { 53 | "registry": "https://registry.npmjs.com/" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/kf-ext/expression/placeholder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 占位符表达式, 扩展KF自有的Empty表达式 3 | */ 4 | 5 | 6 | define( function ( require, exports, module ) { 7 | 8 | var kity = require( "kity" ) , 9 | 10 | kf = require( "kf" ), 11 | 12 | PlaceholderOperator = require( "kf-ext/operator/placeholder" ); 13 | 14 | return kity.createClass( 'PlaceholderExpression', { 15 | 16 | base: kf.CompoundExpression, 17 | 18 | constructor: function () { 19 | 20 | this.callBase(); 21 | 22 | this.setFlag( "Placeholder" ); 23 | 24 | this.label = null; 25 | 26 | this.box.setAttr( "data-type", null ); 27 | this.setOperator( new PlaceholderOperator() ); 28 | 29 | }, 30 | 31 | setLabel: function ( label ) { 32 | this.label = label; 33 | }, 34 | 35 | getLabel: function () { 36 | return this.label; 37 | }, 38 | 39 | // 重载占位符的setAttr, 以处理根占位符节点 40 | setAttr: function ( key, val ) { 41 | 42 | if ( key === "label" ) { 43 | this.setLabel( val ); 44 | } else { 45 | 46 | if ( key.label ) { 47 | this.setLabel( key.label ); 48 | // 删除label 49 | delete key.label; 50 | } 51 | // 继续设置其他属性 52 | this.callBase( key, val ); 53 | 54 | } 55 | 56 | }, 57 | 58 | select: function () { 59 | 60 | this.getOperator().select(); 61 | 62 | }, 63 | 64 | selectAll: function () { 65 | 66 | this.getOperator().selectAll(); 67 | 68 | }, 69 | 70 | unselect: function () { 71 | this.getOperator().unselect(); 72 | } 73 | 74 | } ); 75 | 76 | } ); -------------------------------------------------------------------------------- /src/ui/control/zoom.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 滚动缩放控制器 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var Utils = require( "base/utils" ), 8 | kity = require( "kity"), 9 | 10 | DEFAULT_OPTIONS = { 11 | min: 1, 12 | max: 2 13 | }, 14 | 15 | ScrollZoomController = kity.createClass( 'ScrollZoomController', { 16 | 17 | constructor: function ( parentComponent, kfEditor, target, options ) { 18 | 19 | this.kfEditor = kfEditor; 20 | this.target = target; 21 | 22 | this.zoom = 1; 23 | this.step = 0.05; 24 | 25 | this.options = Utils.extend( {}, DEFAULT_OPTIONS, options ); 26 | 27 | this.initEvent(); 28 | 29 | }, 30 | 31 | initEvent: function () { 32 | 33 | var kfEditor = this.kfEditor, 34 | _self = this, 35 | min = this.options.min, 36 | max = this.options.max, 37 | step = this.step; 38 | 39 | Utils.addEvent( this.target, 'mousewheel', function ( e ) { 40 | 41 | e.preventDefault(); 42 | 43 | if ( e.wheelDelta < 0 ) { 44 | // 缩小 45 | _self.zoom -= _self.zoom * step; 46 | } else { 47 | // 放大 48 | _self.zoom += _self.zoom * step; 49 | } 50 | 51 | _self.zoom = Math.max( _self.zoom, min ); 52 | _self.zoom = Math.min( _self.zoom, max ); 53 | 54 | kfEditor.requestService( "render.set.canvas.zoom", _self.zoom ); 55 | 56 | } ); 57 | 58 | } 59 | 60 | } ); 61 | 62 | 63 | return ScrollZoomController; 64 | 65 | } ); 66 | 67 | -------------------------------------------------------------------------------- /src/ui/ui-impl/keyboard/footer/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-20 11:00:08 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-04-20 11:37:55 6 | */ 7 | /* 8 | * @Author: Demian 9 | * @Date: 2020-04-16 20:03:47 10 | * @LastEditor: Demian 11 | * @LastEditTime: 2020-04-20 10:03:23 12 | */ 13 | define(function (require) { 14 | const kity = require('kity'); 15 | const $$ = require('ui/ui-impl/ui-utils'); 16 | const Footer = kity.createClass('Footer', { 17 | constructor(parentNode, parentProps) { 18 | this.parentNode = parentNode; 19 | this.props = parentProps; 20 | this.prefix = parentProps.prefix + 'keyboard-footer'; 21 | 22 | this.state = {}; 23 | 24 | this.containerClassName = this.prefix; 25 | this.itemClassName = `${this.prefix}-button`; 26 | this.cancelClassName = `${this.itemClassName}-cancel`; 27 | this.submitClassName = `${this.itemClassName}-submit`; 28 | this._onSubmit = this._onSubmit.bind(this); 29 | this._onCancel = this._onCancel.bind(this); 30 | }, 31 | _render: function () { 32 | return $$.ele(this.props.doc, 'div', { 33 | className: this.containerClassName, 34 | content: ` 35 |
36 | 取消 37 |
38 |
39 | 确定 40 |
41 | `, 42 | }); 43 | }, 44 | mount: function () { 45 | const node = this._render(); 46 | $$.delegate(this.parentNode, `#${this.cancelClassName}`, 'click', this._onCancel); 47 | $$.delegate(this.parentNode, `#${this.submitClassName}`, 'click', this._onSubmit); 48 | this.parentNode.appendChild(node); 49 | }, 50 | _onCancel: function (e) { 51 | console.log('cancel'); 52 | this.props.onCancel(); 53 | }, 54 | _onSubmit: function (e) { 55 | console.log('submit'); 56 | this.props.onSubmit(); 57 | }, 58 | }); 59 | return Footer; 60 | }); 61 | -------------------------------------------------------------------------------- /src/base/event/event.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * event模块 3 | */ 4 | 5 | /* jshint camelcase: false */ 6 | 7 | define( function ( require, exports, modules ) { 8 | 9 | var EVENT_LISTENER = {}, 10 | eid = 0, 11 | BEFORE_RESULT = true, 12 | KFEvent = require( "base/event/kfevent" ), 13 | commonUtils = require( "base/common" ), 14 | EVENT_HANDLER = function ( e ) { 15 | 16 | var type = e.type, 17 | target = e.target, 18 | eid = this.__kfe_eid, 19 | hasAutoTrigger = /^(?:before|after)/.test( type ), 20 | HANDLER_LIST = EVENT_LISTENER[ eid ][ type ]; 21 | 22 | if ( !hasAutoTrigger ) { 23 | 24 | EventListener.trigger( target, 'before' + type ); 25 | 26 | if ( BEFORE_RESULT === false ) { 27 | BEFORE_RESULT = true; 28 | return false; 29 | } 30 | 31 | } 32 | 33 | commonUtils.each( HANDLER_LIST, function ( handler, index ) { 34 | 35 | if ( !handler ) { 36 | return; 37 | } 38 | 39 | if ( handler.call( target, e ) === false ) { 40 | BEFORE_RESULT = false; 41 | return BEFORE_RESULT; 42 | } 43 | 44 | } ); 45 | 46 | if ( !hasAutoTrigger ) { 47 | 48 | EventListener.trigger( target, 'after' + type ); 49 | 50 | } 51 | 52 | }; 53 | 54 | var EventListener = { 55 | 56 | addEvent: function ( target, type, handler ) { 57 | 58 | var hasHandler = true, 59 | eventCache = null; 60 | 61 | if ( !target.__kfe_eid ) { 62 | hasHandler = false; 63 | target.__kfe_eid = generateId(); 64 | EVENT_LISTENER[ target.__kfe_eid ] = {}; 65 | } 66 | 67 | eventCache = EVENT_LISTENER[ target.__kfe_eid ]; 68 | 69 | if ( !eventCache[ type ] ) { 70 | hasHandler = false; 71 | eventCache[ type ] = []; 72 | } 73 | 74 | eventCache[ type ].push( handler ); 75 | 76 | if ( hasHandler ) { 77 | return; 78 | } 79 | 80 | target.addEventListener( type, EVENT_HANDLER, false ); 81 | 82 | }, 83 | 84 | trigger: function ( target, type, e ) { 85 | 86 | e = e || KFEvent.createEvent( type, e ); 87 | 88 | target.dispatchEvent( e ); 89 | 90 | } 91 | 92 | }; 93 | 94 | function generateId () { 95 | 96 | return ++eid; 97 | 98 | } 99 | 100 | return EventListener; 101 | 102 | } ); -------------------------------------------------------------------------------- /src/ui/ui-impl/drapdown-box.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-3-31. 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var kity = require( "kity" ), 8 | 9 | // UiUitls 10 | $$ = require( "ui/ui-impl/ui-utils" ), 11 | 12 | Button = require( "ui/ui-impl/button" ), 13 | Box = require( "ui/ui-impl/box" ), 14 | 15 | DrapdownBox = kity.createClass( "DrapdownBox", { 16 | 17 | constructor: function ( doc, options ) { 18 | 19 | this.options = options; 20 | this.toolbar = null; 21 | this.doc = doc; 22 | 23 | this.buttonElement = this.createButton(); 24 | 25 | this.element = this.buttonElement.getNode(); 26 | 27 | this.boxElement = this.createBox(); 28 | 29 | this.buttonElement.mount( this.boxElement ); 30 | 31 | this.initEvent(); 32 | 33 | }, 34 | 35 | initEvent: function () { 36 | 37 | var _self = this; 38 | 39 | // 通知工具栏互斥 40 | $$.on( this.element, "mousedown", function ( e ) { 41 | 42 | e.preventDefault(); 43 | e.stopPropagation(); 44 | 45 | _self.toolbar.notify( "closeOther", _self ); 46 | 47 | } ); 48 | 49 | 50 | this.buttonElement.initEvent(); 51 | this.boxElement.initEvent(); 52 | 53 | this.boxElement.setSelectHandler( function ( val ) { 54 | // 发布 55 | $$.publish( "data.select", val ); 56 | _self.buttonElement.hide(); 57 | } ); 58 | 59 | }, 60 | 61 | disable: function () { 62 | this.buttonElement.disable(); 63 | }, 64 | 65 | enable: function () { 66 | this.buttonElement.enable(); 67 | }, 68 | 69 | setToolbar: function ( toolbar ) { 70 | this.toolbar = toolbar; 71 | this.buttonElement.setToolbar( toolbar ); 72 | this.boxElement.setToolbar( toolbar ); 73 | }, 74 | 75 | createButton: function () { 76 | 77 | return new Button( this.doc, this.options.button ); 78 | 79 | }, 80 | 81 | show: function () { 82 | this.buttonElement.show(); 83 | }, 84 | 85 | hide: function () { 86 | this.buttonElement.hide(); 87 | }, 88 | 89 | createBox: function () { 90 | 91 | return new Box( this.doc, this.options.box ); 92 | 93 | }, 94 | 95 | attachTo: function ( container ) { 96 | 97 | container.appendChild( this.element ); 98 | 99 | } 100 | 101 | }); 102 | 103 | return DrapdownBox; 104 | 105 | } ); -------------------------------------------------------------------------------- /src/print/printer.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 打印服务 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var kity = require( "kity" ); 8 | 9 | return kity.createClass( "Printer", { 10 | 11 | constructor: function ( kfEditor ) { 12 | 13 | this.kfEditor = kfEditor; 14 | 15 | this.initServices(); 16 | 17 | this.initCommands(); 18 | 19 | }, 20 | 21 | initServices: function () { 22 | 23 | this.kfEditor.registerService( "print.image", this, { 24 | printImage: this.printImage 25 | } ); 26 | 27 | }, 28 | 29 | initCommands: function () { 30 | 31 | this.kfEditor.registerCommand( "get.image.data", this, this.getImageData ); 32 | 33 | }, 34 | 35 | printImage: function ( type ) { 36 | 37 | var formula = this.kfEditor.requestService( "render.get.paper" ); 38 | 39 | this._formatCanvas(); 40 | 41 | formula.toPNG( function ( dataUrl ) { 42 | 43 | document.body.innerHTML = ''; 44 | 45 | } ); 46 | 47 | this._restoreCanvas(); 48 | 49 | }, 50 | 51 | getImageData: function ( cb ) { 52 | 53 | var canvas = this.kfEditor.requestService( "render.get.canvas" ), 54 | formula = this.kfEditor.requestService( "render.get.paper" ); 55 | 56 | this._formatCanvas(); 57 | 58 | formula.toPNG( function ( dataUrl ) { 59 | 60 | cb( { 61 | width: canvas.width, 62 | height: canvas.height, 63 | img: dataUrl 64 | } ); 65 | 66 | } ); 67 | 68 | this._restoreCanvas(); 69 | 70 | }, 71 | 72 | _formatCanvas: function () { 73 | 74 | var canvas = this.kfEditor.requestService( "render.get.canvas" ), 75 | rect = canvas.container.getRenderBox(); 76 | 77 | canvas.node.setAttribute( "width", rect.width ); 78 | canvas.node.setAttribute( "height", rect.height ); 79 | 80 | this.kfEditor.requestService( "render.clear.canvas.transform" ); 81 | this.kfEditor.requestService( "control.cursor.hide" ); 82 | this.kfEditor.requestService( "render.clear.select" ); 83 | 84 | }, 85 | 86 | _restoreCanvas: function () { 87 | 88 | var canvas = this.kfEditor.requestService( "render.get.canvas" ); 89 | 90 | canvas.node.setAttribute( "width", "100%" ); 91 | canvas.node.setAttribute( "height", "100%" ); 92 | 93 | this.kfEditor.requestService( "render.revert.canvas.transform" ); 94 | this.kfEditor.requestService( "control.cursor.relocation" ); 95 | this.kfEditor.requestService( "render.reselect" ); 96 | 97 | } 98 | 99 | } ); 100 | 101 | } ); 102 | 103 | 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # node # 3 | ############### 4 | node_modules 5 | npm-debug.log 6 | 7 | ############### 8 | # folder # 9 | ############### 10 | .idea 11 | log 12 | upload 13 | 14 | 15 | ############### 16 | # fixed file # 17 | ############### 18 | *.pptx 19 | *.doc 20 | *.docx 21 | *.xml 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ################# 33 | ## Eclipse 34 | ################# 35 | 36 | *.pydevproject 37 | .project 38 | .metadata 39 | bin/ 40 | tmp/ 41 | *.tmp 42 | *.bak 43 | *.swp 44 | *~.nib 45 | local.properties 46 | .classpath 47 | .settings/ 48 | .loadpath 49 | 50 | # External tool builders 51 | .externalToolBuilders/ 52 | 53 | # Locally stored "Eclipse launch configurations" 54 | *.launch 55 | 56 | # CDT-specific 57 | .cproject 58 | 59 | # PDT-specific 60 | .buildpath 61 | 62 | 63 | ################# 64 | ## Visual Studio 65 | ################# 66 | 67 | ## Ignore Visual Studio temporary files, build results, and 68 | ## files generated by popular Visual Studio add-ons. 69 | 70 | # User-specific files 71 | *.suo 72 | *.user 73 | *.sln.docstates 74 | 75 | # Build results 76 | [Dd]ebug/ 77 | [Rr]elease/ 78 | *_i.c 79 | *_p.c 80 | *.ilk 81 | *.meta 82 | *.obj 83 | *.pch 84 | *.pdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.vspscc 94 | .builds 95 | *.dotCover 96 | 97 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 98 | #packages/ 99 | 100 | # Visual C++ cache files 101 | ipch/ 102 | *.aps 103 | *.ncb 104 | *.opensdf 105 | *.sdf 106 | 107 | # Visual Studio profiler 108 | *.psess 109 | *.vsp 110 | 111 | # ReSharper is a .NET coding add-in 112 | _ReSharper* 113 | 114 | # Installshield output folder 115 | [Ee]xpress 116 | 117 | # DocProject is a documentation generator add-in 118 | DocProject/buildhelp/ 119 | DocProject/Help/*.HxT 120 | DocProject/Help/*.HxC 121 | DocProject/Help/*.hhc 122 | DocProject/Help/*.hhk 123 | DocProject/Help/*.hhp 124 | DocProject/Help/Html2 125 | DocProject/Help/html 126 | 127 | # Click-Once directory 128 | publish 129 | 130 | # Others 131 | [Bb]in 132 | [Oo]bj 133 | sql 134 | TestResults 135 | *.Cache 136 | ClientBin 137 | stylecop.* 138 | ~$* 139 | *.dbmdl 140 | Generated_Code #added for RIA/Silverlight projects 141 | 142 | # Backup & report files from converting an old project file to a newer 143 | # Visual Studio version. Backup files are not needed, because we have git ;-) 144 | _UpgradeReport_Files/ 145 | Backup*/ 146 | UpgradeLog*.XML 147 | 148 | 149 | 150 | ############ 151 | ## Windows 152 | ############ 153 | 154 | # Windows image file caches 155 | Thumbs.db 156 | 157 | # Folder config file 158 | Desktop.ini 159 | 160 | 161 | ############# 162 | ## Python 163 | ############# 164 | 165 | *.py[co] 166 | 167 | # Packages 168 | *.egg 169 | *.egg-info 170 | eggs 171 | parts 172 | bin 173 | var 174 | sdist 175 | develop-eggs 176 | .installed.cfg 177 | 178 | # Installer logs 179 | pip-log.txt 180 | 181 | # Unit test / coverage reports 182 | .coverage 183 | .tox 184 | 185 | #Translations 186 | *.mo 187 | 188 | #Mr Developer 189 | .mr.developer.cfg 190 | 191 | # Mac crap 192 | .DS_Store 193 | 194 | .npmrc -------------------------------------------------------------------------------- /src/ui/ui-impl/keyboard/menu/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-16 16:11:27 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-05-18 14:20:00 6 | */ 7 | define(function (require) { 8 | const kity = require('kity'); 9 | const $$ = require('ui/ui-impl/ui-utils'); 10 | const Constant = require('ui/ui-impl/keyboard/const'); 11 | const Menu = kity.createClass('Menu', { 12 | constructor(parentNode, parentProps) { 13 | this.parentNode = parentNode; 14 | this.props = parentProps; 15 | this.prefix = parentProps.prefix + 'keyboard-menu'; 16 | this.elementList = [ 17 | { type: Constant.Type.Common, title: '常用', index: 0 }, 18 | { type: Constant.Type.Algebra, title: '代数', index: 1 }, 19 | { type: Constant.Type.Geometry, title: '几何', index: 2 }, 20 | { type: Constant.Type.Letter, title: '字母', index: 3 }, 21 | { type: Constant.Type.Other, title: '其他', index: 4 }, 22 | ]; 23 | 24 | this.state = { 25 | type: Constant.Type.Common, 26 | }; 27 | 28 | this.containerClassName = this.prefix; 29 | this.listClassName = `${this.prefix}-list`; 30 | this.itemClassName = `${this.prefix}-list-item`; 31 | this._onClick = this._onClick.bind(this); 32 | this._initCommand.call(this); 33 | }, 34 | _render: function () { 35 | return $$.ele(this.props.doc, 'div', { 36 | className: this.containerClassName, 37 | content: ` 38 | 48 | `, 49 | }); 50 | function isActive(type) { 51 | return type === this.state.type; 52 | } 53 | }, 54 | mount: function () { 55 | const node = this._render(); 56 | $$.delegate(this.parentNode, '.' + this.itemClassName, 'click', this._onClick); 57 | this.parentNode.appendChild(node); 58 | }, 59 | destroy: function () { 60 | $(this.parentNode).find(this.prefix).remove(); 61 | }, 62 | update: function (nextProps) { 63 | if (!this._shouldUpdate(nextProps)) return; 64 | Object.keys(nextProps) 65 | .filter((x) => x in this.props) 66 | .forEach((x) => 67 | this._setState({ 68 | [x]: nextProps[x], 69 | }) 70 | ); 71 | const node = this._render(); 72 | $('.' + this.prefix).html(node); 73 | }, 74 | _shouldUpdate: function (nextProps) { 75 | const isSame = Object.keys(this.state).every((x) => nextProps[x] === this.state[x]); 76 | if (isSame) { 77 | return false; 78 | } 79 | return true; 80 | }, 81 | _initCommand: function () { 82 | this.props.kfEditor.registerCommand('menu.clearType', this, function () { 83 | this.props.onClick(Constant.Type.Common); 84 | }); 85 | }, 86 | _onClick: function (e) { 87 | const val = e.target.dataset.value; 88 | this.props.onClick(val); 89 | }, 90 | _setState: function (nextState) { 91 | this.state = { 92 | ...this.state, 93 | ...nextState, 94 | }; 95 | }, 96 | }); 97 | return Menu; 98 | }); 99 | -------------------------------------------------------------------------------- /src/ui/ui-impl/ui-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-4-1. 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var $ = require( "jquery" ), 8 | kity = require( "kity" ), 9 | TOPIC_POOL = {}; 10 | 11 | var Utils = { 12 | 13 | ele: function ( doc, name, options ) { 14 | 15 | var node = null; 16 | 17 | if ( name === "text" ) { 18 | return doc.createTextNode( options ); 19 | } 20 | 21 | node = doc.createElement( name ); 22 | options.className && ( node.className = options.className ); 23 | 24 | if ( options.content ) { 25 | node.innerHTML = options.content; 26 | } 27 | return node; 28 | }, 29 | 30 | getRectBox: function ( node ) { 31 | return node.getBoundingClientRect(); 32 | }, 33 | 34 | on: function ( target, type, fn ) { 35 | $( target ).on( type, fn ); 36 | return this; 37 | }, 38 | 39 | delegate: function ( target, selector, type, fn ) { 40 | 41 | $( target ).delegate( selector, type, fn ); 42 | return this; 43 | 44 | }, 45 | 46 | publish: function ( topic, args ) { 47 | 48 | var callbackList = TOPIC_POOL[ topic ]; 49 | 50 | if ( !callbackList ) { 51 | return; 52 | } 53 | 54 | args = [].slice.call( arguments, 1 ); 55 | 56 | kity.Utils.each( callbackList, function ( callback ) { 57 | 58 | callback.apply( null, args ); 59 | 60 | } ); 61 | 62 | }, 63 | 64 | subscribe: function ( topic, callback ) { 65 | 66 | if ( !TOPIC_POOL[ topic ] ) { 67 | 68 | TOPIC_POOL[ topic ] = []; 69 | 70 | } 71 | 72 | TOPIC_POOL[ topic ].push( callback ); 73 | 74 | }, 75 | 76 | getClassList: function ( node ) { 77 | 78 | return node.classList || new ClassList( node ); 79 | 80 | } 81 | 82 | }; 83 | 84 | 85 | //注意: 仅保证兼容IE9以上 86 | function ClassList ( node ) { 87 | 88 | this.node = node; 89 | this.classes = node.className.replace( /^\s+|\s+$/g, '' ).split( /\s+/ ); 90 | 91 | } 92 | 93 | ClassList.prototype = { 94 | 95 | constructor: ClassList, 96 | 97 | contains: function ( className ) { 98 | 99 | return this.classes.indexOf( className ) !== -1; 100 | 101 | }, 102 | 103 | add: function ( className ) { 104 | 105 | if ( this.classes.indexOf( className ) == -1 ) { 106 | this.classes.push( className ); 107 | } 108 | 109 | this._update(); 110 | 111 | return this; 112 | 113 | }, 114 | 115 | remove: function ( className ) { 116 | 117 | var index = this.classes.indexOf( className ); 118 | 119 | if ( index !== -1 ) { 120 | this.classes.splice( index, 1 ); 121 | this._update(); 122 | } 123 | 124 | return this; 125 | }, 126 | 127 | toggle: function ( className ) { 128 | 129 | var method = this.contains( className ) ? 'remove' : 'add'; 130 | 131 | return this[ method ]( className ); 132 | 133 | }, 134 | 135 | _update: function () { 136 | 137 | this.node.className = this.classes.join( " " ); 138 | 139 | } 140 | 141 | }; 142 | 143 | return Utils; 144 | 145 | } ); -------------------------------------------------------------------------------- /src/ui/ui-impl/keyboard/page/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-16 20:03:47 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-04-26 18:06:01 6 | */ 7 | define(function (require) { 8 | const kity = require('kity'); 9 | const $$ = require('ui/ui-impl/ui-utils'); 10 | const Page = kity.createClass('Page', { 11 | constructor(parentNode, parentProps) { 12 | this.parentNode = parentNode; 13 | this.props = parentProps; 14 | this.prefix = parentProps.prefix + 'keyboard-page'; 15 | this.elementList = [ 16 | { type: 'prev', title: '上一页', index: 0 }, 17 | { type: 'next', title: '下一页', index: 1 }, 18 | ]; 19 | 20 | this.state = { 21 | type: this.props.type, 22 | page: this.props.page, 23 | totalPage: this.props.totalPage, 24 | }; 25 | 26 | this.containerClassName = this.prefix; 27 | this.listClassName = `${this.prefix}-list`; 28 | this.itemClassName = `${this.prefix}-list-item`; 29 | this._onClick = this._onClick.bind(this); 30 | }, 31 | _render: function () { 32 | return $$.ele(this.props.doc, 'div', { 33 | className: this.containerClassName, 34 | content: ` 35 | 51 | `, 52 | }); 53 | function isDisabled(type) { 54 | if (type === 'prev') { 55 | return this.state.page === 0 && this.state.type === 'common'; 56 | } else if (type === 'next') { 57 | return this.state.page === this.state.totalPage - 1 && this.state.type === 'other'; 58 | } 59 | } 60 | }, 61 | mount: function () { 62 | const node = this._render(); 63 | $$.delegate(this.parentNode, '.' + this.itemClassName, 'click', this._onClick); 64 | this.parentNode.appendChild(node); 65 | }, 66 | update: function (nextProps) { 67 | if (!this._shouldUpdate(nextProps)) return; 68 | Object.keys(nextProps) 69 | .filter((x) => x in this.state) 70 | .forEach((x) => 71 | this._setState({ 72 | [x]: nextProps[x], 73 | }) 74 | ); 75 | const node = this._render(); 76 | $('.' + this.prefix).html(node); 77 | }, 78 | _shouldUpdate: function (nextProps) { 79 | const isSame = Object.keys(this.state).every((x) => nextProps[x] === this.state[x]); 80 | if (isSame) { 81 | return false; 82 | } 83 | return true; 84 | }, 85 | _onClick: function (e) { 86 | const val = e.target.dataset.value; 87 | switch (val) { 88 | case 'next': 89 | this.props.onNextPage(); 90 | break; 91 | case 'prev': 92 | this.props.onPrevPage(); 93 | break; 94 | case 'delete': 95 | this.props.onDelete(); 96 | break; 97 | case 'submit': 98 | this.props.onSubmit(); 99 | break; 100 | } 101 | }, 102 | _setState: function (nextState) { 103 | this.state = { 104 | ...this.state, 105 | ...nextState, 106 | }; 107 | }, 108 | }); 109 | return Page; 110 | }); 111 | -------------------------------------------------------------------------------- /src/kf-ext/operator/placeholder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 占位符操作符 3 | */ 4 | 5 | define( function ( require, exports, modules ) { 6 | 7 | var kity = require( "kity" ), 8 | // FILL_COLOR = require( "sysconf" ).rootPlaceholder.color, 9 | SELECT_COLOR = require( "kf-ext/def" ).selectColor, 10 | ALL_SELECT_COLOR = require( "kf-ext/def" ).allSelectColor; 11 | 12 | return kity.createClass( 'PlaceholderOperator', { 13 | 14 | base: require( "kf" ).Operator, 15 | 16 | constructor: function () { 17 | 18 | this.opShape = null; 19 | this.callBase( "Placeholder" ); 20 | 21 | }, 22 | 23 | applyOperand: function () { 24 | 25 | this.opShape = generateOpShape( this, this.parentExpression.getLabel() ); 26 | this.parentExpression.expand( 20, 20 ); 27 | this.parentExpression.translateElement( 10, 10 ); 28 | 29 | }, 30 | 31 | select: function () { 32 | 33 | // 默认placeholder改为光标后, select要置为黑色 34 | var isPlaceholder = !!this.parentExpression.getLabel(); 35 | this.opShape.fill( isPlaceholder ? '#000' : SELECT_COLOR ); 36 | 37 | }, 38 | 39 | selectAll: function () { 40 | 41 | this.opShape.fill( ALL_SELECT_COLOR ); 42 | 43 | }, 44 | 45 | unselect: function () { 46 | 47 | this.opShape.fill( "transparent" ); 48 | 49 | } 50 | 51 | } ); 52 | 53 | function generateOpShape ( operator, label ) { 54 | 55 | if ( label !== null ) { 56 | return createCursor( operator, label ); 57 | } else { 58 | return createCommonShape( operator ); 59 | } 60 | 61 | 62 | } 63 | 64 | // 创建通用图形 65 | function createCommonShape ( operator ) { 66 | 67 | var w = 35, 68 | h = 50, 69 | shape = null; 70 | 71 | shape = new kity.Rect( w, h, 0, 0 ).stroke( "black" ).fill( "transparent" ); 72 | shape.setAttr( "stroke-dasharray", "5, 5" ); 73 | 74 | operator.addOperatorShape( shape ); 75 | 76 | return shape; 77 | 78 | } 79 | 80 | function createCursor ( operator, label) { 81 | 82 | var cursorShape = new kity.Rect( 1, 50, 0, 0 ).fill( "#000" ); 83 | 84 | cursorShape.setAttr( "style", "display: block" ); 85 | 86 | operator.addOperatorShape( cursorShape ); 87 | 88 | return cursorShape; 89 | 90 | } 91 | 92 | // 创建根占位符图形 93 | // function createRootPlaceholder ( operator, label ) { 94 | 95 | // var textShape = new kity.Text( label ).fill( FILL_COLOR ), 96 | // shapeGroup = new kity.Group(), 97 | // padding = 20, 98 | // radius = 7, 99 | // borderBoxShape = new kity.Rect( 0, 0, 0, 0, radius ).stroke( FILL_COLOR ).fill( "transparent" ), 100 | // textBox = null; 101 | 102 | // textShape.setFontSize( 40 ); 103 | // shapeGroup.addShape( borderBoxShape ); 104 | // shapeGroup.addShape( textShape ); 105 | // operator.addOperatorShape( shapeGroup ); 106 | 107 | // textBox = textShape.getFixRenderBox(); 108 | 109 | // // 宽度要加上padding 110 | // borderBoxShape.stroke( FILL_COLOR ).fill( "transparent" ); 111 | // borderBoxShape.setSize( textBox.width + padding * 2, textBox.height + padding * 2 ); 112 | // borderBoxShape.setRadius( radius ); 113 | // borderBoxShape.setAttr( "stroke-dasharray", "5, 5" ); 114 | 115 | // textShape.setAttr( { 116 | // dx: 0-textBox.x, 117 | // dy: 0-textBox.y 118 | // } ); 119 | 120 | // textShape.translate( padding, padding ); 121 | 122 | // // 对于根占位符, 返回的不是组, 而是组容器内部的虚线框。 以方便选中变色 123 | // return borderBoxShape; 124 | 125 | // } 126 | 127 | } ); -------------------------------------------------------------------------------- /src/ui/keyboard/keyboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-14 16:31:36 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-05-07 10:26:22 6 | */ 7 | define(function (require) { 8 | var kity = require('kity'), 9 | UiImpl = require('ui/ui-impl/ui'), 10 | $$ = require('ui/ui-impl/ui-utils'), 11 | Keyboard = kity.createClass('Keyboard', { 12 | constructor: function (uiComponent, kfEditor) { 13 | this.kfEditor = kfEditor; 14 | this.uiComponent = uiComponent; 15 | this.initKeyboardElements(); 16 | 17 | // this.initServices(); 18 | 19 | this.initEvent(); 20 | }, 21 | 22 | // initServices: function () { 23 | // this.kfEditor.registerService('ui.toolbar.disable', this, { 24 | // disableToolbar: this.disableToolbar, 25 | // }); 26 | 27 | // this.kfEditor.registerService('ui.toolbar.enable', this, { 28 | // enableToolbar: this.enableToolbar, 29 | // }); 30 | 31 | // this.kfEditor.registerService('ui.toolbar.close', this, { 32 | // closeToolbar: this.closeToolbar, 33 | // }); 34 | // }, 35 | 36 | initEvent: function () { 37 | var _self = this; 38 | 39 | $$.on(this.uiComponent.keyboardContainer, 'mousedown', function (e) { 40 | e.preventDefault(); 41 | }); 42 | 43 | $$.on(this.uiComponent.keyboardContainer, 'mousewheel', function (e) { 44 | e.preventDefault(); 45 | }); 46 | 47 | // // 通知所有组件关闭 48 | // $$.on(this.kfEditor.getContainer(), 'mousedown', function () { 49 | // _self.notify('closeAll'); 50 | // }); 51 | 52 | // 订阅数据选择主题 53 | $$.subscribe('panel.select', function (data) { 54 | _self.insertSource(data); 55 | }); 56 | }, 57 | 58 | insertSource: function (val) { 59 | this.kfEditor.requestService('control.insert.string', val); 60 | this.kfEditor.eclassWebService.send({ 61 | type: 'common.selectKey', 62 | data: { 63 | body: { 64 | key: val, 65 | }, 66 | }, 67 | }); 68 | }, 69 | 70 | // disableToolbar: function () { 71 | // kity.Utils.each(this.elements, function (ele) { 72 | // ele.disable && ele.disable(); 73 | // }); 74 | // }, 75 | 76 | // enableToolbar: function () { 77 | // kity.Utils.each(this.elements, function (ele) { 78 | // ele.enable && ele.enable(); 79 | // }); 80 | // }, 81 | 82 | // getContainer: function () { 83 | // return this.kfEditor.requestService('ui.get.canvas.container'); 84 | // }, 85 | 86 | // closeToolbar: function () { 87 | // this.closeElement(); 88 | // }, 89 | 90 | // 接受到关闭通知 91 | // notify: function (type) { 92 | // switch (type) { 93 | // // 关闭所有组件 94 | // case 'closeAll': 95 | // // 关闭其他组件 96 | // case 'closeOther': 97 | // this.closeElement(arguments[1]); 98 | // return; 99 | // } 100 | // }, 101 | 102 | // closeElement: function (exception) { 103 | // kity.Utils.each(this.elements, function (ele) { 104 | // if (ele != exception) { 105 | // ele.hide && ele.hide(); 106 | // } 107 | // }); 108 | // }, 109 | 110 | initKeyboardElements: function () { 111 | var doc = this.uiComponent.keyboardContainer.ownerDocument; 112 | 113 | var ele = createKeyboard(doc, this.kfEditor); 114 | this.appendElement(ele); 115 | }, 116 | 117 | appendElement: function (uiElement) { 118 | uiElement.attachTo(this.uiComponent.keyboardContainer); 119 | }, 120 | }); 121 | 122 | function createKeyboard(doc, kfEditor) { 123 | return new UiImpl.Keyboard(doc, kfEditor); 124 | } 125 | 126 | return Keyboard; 127 | }); 128 | -------------------------------------------------------------------------------- /dev-lib/dev-define.js: -------------------------------------------------------------------------------- 1 | /** 2 | * cmd 内部定义 3 | * 开发用 4 | */ 5 | 6 | ( function ( global ) { 7 | 8 | var _modules = {}, 9 | // 记录模块执行顺序的key列表 10 | keyList = [], 11 | loaded = {}; 12 | 13 | global.inc = { 14 | 15 | base: '', 16 | 17 | config: function ( options ) { 18 | 19 | this.base = options.base || ''; 20 | 21 | }, 22 | 23 | record: function ( key ) { 24 | keyList.push( key ); 25 | }, 26 | 27 | use: function ( id ) { 28 | 29 | return require( id ); 30 | 31 | }, 32 | 33 | remove: function ( node ) { 34 | 35 | node.parentNode.removeChild( node ); 36 | 37 | } 38 | 39 | }; 40 | 41 | global.define = function ( id, deps, f ) { 42 | 43 | var argLen = arguments.length, 44 | module = null; 45 | 46 | switch ( argLen ) { 47 | 48 | case 1: 49 | f = id; 50 | id = keyList.shift(); 51 | break; 52 | 53 | case 2: 54 | if ( typeof id === 'string' ) { 55 | 56 | f = deps; 57 | 58 | } else { 59 | 60 | f = deps; 61 | id = keyList.shift(); 62 | 63 | } 64 | 65 | break; 66 | 67 | } 68 | 69 | module = _modules[ id ] = { 70 | 71 | exports: {}, 72 | value: null, 73 | factory: null 74 | 75 | }; 76 | 77 | loadDeps( f ); 78 | 79 | if ( typeof f === 'function' ) { 80 | 81 | module.factory = f; 82 | 83 | } else { 84 | 85 | module.value = f; 86 | 87 | } 88 | 89 | } 90 | 91 | function require ( id ) { 92 | 93 | var exports = {}, 94 | module = _modules[ id ]; 95 | 96 | if ( module.value ) { 97 | 98 | return module.value; 99 | 100 | } 101 | 102 | exports = module.factory( require, module.exports, module ); 103 | 104 | if ( exports ) { 105 | 106 | module.exports = exports; 107 | 108 | } 109 | 110 | module.value = module.exports; 111 | module.exports = null; 112 | module.factory = null; 113 | 114 | return module.value; 115 | 116 | } 117 | 118 | function loadDeps ( factory ) { 119 | 120 | var deps = null, 121 | pathname = location.pathname, 122 | uri = location.protocol + '//' + location.host; 123 | 124 | pathname = pathname.split( '/'); 125 | 126 | if ( pathname[ pathname.length - 1 ] !== '' ) { 127 | 128 | pathname[ pathname.length - 1 ] = ''; 129 | 130 | } 131 | 132 | uri += pathname.join( '/' ); 133 | 134 | if ( typeof factory === 'function' ) { 135 | 136 | deps = loadDepsByFunction( factory ); 137 | 138 | } else { 139 | 140 | // 未处理object的情况 141 | return; 142 | 143 | } 144 | 145 | for ( var i = 0, len = deps.length; i < len; i++ ) { 146 | 147 | var key = deps[ i ]; 148 | 149 | if ( loaded[ key ] ) { 150 | continue; 151 | } 152 | 153 | loaded[ key ] = true; 154 | 155 | document.write( '' ); 156 | document.write( '' ); 157 | 158 | } 159 | 160 | } 161 | 162 | function loadDepsByFunction ( factory ) { 163 | 164 | var content = factory.toString(), 165 | match = null, 166 | deps = [], 167 | pattern = /require\s*\(\s*([^)]+?)\s*\)/g; 168 | 169 | while ( match = pattern.exec( content ) ) { 170 | 171 | deps.push( match[ 1 ].replace( /'|"/g, '' ) ); 172 | 173 | } 174 | 175 | return deps; 176 | 177 | } 178 | 179 | } )( this ); -------------------------------------------------------------------------------- /src/ui/ui-impl/keyboard/panel/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-16 18:52:57 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-05-06 15:42:41 6 | */ 7 | define(function (require) { 8 | const kity = require('kity'); 9 | const $$ = require('ui/ui-impl/ui-utils'); 10 | const Panel = kity.createClass('Panel', { 11 | constructor(parentNode, parentProps) { 12 | this.parentNode = parentNode; 13 | this.props = parentProps; 14 | this.prefix = parentProps.prefix + 'keyboard-panel'; 15 | this.scrollHeight = parentProps.scrollHeight; 16 | this.rowHeight = parentProps.rowHeight; 17 | // 初始化状态 18 | this.state = { 19 | type: this.props.type, 20 | page: this.props.page, 21 | }; 22 | 23 | this.containerClassName = this.prefix; 24 | this.listClassName = `${this.prefix}-list`; 25 | this.itemClassName = `${this.prefix}-list-item`; 26 | 27 | this._onClick = this._onClick.bind(this); 28 | }, 29 | _calculateHeight: function (type, page) { 30 | // 计算当前类型的起始行数 31 | const curTypeIndex = 32 | this.props.panelConstant.findIndex((item) => item.type === type) || 0; 33 | const prevList = 34 | this.props.panelConstant.slice(0, curTypeIndex) || this.props.panelConstant[0]; 35 | const rows = prevList.reduce((acc, cur, index) => { 36 | const curRows = Math.ceil(cur.items.length / 8); 37 | return acc + curRows; 38 | }, 0); 39 | // 计算当前类型的锚点坐标 40 | const typeHeight = rows * this.rowHeight; 41 | return typeHeight + page * this.scrollHeight; 42 | }, 43 | _render: function () { 44 | const list = this.props.panelConstant.reduce((acc, cur, index) => { 45 | const itemLenOfLastRow = cur.items.length % 8; 46 | const blankArr = itemLenOfLastRow ? new Array(8 - itemLenOfLastRow).fill('') : []; 47 | return acc.concat(cur.items, blankArr); 48 | }, []); 49 | const table = list.reduce((acc, cur, index) => { 50 | const row = Math.floor(index / 8); 51 | const col = Math.floor(index % 8); 52 | if (!acc[row]) acc[row] = []; 53 | acc[row][col] = cur; 54 | return acc; 55 | }, []); 56 | return $$.ele(this.props.doc, 'div', { 57 | className: this.containerClassName, 58 | content: ` 59 | 65 | ${table 66 | .map( 67 | (row) => 68 | '' + 69 | row 70 | .map((x) => 71 | x 72 | ? `' 80 | ) 81 | .join('')} 82 |
` 76 | : null 77 | ) 78 | .join('') + 79 | '
83 | `, 84 | }); 85 | }, 86 | mount: function () { 87 | const node = this._render(); 88 | $$.delegate(this.parentNode, '.' + this.itemClassName, 'click', this._onClick); 89 | this.parentNode.appendChild(node); 90 | }, 91 | update: function (nextProps) { 92 | $('#' + this.listClassName).css( 93 | 'top', 94 | `-${this._calculateHeight(nextProps.type, nextProps.page)}px` 95 | ); 96 | this._setState({ 97 | type: nextProps.type, 98 | page: nextProps.page, 99 | }); 100 | }, 101 | _onClick: function (e) { 102 | const val = e.target.dataset.value; 103 | this.props.onClick(val); 104 | }, 105 | _setState: function (nextState) { 106 | this.state = { 107 | ...this.state, 108 | ...nextState, 109 | }; 110 | }, 111 | }); 112 | return Panel; 113 | }); 114 | -------------------------------------------------------------------------------- /src/base/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-3-17. 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | // copy保护 8 | var MAX_COPY_DEEP = 10, 9 | 10 | commonUtils = { 11 | extend: function ( target, source ) { 12 | 13 | var isDeep = false; 14 | 15 | if ( typeof target === "boolean" ) { 16 | isDeep = target; 17 | target = source; 18 | source = [].splice.call( arguments, 2 ); 19 | } else { 20 | source = [].splice.call( arguments, 1 ); 21 | } 22 | 23 | if ( !target ) { 24 | throw new Error( 'Utils: extend, target can not be empty' ); 25 | } 26 | 27 | commonUtils.each( source, function ( src ) { 28 | 29 | if ( src && typeof src === "object" || typeof src === "function" ) { 30 | 31 | copy( isDeep, target, src ); 32 | 33 | } 34 | 35 | } ); 36 | 37 | return target; 38 | 39 | }, 40 | 41 | /** 42 | * 返回给定节点parent是否包含target节点 43 | * @param parent 44 | * @param target 45 | */ 46 | contains: function ( parent, target ) { 47 | 48 | if ( parent.contains ) { 49 | return parent.contains( target ); 50 | } else if ( parent.compareDocumentPosition ) { 51 | return !!( parent.compareDocumentPosition( target ) & 16 ); 52 | } 53 | 54 | }, 55 | 56 | getRect: function ( node ) { 57 | return node.getBoundingClientRect(); 58 | }, 59 | 60 | isArray: function ( obj ) { 61 | return obj && ({}).toString.call( obj ) === "[object Array]"; 62 | }, 63 | 64 | isString: function ( obj ) { 65 | return typeof obj === "string"; 66 | }, 67 | 68 | proxy: function ( fn, context ) { 69 | 70 | return function () { 71 | return fn.apply( context, arguments ); 72 | }; 73 | 74 | }, 75 | 76 | each: function ( obj, fn ) { 77 | 78 | if ( !obj ) { 79 | return; 80 | } 81 | 82 | if ( 'length' in obj && typeof obj.length === "number" ) { 83 | 84 | for ( var i = 0, len = obj.length; i < len; i++ ) { 85 | 86 | if ( fn.call( null, obj[ i ], i, obj ) === false ) { 87 | break; 88 | } 89 | 90 | } 91 | 92 | } else { 93 | 94 | for ( var key in obj ) { 95 | 96 | if ( obj.hasOwnProperty( key ) ) { 97 | if ( fn.call( null, obj[ key ], key, obj ) === false ) { 98 | break; 99 | } 100 | } 101 | 102 | } 103 | 104 | } 105 | 106 | } 107 | }; 108 | 109 | function copy ( isDeep, target, source, count ) { 110 | 111 | count = count | 0; 112 | 113 | if ( count > MAX_COPY_DEEP ) { 114 | return source; 115 | } 116 | 117 | count++; 118 | 119 | commonUtils.each( source, function ( value, index, origin ) { 120 | 121 | if ( isDeep ) { 122 | 123 | if ( !value || ( typeof value !== "object" && typeof value !== "function" ) ) { 124 | target[ index ] = value; 125 | } else { 126 | target[ index ] = target[ index ] || ( commonUtils.isArray( value ) ? [] : {} ); 127 | target[ index ] = copy( isDeep, target[ index ], value, count ); 128 | } 129 | 130 | } else { 131 | target[ index ] = value; 132 | } 133 | 134 | } ); 135 | 136 | return target; 137 | 138 | } 139 | 140 | return commonUtils; 141 | 142 | } ); -------------------------------------------------------------------------------- /src/editor/command.md: -------------------------------------------------------------------------------- 1 | # 接口文档 2 | 3 | 公式编辑器借助 web-service 库,通过事件的触发和监听实现数据交互,有 webview 和 documentEvent 两种通信方式。下面的示例均以 documentEvent 作为通信通道,公式编辑器启用 documentEvent 需要在 url 中增加 protocol,详细请参照[readme](../../readme.md) 4 | 5 | ## 信令格式 6 | 7 | 格式如下 8 | 9 | ```js 10 | { 11 | type: string; // 信令的类型 12 | headers: { 13 | reqId: string; // 信令的请求id,每次都随机 14 | } 15 | data: { 16 | body: object, // body中存储具体数据 17 | }, 18 | } 19 | ``` 20 | 21 | 以导出公式-common.setFormula 为例,控制台输入如下调试语句: 22 | 23 | ```js 24 | document.addEventListener('documentMessage', (e) => { 25 | console.log(e); 26 | }); 27 | ``` 28 | 29 | 可以看到调试数据如下所示: 30 | 31 | ```js 32 | CustomEvent: 33 | { 34 | ... 35 | detail: { 36 | type: 'common.setFormula', 37 | headers: { 38 | reqId: '......' 39 | }, 40 | data: { 41 | body: { 42 | formula: '<>', 43 | formulaSrc: 'data:image/png;base64,......' 44 | } 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | ## 事件列表 51 | 52 | 1、关闭弹窗 53 | 54 | - 说明:点击关闭弹窗按钮时触发,使用方可监听该事件,实现页面跳转、弹窗关闭。 55 | - 发布者:公式编辑器 56 | - 订阅者:使用方 57 | - 事件名:common.closeModal 58 | - 使用示例: 59 | 60 | ```js 61 | document.addEventListener('documentMessage', (e) => { 62 | const { type } = e?.detail; 63 | const msg = e?.detail?.data?.body; 64 | if (type !== 'common.closeModal') return; 65 | console.log('关闭'); 66 | }); 67 | ``` 68 | 69 | 2、鼠标点击左侧 tab 切换事件 70 | 71 | - 说明:点击左侧“常用”、“单位”按钮切换字符类型时触发 72 | - 发布者:公式编辑器 73 | - 订阅者:使用方 74 | - 事件名:common.setType 75 | - 属性列表: 76 | |属性名|类型|默认值|备注| 77 | |-|-|-|-| 78 | |type|'common'\|'algebra'\|'geometry'\|'letter'\|'other'|无|当前字符类型| 79 | - 使用示例: 80 | 81 | ```js 82 | document.addEventListener('documentMessage', (e) => { 83 | const { type } = e?.detail; 84 | const msg = e?.detail?.data?.body; 85 | if (type !== 'common.setType') return; 86 | console.log('msg', msg.type); // msg algebra 87 | }); 88 | ``` 89 | 90 | 3、鼠标点击 latex 字符事件 91 | 92 | - 说明:点击键盘按钮时触发,常用于业务方埋点 93 | - 发布者:公式编辑器 94 | - 订阅者:使用方 95 | - 事件名:common.selectKey 96 | - 属性列表: 97 | |属性名|类型|默认值|备注| 98 | |-|-|-|-| 99 | |key|string|无|键盘字符的 latex 值| 100 | - 使用示例: 101 | 102 | ```js 103 | document.addEventListener('documentMessage', (e) => { 104 | const { type } = e?.detail; 105 | const msg = e?.detail?.data?.body; 106 | if (type !== 'common.selectKey') return; 107 | console.log('msg', msg.key); // msg \delta 108 | }); 109 | ``` 110 | 111 | 4、导出公式事件 112 | 113 | - 说明:鼠标点击完成按钮,导出编辑器中的公式的 latex 值和 base64 格式的图片 114 | - 发布者:公式编辑器 115 | - 订阅者:使用方 116 | - 事件名:common.setFormula 117 | - 属性列表: 118 | |属性名|类型|默认值|备注| 119 | |-|-|-|-| 120 | |formula|string|无|总 latex 值| 121 | |formulaSrc|string|无|base64 格式的 url| 122 | - 使用示例: 123 | 124 | ```js 125 | document.addEventListener('documentMessage', (e) => { 126 | const { type } = e?.detail; 127 | const msg = e?.detail?.data?.body; 128 | if (type !== 'common.setFormula') return; 129 | console.log('msg', msg.formula, msg.formulaSrc); // msg 123 data:image/png;...... 130 | }); 131 | ``` 132 | 133 | 5、初始化事件 134 | 135 | - 说明:通知使用方,公式编辑器已初始化完成,完成后才可以进行通信。 136 | - 发布者:公式编辑器 137 | - 订阅者:使用方 138 | - 事件名:common.ready 139 | - 使用示例: 140 | 141 | ```js 142 | document.addEventListener('documentMessage', (e) => { 143 | const { type } = e?.detail; 144 | const msg = e?.detail?.data?.body; 145 | if (type !== 'common.ready') return; 146 | console.log('已准备'); 147 | }); 148 | ``` 149 | 150 | 6、设置公式初始值 151 | 152 | - 说明:通知公式编辑器初始 latex 值,一般用于二次编辑 latex 公式 153 | - 发布者:使用者 154 | - 订阅者:公式编辑器 155 | - 事件名:common.readFormula 156 | - 属性列表: 157 | |属性名|类型|默认值|备注| 158 | |-|-|-|-| 159 | |formula|string|无|总 latex 值| 160 | - 使用示例: 161 | 162 | ```js 163 | const event = new CustomEvent('documentMessage', { 164 | detail: { 165 | type: 'common.readFormula', 166 | headers: { 167 | reqId: 1, 168 | }, 169 | data: { 170 | body: { 171 | formula: 'abc', 172 | }, 173 | }, 174 | }, 175 | }); 176 | document.dispatchEvent(event); 177 | ``` 178 | 179 | 7、清空公式编辑器 180 | 181 | - 说明:通知公式编辑器清空内容和左侧tab,一般用于关闭弹窗。 182 | - 发布者:使用者 183 | - 订阅者:公式编辑器 184 | - 事件名:common.clearFormula 185 | - 使用示例: 186 | 187 | ```js 188 | const event = new CustomEvent('documentMessage', { 189 | detail: { 190 | type: 'common.clearFormula', 191 | headers: { 192 | reqId: 2, 193 | }, 194 | data: { 195 | body: {}, 196 | }, 197 | }, 198 | }); 199 | document.dispatchEvent(event); 200 | ``` 201 | -------------------------------------------------------------------------------- /src/ui/ui-impl/list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-3-31. 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var kity = require( "kity" ), 8 | 9 | PREFIX = "kf-editor-ui-", 10 | 11 | // UiUitls 12 | $$ = require( "ui/ui-impl/ui-utils" ), 13 | 14 | List = kity.createClass( "List", { 15 | 16 | constructor: function ( doc, options ) { 17 | 18 | this.options = options; 19 | 20 | this.doc = doc; 21 | 22 | this.onselectHandler = null; 23 | 24 | this.currentSelect = -1; 25 | 26 | this.element = this.createBox(); 27 | this.itemGroups = this.createItems(); 28 | 29 | this.mergeElement(); 30 | 31 | }, 32 | 33 | // 预定义的方法留空 34 | onselectHandler: function ( index, oldIndex ) {}, 35 | 36 | setSelectHandler: function ( selectHandler ) { 37 | this.onselectHandler = selectHandler; 38 | }, 39 | 40 | createBox: function () { 41 | 42 | var boxNode = $$.ele( this.doc, "div", { 43 | className: PREFIX + "list" 44 | } ), 45 | 46 | // 创建背景 47 | bgNode = $$.ele( this.doc, "div", { 48 | className: PREFIX + "list-bg" 49 | } ); 50 | 51 | if ( "width" in this.options ) { 52 | boxNode.style.width = this.options.width + "px"; 53 | } 54 | 55 | boxNode.appendChild( bgNode ); 56 | 57 | return boxNode; 58 | 59 | }, 60 | 61 | select: function ( index ) { 62 | 63 | var oldSelect = this.currentSelect; 64 | 65 | if ( oldSelect === -1 ) { 66 | oldSelect = index; 67 | } 68 | 69 | this.unselect( oldSelect ); 70 | 71 | this.currentSelect = index; 72 | 73 | $$.getClassList( this.itemGroups.items[ index ] ).add( PREFIX + "list-item-select" ); 74 | 75 | this.onselectHandler( index, oldSelect ); 76 | 77 | }, 78 | 79 | unselect: function ( index ) { 80 | 81 | $$.getClassList( this.itemGroups.items[ index ] ).remove( PREFIX + "list-item-select" ); 82 | 83 | }, 84 | 85 | setOffset: function ( x, y ) { 86 | this.element.style.left = x + "px"; 87 | this.element.style.top = y + "px"; 88 | }, 89 | 90 | initEvent: function () { 91 | 92 | var className = "." + PREFIX + "list-item", 93 | _self = this; 94 | 95 | $$.delegate( this.itemGroups.container, className, "mousedown", function ( e ) { 96 | 97 | e.preventDefault(); 98 | 99 | if ( e.which !== 1 ) { 100 | return; 101 | } 102 | 103 | _self.select( this.getAttribute( "data-index" ) ); 104 | 105 | } ); 106 | 107 | $$.on( this.element, "mousedown", function ( e ) { 108 | 109 | e.stopPropagation(); 110 | e.preventDefault(); 111 | 112 | } ); 113 | 114 | }, 115 | 116 | getPositionInfo: function () { 117 | return $$.getRectBox( this.element ); 118 | }, 119 | 120 | createItems: function () { 121 | 122 | var doc = this.doc, 123 | groupNode = null, 124 | itemNode = null, 125 | iconNode = null, 126 | items = [], 127 | itemContainer = null; 128 | 129 | groupNode = $$.ele( this.doc, "div", { 130 | className: PREFIX + "list-item" 131 | } ); 132 | 133 | itemContainer = groupNode.cloneNode( false ); 134 | itemContainer.className = PREFIX + "list-item-container"; 135 | 136 | kity.Utils.each( this.options.items, function ( itemText, i ) { 137 | 138 | itemNode = groupNode.cloneNode( false ); 139 | 140 | iconNode = groupNode.cloneNode( false ); 141 | iconNode.className = PREFIX + "list-item-icon"; 142 | 143 | itemNode.appendChild( iconNode ); 144 | itemNode.appendChild( $$.ele( doc, "text", itemText ) ); 145 | 146 | itemNode.setAttribute( "data-index", i ); 147 | 148 | items.push( itemNode ); 149 | itemContainer.appendChild( itemNode ); 150 | 151 | } ); 152 | 153 | return { 154 | container: itemContainer, 155 | items: items 156 | }; 157 | 158 | }, 159 | 160 | mergeElement: function () { 161 | 162 | this.element.appendChild( this.itemGroups.container ); 163 | 164 | }, 165 | 166 | mountTo: function ( container ) { 167 | container.appendChild( this.element ); 168 | } 169 | 170 | } ); 171 | 172 | return List; 173 | 174 | } ); -------------------------------------------------------------------------------- /assets/styles/theme/android.less: -------------------------------------------------------------------------------- 1 | .android { 2 | width: 1920px; 3 | height: 835px; 4 | position: fixed; 5 | bottom: 0; 6 | background-color: transparent; 7 | user-select: none; 8 | * { 9 | box-sizing: border-box; 10 | margin: 0; 11 | padding: 0; 12 | list-style: none; 13 | } 14 | .kf-editor-edit-area { 15 | width: 100%; 16 | height: auto; 17 | float: left; 18 | padding: 40px; 19 | position: relative; 20 | .kf-editor-edit-scrollbar { 21 | bottom: 0px; 22 | } 23 | .kf-editor-header-container { 24 | display: none; 25 | } 26 | .kf-editor-canvas-wrapper { 27 | width: 1840px; 28 | height: 140px; 29 | background: #f8f8f8; 30 | padding: 0 40px; 31 | float: left; 32 | border-radius: 54px; 33 | 34 | .kf-editor-canvas-container { 35 | width: 100%; 36 | height: 100%; 37 | // margin: 40px; 38 | } 39 | } 40 | } 41 | .kf-editor-edit-keyboard { 42 | width: 1920px; 43 | height: 615px; 44 | float: left; 45 | background: #eceff1; 46 | overflow: hidden; 47 | .kf-editor-ui-keyboard-menu { 48 | width: 274px; 49 | height: 615px; 50 | float: left; 51 | border-right: 1px solid rgba(0, 0, 0, 0.1); 52 | .kf-editor-ui-keyboard-menu-list { 53 | width: 100%; 54 | height: 100%; 55 | padding: 26px; 56 | background: #eceff1; 57 | overflow: hidden; 58 | .kf-editor-ui-keyboard-menu-list-item { 59 | width: 183px; 60 | height: 75px; 61 | margin: 19px; 62 | text-align: center; 63 | line-height: 75px; 64 | font-size: 16px; 65 | color: #49494d; 66 | cursor: pointer; 67 | float: left; 68 | font-size: 34px; 69 | } 70 | .kf-editor-ui-keyboard-menu-list-item-active { 71 | background: #ffab30; 72 | border-radius: 8px; 73 | color: #ffffff; 74 | } 75 | } 76 | } 77 | .kf-editor-ui-keyboard-panel { 78 | width: 1425px; 79 | height: 100%; 80 | float: left; 81 | .kf-editor-ui-keyboard-panel-list { 82 | width: 100%; 83 | height: 100%; 84 | padding: 14px 0 0 33px; 85 | overflow: hidden; 86 | position: relative; 87 | transition: top 0.3s linear; 88 | .kf-editor-ui-keyboard-panel-list-item { 89 | width: 148px; 90 | height: 122px; 91 | margin: 12px; 92 | text-align: center; 93 | line-height: 121px; 94 | font-size: 16px; 95 | color: #49494d; 96 | cursor: pointer; 97 | float: left; 98 | position: relative; 99 | &:active { 100 | &::before { 101 | content: ''; 102 | display: block; 103 | position: absolute; 104 | width: 100%; 105 | height: 100%; 106 | background-color: rgba(64, 75, 80, 0.15); 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | .kf-editor-ui-keyboard-page { 114 | width: 212px; 115 | height: 100%; 116 | float: left; 117 | .kf-editor-ui-keyboard-page-list { 118 | width: 100%; 119 | height: 100%; 120 | padding: 14px 0px 26px 0; 121 | overflow: hidden; 122 | .kf-editor-ui-keyboard-page-list-item { 123 | width: 100%; 124 | height: 122px; 125 | margin: 12px; 126 | float: left; 127 | font-size: 46px; 128 | color: #ff9800; 129 | text-align: center; 130 | line-height: 122px; 131 | cursor: pointer; 132 | } 133 | .kf-editor-ui-keyboard-page-list-item-delete { 134 | width: 100%; 135 | height: 122px; 136 | background: url('https://store-g1.seewo.com/easiclass-public/a7ea5932b621487aaa1c8b9dd4e58cb0') center no-repeat; 137 | background-size: initial; 138 | &:active { 139 | background-color: rgba(64, 75, 80, 0.15); 140 | } 141 | } 142 | .kf-editor-ui-keyboard-page-list-item-prev { 143 | position: relative; 144 | &::before { 145 | content: ' '; 146 | display: block; 147 | width: 100%; 148 | height: 100%; 149 | background: url('https://store-g1.seewo.com/easiclass-public/27257523137e47499311266d0f872c8b') center no-repeat; 150 | background-size: initial; 151 | } 152 | &:not(.kf-editor-ui-keyboard-page-list-item-disabled):active { 153 | background-color: rgba(64, 75, 80, 0.15); 154 | } 155 | } 156 | .kf-editor-ui-keyboard-page-list-item-next { 157 | position: relative; 158 | &::before { 159 | content: ' '; 160 | display: block; 161 | width: 100%; 162 | height: 100%; 163 | background: url('https://store-g1.seewo.com/easiclass-public/3c2faacfac52472ea6cb522a34aa04db') center no-repeat; 164 | background-size: initial; 165 | } 166 | &:not(.kf-editor-ui-keyboard-page-list-item-disabled):active { 167 | background-color: rgba(64, 75, 80, 0.15); 168 | } 169 | } 170 | .kf-editor-ui-keyboard-page-list-item-disabled::before { 171 | opacity: 0.5; 172 | } 173 | } 174 | } 175 | .kf-editor-ui-keyboard-footer { 176 | display: none; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/ui/toolbar/toolbar.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 工具条组件 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var kity = require( "kity" ), 8 | 9 | UiImpl = require( "ui/ui-impl/ui" ), 10 | 11 | $$ = require( "ui/ui-impl/ui-utils" ), 12 | 13 | UI_ELE_TYPE = require( "ui/ui-impl/def/ele-type" ), 14 | 15 | Tollbar = kity.createClass( "Tollbar", { 16 | 17 | constructor: function ( uiComponent, kfEditor, elementList ) { 18 | 19 | this.kfEditor = kfEditor; 20 | this.uiComponent = uiComponent; 21 | 22 | // 工具栏元素定义列表 23 | this.elementList = elementList; 24 | 25 | this.elements = []; 26 | 27 | this.initToolbarElements(); 28 | 29 | this.initServices(); 30 | 31 | this.initEvent(); 32 | 33 | }, 34 | 35 | initServices: function () { 36 | 37 | this.kfEditor.registerService( "ui.toolbar.disable", this, { 38 | disableToolbar: this.disableToolbar 39 | } ); 40 | 41 | this.kfEditor.registerService( "ui.toolbar.enable", this, { 42 | enableToolbar: this.enableToolbar 43 | } ); 44 | 45 | this.kfEditor.registerService( "ui.toolbar.close", this, { 46 | closeToolbar: this.closeToolbar 47 | } ); 48 | 49 | }, 50 | 51 | initEvent: function () { 52 | 53 | var _self = this; 54 | 55 | $$.on( this.uiComponent.toolbarContainer, "mousedown", function ( e ) { 56 | e.preventDefault(); 57 | } ); 58 | 59 | $$.on( this.uiComponent.toolbarContainer, "mousewheel", function ( e ) { 60 | e.preventDefault(); 61 | } ); 62 | 63 | // 通知所有组件关闭 64 | $$.on( this.kfEditor.getContainer(), "mousedown", function () { 65 | _self.notify( "closeAll" ); 66 | } ); 67 | 68 | // 订阅数据选择主题 69 | $$.subscribe( "data.select", function ( data ) { 70 | 71 | _self.insertSource( data ); 72 | 73 | } ); 74 | 75 | }, 76 | 77 | insertSource: function ( val ) { 78 | 79 | this.kfEditor.requestService( "control.insert.string", val ); 80 | 81 | }, 82 | 83 | disableToolbar: function () { 84 | 85 | kity.Utils.each( this.elements, function ( ele ) { 86 | ele.disable && ele.disable(); 87 | } ); 88 | 89 | }, 90 | 91 | enableToolbar: function () { 92 | 93 | kity.Utils.each( this.elements, function ( ele ) { 94 | ele.enable && ele.enable(); 95 | } ); 96 | 97 | }, 98 | 99 | getContainer: function () { 100 | return this.kfEditor.requestService( "ui.get.canvas.container" ); 101 | }, 102 | 103 | closeToolbar: function () { 104 | this.closeElement(); 105 | }, 106 | 107 | // 接受到关闭通知 108 | notify: function ( type ) { 109 | 110 | switch ( type ) { 111 | 112 | // 关闭所有组件 113 | case "closeAll": 114 | // 关闭其他组件 115 | case "closeOther": 116 | this.closeElement( arguments[ 1 ] ); 117 | return; 118 | 119 | } 120 | 121 | }, 122 | 123 | closeElement: function ( exception ) { 124 | 125 | kity.Utils.each( this.elements, function ( ele ) { 126 | 127 | if ( ele != exception ) { 128 | ele.hide && ele.hide(); 129 | } 130 | 131 | } ); 132 | 133 | }, 134 | 135 | initToolbarElements: function () { 136 | 137 | var elements = this.elements, 138 | doc = this.uiComponent.toolbarContainer.ownerDocument, 139 | _self = this; 140 | 141 | kity.Utils.each( this.elementList, function ( eleInfo, i ) { 142 | 143 | var ele = createElement( eleInfo.type, doc, eleInfo.options ); 144 | elements.push( ele ); 145 | _self.appendElement( ele ); 146 | 147 | } ); 148 | 149 | }, 150 | 151 | appendElement: function ( uiElement ) { 152 | 153 | uiElement.setToolbar( this ); 154 | uiElement.attachTo( this.uiComponent.toolbarContainer ); 155 | 156 | } 157 | 158 | } ); 159 | 160 | function createElement ( type, doc, options ) { 161 | 162 | switch ( type ) { 163 | 164 | case UI_ELE_TYPE.DRAPDOWN_BOX: 165 | return createDrapdownBox( doc, options ); 166 | 167 | case UI_ELE_TYPE.DELIMITER: 168 | return createDelimiter( doc ); 169 | 170 | case UI_ELE_TYPE.AREA: 171 | return createArea( doc, options ); 172 | 173 | 174 | } 175 | 176 | } 177 | 178 | function createDrapdownBox ( doc, options ) { 179 | 180 | return new UiImpl.DrapdownBox( doc, options ); 181 | 182 | } 183 | 184 | function createDelimiter ( doc ) { 185 | return new UiImpl.Delimiter( doc ); 186 | } 187 | 188 | function createArea ( doc, options ) { 189 | return new UiImpl.Area( doc, options ); 190 | } 191 | 192 | return Tollbar; 193 | 194 | } ); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 公式编辑器 5 | 6 | 7 | 16 | 17 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 153 | 154 | 155 | 156 | 157 |
加载中...
158 | 159 | 160 | -------------------------------------------------------------------------------- /src/ui/ui-impl/keyboard/panel/pcConst.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-15 10:11:11 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-05-18 14:36:33 6 | */ 7 | 8 | define(function (require) { 9 | const kity = require('kity'), 10 | CHAR_POSITION = require('ui/ui-impl/keyboard/panel/position.data'), 11 | Constant = [ 12 | { type: 'common', title: '常用', index: 0, items: [] }, 13 | { type: 'algebra', title: '代数', index: 1, items: [] }, 14 | { type: 'geometry', title: '几何', index: 2, items: [] }, 15 | { type: 'letter', title: '字母', index: 3, items: [] }, 16 | { type: 'other', title: '其他', index: 4, items: [] }, 17 | ]; 18 | 19 | // ----------------------------取雪碧图icon 20 | // 常用 21 | (function () { 22 | const list = [ 23 | '<', 24 | '\\frac \\placeholder\\placeholder', 25 | '\\sqrt \\placeholder', 26 | 'a', 27 | '+', 28 | '7', 29 | '8', 30 | '9', 31 | '>', 32 | '\\left|\\placeholder\\right|', 33 | '\\placeholder^2', 34 | 'b', 35 | '-', 36 | '4', 37 | '5', 38 | '6', 39 | '\\leq', 40 | '\\left(\\placeholder\\right)', 41 | '\\sqrt [3] \\placeholder', 42 | 'x', 43 | '\\pm', 44 | '1', 45 | '2', 46 | '3', 47 | '\\geq', 48 | '%', 49 | '\\placeholder^3', 50 | 'y', 51 | ',', 52 | '0', 53 | '.', 54 | '=', 55 | ]; 56 | 57 | Constant[0].items = getIconContents( 58 | list, 59 | 'https://store-g1.seewo.com/easiclass-public/a7da75a0d13c427eb7299fac9f634783' 60 | ); 61 | })(); 62 | // 代数 63 | (function () { 64 | const list = [ 65 | '\\times', 66 | '\\div', 67 | '\\approx', 68 | '\\neq', 69 | '\\sqrt [\\placeholder] \\placeholder', 70 | '\\pi', 71 | '\\delta', 72 | '\\left[\\placeholder\\right]', 73 | '\\placeholder^\\placeholder', 74 | '\\placeholder_\\placeholder', 75 | '{^\\placeholder_\\placeholder\\placeholder}', 76 | '\\placeholder^\\placeholder_\\placeholder', 77 | '\\sum\\placeholder', 78 | '\\sum_\\placeholder\\placeholder', 79 | '\\sum^\\placeholder_\\placeholder\\placeholder', 80 | '\\int \\placeholder', 81 | '\\int^\\placeholder_\\placeholder\\placeholder', 82 | '\\iint\\placeholder', 83 | '\\iint^\\placeholder_\\placeholder\\placeholder', 84 | '\\iiint\\placeholder', 85 | '\\iiint^\\placeholder_\\placeholder\\placeholder', 86 | '\\log\\placeholder', 87 | '\\ln\\placeholder', 88 | '\\land', 89 | '\\lor', 90 | '\\neg', 91 | '\\forall', 92 | '\\exists', 93 | '\\infty', 94 | '\\cup', 95 | '\\cap', 96 | '\\in', 97 | '\\notin', 98 | '\\subset', 99 | '\\subseteq', 100 | '\\supset', 101 | '\\supseteq', 102 | '\\varnothing', 103 | '\\cdot', 104 | '\\colon' 105 | ]; 106 | 107 | Constant[1].items = getIconContents( 108 | list, 109 | 'https://store-g1.seewo.com/easiclass-public/a7da75a0d13c427eb7299fac9f634783' 110 | ); 111 | })(); 112 | // 几何 113 | (function () { 114 | const list = [ 115 | '\\sin\\placeholder', 116 | '\\cos\\placeholder', 117 | '\\tan\\placeholder', 118 | '\\sec\\placeholder', 119 | '\\csc\\placeholder', 120 | '\\cot\\placeholder', 121 | '\\arcsin\\placeholder', 122 | '\\arccos\\placeholder', 123 | '\\arctan\\placeholder', 124 | '\\triangle', 125 | '\\sim', 126 | '\\cong', 127 | '\\angle', 128 | '\\bot', 129 | '\\alpha', 130 | '\\beta', 131 | '\\gamma', 132 | '\\theta', 133 | '\\degree', 134 | '\\bigcirc' 135 | ]; 136 | 137 | Constant[2].items = getIconContents( 138 | list, 139 | 'https://store-g1.seewo.com/easiclass-public/a7da75a0d13c427eb7299fac9f634783' 140 | ); 141 | })(); 142 | // 单位 143 | (function () { 144 | const list = [ 145 | 'a', 146 | 'b', 147 | 'c', 148 | 'd', 149 | 'e', 150 | 'f', 151 | 'g', 152 | 'h', 153 | 'i', 154 | 'j', 155 | 'k', 156 | 'l', 157 | 'm', 158 | 'n', 159 | 'o', 160 | 'p', 161 | 'q', 162 | 'r', 163 | 's', 164 | 't', 165 | 'u', 166 | 'v', 167 | 'w', 168 | 'x', 169 | 'y', 170 | 'z', 171 | ]; 172 | 173 | Constant[3].items = getIconContents( 174 | list, 175 | 'https://store-g1.seewo.com/easiclass-public/a7da75a0d13c427eb7299fac9f634783' 176 | ); 177 | })(); 178 | // 其他 179 | (function () { 180 | const list = [ 181 | '\\Omega', 182 | '\\because', 183 | '\\therefore', 184 | '\\Longrightarrow', 185 | '\\Leftrightarrow', 186 | '\\uparrow', 187 | '\\downarrow', 188 | '\\lambda', 189 | '\\kappa', 190 | '\\mu', 191 | '\\rho', 192 | '\\sigma', 193 | '\\tau', 194 | '\\upsilon', 195 | '\\varphi', 196 | '\\Psi', 197 | '\\omega', 198 | '\\varepsilon', 199 | '\\zeta', 200 | '\\eta', 201 | '\\nu', 202 | '\\xi', 203 | '\\chi', 204 | ]; 205 | 206 | Constant[4].items = getIconContents( 207 | list, 208 | 'https://store-g1.seewo.com/easiclass-public/a7da75a0d13c427eb7299fac9f634783' 209 | ); 210 | })(); 211 | 212 | function getIconContents(keySet, imgSrc) { 213 | const result = []; 214 | 215 | kity.Utils.each(keySet, function (key) { 216 | const point = CHAR_POSITION[key] || { x: 0, y: 0 }; 217 | const pos = { x: point.x * 83, y: point.y * 65 }; 218 | result.push({ 219 | key: key, 220 | img: imgSrc, 221 | pos, 222 | }); 223 | }); 224 | 225 | return result; 226 | } 227 | 228 | return Constant; 229 | }); 230 | -------------------------------------------------------------------------------- /src/ui/ui-impl/keyboard/panel/androidConst.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-22 18:00:32 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-05-18 14:36:50 6 | */ 7 | 8 | define(function (require) { 9 | const kity = require('kity'), 10 | CHAR_POSITION = require('ui/ui-impl/keyboard/panel/position.data'), 11 | Constant = [ 12 | { type: 'common', title: '常用', index: 0, items: [] }, 13 | { type: 'algebra', title: '代数', index: 1, items: [] }, 14 | { type: 'geometry', title: '几何', index: 2, items: [] }, 15 | { type: 'letter', title: '字母', index: 3, items: [] }, 16 | { type: 'other', title: '其他', index: 4, items: [] }, 17 | ]; 18 | 19 | // ----------------------------取雪碧图icon 20 | // 常用 21 | (function () { 22 | const list = [ 23 | '<', 24 | '\\frac \\placeholder\\placeholder', 25 | '\\sqrt \\placeholder', 26 | 'a', 27 | '+', 28 | '7', 29 | '8', 30 | '9', 31 | '>', 32 | '\\left|\\placeholder\\right|', 33 | '\\placeholder^2', 34 | 'b', 35 | '-', 36 | '4', 37 | '5', 38 | '6', 39 | '\\leq', 40 | '\\left(\\placeholder\\right)', 41 | '\\sqrt [3] \\placeholder', 42 | 'x', 43 | '\\pm', 44 | '1', 45 | '2', 46 | '3', 47 | '\\geq', 48 | '%', 49 | '\\placeholder^3', 50 | 'y', 51 | ',', 52 | '0', 53 | '.', 54 | '=', 55 | ]; 56 | 57 | Constant[0].items = getIconContents( 58 | list, 59 | 'https://store-g1.seewo.com/easiclass-public/ec4941099e30462b935956b413e3ca8d' 60 | ); 61 | })(); 62 | // 代数 63 | (function () { 64 | const list = [ 65 | '\\times', 66 | '\\div', 67 | '\\approx', 68 | '\\neq', 69 | '\\sqrt [\\placeholder] \\placeholder', 70 | '\\pi', 71 | '\\delta', 72 | '\\left[\\placeholder\\right]', 73 | '\\placeholder^\\placeholder', 74 | '\\placeholder_\\placeholder', 75 | '{^\\placeholder_\\placeholder\\placeholder}', 76 | '\\placeholder^\\placeholder_\\placeholder', 77 | '\\sum\\placeholder', 78 | '\\sum_\\placeholder\\placeholder', 79 | '\\sum^\\placeholder_\\placeholder\\placeholder', 80 | '\\int \\placeholder', 81 | '\\int^\\placeholder_\\placeholder\\placeholder', 82 | '\\iint\\placeholder', 83 | '\\iint^\\placeholder_\\placeholder\\placeholder', 84 | '\\iiint\\placeholder', 85 | '\\iiint^\\placeholder_\\placeholder\\placeholder', 86 | '\\log\\placeholder', 87 | '\\ln\\placeholder', 88 | '\\land', 89 | '\\lor', 90 | '\\neg', 91 | '\\forall', 92 | '\\exists', 93 | '\\infty', 94 | '\\cup', 95 | '\\cap', 96 | '\\in', 97 | '\\notin', 98 | '\\subset', 99 | '\\subseteq', 100 | '\\supset', 101 | '\\supseteq', 102 | '\\varnothing', 103 | '\\cdot', 104 | '\\colon' 105 | ]; 106 | 107 | Constant[1].items = getIconContents( 108 | list, 109 | 'https://store-g1.seewo.com/easiclass-public/ec4941099e30462b935956b413e3ca8d' 110 | ); 111 | })(); 112 | // 几何 113 | (function () { 114 | const list = [ 115 | '\\sin\\placeholder', 116 | '\\cos\\placeholder', 117 | '\\tan\\placeholder', 118 | '\\sec\\placeholder', 119 | '\\csc\\placeholder', 120 | '\\cot\\placeholder', 121 | '\\arcsin\\placeholder', 122 | '\\arccos\\placeholder', 123 | '\\arctan\\placeholder', 124 | '\\triangle', 125 | '\\sim', 126 | '\\cong', 127 | '\\angle', 128 | '\\bot', 129 | '\\alpha', 130 | '\\beta', 131 | '\\gamma', 132 | '\\theta', 133 | '\\degree', 134 | '\\bigcirc', 135 | ]; 136 | 137 | Constant[2].items = getIconContents( 138 | list, 139 | 'https://store-g1.seewo.com/easiclass-public/ec4941099e30462b935956b413e3ca8d' 140 | ); 141 | })(); 142 | // 字母 143 | (function () { 144 | const list = [ 145 | 'a', 146 | 'b', 147 | 'c', 148 | 'd', 149 | 'e', 150 | 'f', 151 | 'g', 152 | 'h', 153 | 'i', 154 | 'j', 155 | 'k', 156 | 'l', 157 | 'm', 158 | 'n', 159 | 'o', 160 | 'p', 161 | 'q', 162 | 'r', 163 | 's', 164 | 't', 165 | 'u', 166 | 'v', 167 | 'w', 168 | 'x', 169 | 'y', 170 | 'z', 171 | ]; 172 | 173 | Constant[3].items = getIconContents( 174 | list, 175 | 'https://store-g1.seewo.com/easiclass-public/ec4941099e30462b935956b413e3ca8d' 176 | ); 177 | })(); 178 | // 其他 179 | (function () { 180 | const list = [ 181 | '\\Omega', 182 | '\\because', 183 | '\\therefore', 184 | '\\Longrightarrow', 185 | '\\Leftrightarrow', 186 | '\\uparrow', 187 | '\\downarrow', 188 | '\\lambda', 189 | '\\kappa', 190 | '\\mu', 191 | '\\rho', 192 | '\\sigma', 193 | '\\tau', 194 | '\\upsilon', 195 | '\\varphi', 196 | '\\Psi', 197 | '\\omega', 198 | '\\varepsilon', 199 | '\\zeta', 200 | '\\eta', 201 | '\\nu', 202 | '\\xi', 203 | '\\chi', 204 | ]; 205 | 206 | Constant[4].items = getIconContents( 207 | list, 208 | 'https://store-g1.seewo.com/easiclass-public/ec4941099e30462b935956b413e3ca8d' 209 | ); 210 | })(); 211 | 212 | function getIconContents(keySet, imgSrc) { 213 | const result = []; 214 | 215 | kity.Utils.each(keySet, function (key) { 216 | const point = CHAR_POSITION[key] || { x: 0, y: 0 }; 217 | const pos = { x: point.x * 172, y: point.y * 146 + 26 }; 218 | result.push({ 219 | key: key, 220 | img: imgSrc, 221 | pos, 222 | }); 223 | }); 224 | 225 | return result; 226 | } 227 | 228 | return Constant; 229 | }); 230 | -------------------------------------------------------------------------------- /src/syntax/delete.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 删除控制 3 | */ 4 | 5 | define( function ( require, exports, module ) { 6 | 7 | var kity = require( "kity" ); 8 | 9 | return kity.createClass( "DeleteComponent", { 10 | 11 | constructor: function ( parentComponent, kfEditor ) { 12 | 13 | this.parentComponent = parentComponent; 14 | this.kfEditor = kfEditor; 15 | 16 | }, 17 | 18 | deleteGroup: function () { 19 | 20 | var cursorInfo = this.parentComponent.getCursorRecord(), 21 | objTree = this.parentComponent.getObjectTree(), 22 | // 当前的树信息 23 | currentTree = objTree.mapping[ cursorInfo.groupId ].strGroup; 24 | 25 | // 选区长度为0, 则删除前一个组 26 | if ( cursorInfo.startOffset === cursorInfo.endOffset ) { 27 | 28 | // 已经到最前, 需要进一步处理 29 | if ( cursorInfo.startOffset === 0 ) { 30 | 31 | // 根节点时, 直接退出, 不做任何处理 32 | if ( this.parentComponent.isRootTree( currentTree ) ) { 33 | return false; 34 | } 35 | 36 | // 不是根节点时, 选中当前容器的父容器 37 | cursorInfo = this.selectParentContainer( cursorInfo.groupId ); 38 | this.parentComponent.updateCursor( cursorInfo ); 39 | 40 | return false; 41 | 42 | } else { 43 | 44 | // 还有更多剩余内容, 则直接删除前一个组 45 | if ( currentTree.operand.length > 1 ) { 46 | 47 | cursorInfo = this.deletePrevGroup( currentTree, cursorInfo ); 48 | 49 | // 仅有一个需要删除的组存在时的处理 50 | } else { 51 | 52 | // 更新光标位置 53 | cursorInfo.startOffset = 0; 54 | cursorInfo.endOffset = 1; 55 | 56 | // 处理组类型, 选中该组即可 57 | if ( currentTree.operand[ 0 ].attr && this.parentComponent.isGroupNode( currentTree.operand[ 0 ].attr.id ) ) { 58 | 59 | this.parentComponent.updateCursor( cursorInfo ); 60 | 61 | return false; 62 | 63 | // 普通元素处理 64 | } else { 65 | 66 | // 替换成占位符 67 | currentTree.operand[ 0 ] = { 68 | name: "placeholder", 69 | operand: [] 70 | }; 71 | this.parentComponent.updateCursor( cursorInfo ); 72 | 73 | return true; 74 | 75 | } 76 | 77 | } 78 | 79 | } 80 | 81 | // 当前是选区 82 | } else { 83 | 84 | // 当前选中占位符的情况 85 | if ( this.parentComponent.isSelectPlaceholder() ) { 86 | 87 | // 如果是根节点, 则不允许删除 88 | if ( this.parentComponent.isRootTree( currentTree ) ) { 89 | 90 | return false; 91 | 92 | // 否则,更新选区到选中该容器 93 | } else { 94 | 95 | cursorInfo = this.selectParentContainer( cursorInfo.groupId ); 96 | this.parentComponent.updateCursor( cursorInfo ); 97 | 98 | return false; 99 | 100 | } 101 | 102 | // 其他选区正常删除 103 | } else { 104 | 105 | return this.deleteSelection( currentTree, cursorInfo ); 106 | 107 | } 108 | 109 | } 110 | 111 | this.parentComponent.updateCursor( cursorInfo ); 112 | 113 | // 选区长度为0, 则可以判定当前公式发生了改变 114 | if ( cursorInfo.startOffset === cursorInfo.endOffset ) { 115 | return true; 116 | } 117 | 118 | return false; 119 | 120 | }, 121 | 122 | // 删除前一个节点, 返回更新后的光标信息 123 | deletePrevGroup: function ( tree, cursorInfo ) { 124 | 125 | // 待删除的组 126 | var index = cursorInfo.startOffset - 1, 127 | group = tree.operand[ index ]; 128 | 129 | // 叶子节点可以直接删除 130 | if ( this.parentComponent.isLeafTree( group ) ) { 131 | 132 | tree.operand.splice( index, 1 ); 133 | cursorInfo.startOffset -= 1; 134 | cursorInfo.endOffset -= 1; 135 | 136 | // 否则, 选中该节点 137 | } else { 138 | 139 | cursorInfo.startOffset -= 1; 140 | 141 | } 142 | 143 | return cursorInfo; 144 | 145 | }, 146 | 147 | // 删除选区内容 148 | deleteSelection: function ( tree, cursorInfo ) { 149 | 150 | // 选中的是容器内的所有内容 151 | if ( cursorInfo.startOffset === 0 && cursorInfo.endOffset === tree.operand.length ) { 152 | 153 | tree.operand.length = 1; 154 | 155 | tree.operand[ 0 ] = { 156 | name: "placeholder", 157 | operand: [] 158 | }; 159 | 160 | cursorInfo.endOffset = 1; 161 | 162 | // 否则可以删除当前选中内容 163 | } else { 164 | tree.operand.splice( cursorInfo.startOffset, cursorInfo.endOffset - cursorInfo.startOffset ); 165 | cursorInfo.endOffset = cursorInfo.startOffset; 166 | } 167 | 168 | this.parentComponent.updateCursor( cursorInfo ); 169 | 170 | return true; 171 | 172 | }, 173 | 174 | // 选中给定ID节点的父容器 175 | selectParentContainer: function ( groupId ) { 176 | 177 | var currentGroupNode = this.parentComponent.getGroupObject( groupId ).node, 178 | parentContainerInfo = this.kfEditor.requestService( "position.get.group", currentGroupNode ), 179 | // 当前组在父容器中的索引 180 | index = this.kfEditor.requestService( "position.get.index", parentContainerInfo.groupObj, currentGroupNode ); 181 | 182 | // 返回新的光标信息 183 | return { 184 | groupId: parentContainerInfo.id, 185 | startOffset: index, 186 | endOffset: index + 1 187 | }; 188 | 189 | } 190 | 191 | } ); 192 | 193 | 194 | } ); -------------------------------------------------------------------------------- /dist/assets/styles/theme/android.css: -------------------------------------------------------------------------------- 1 | .android { 2 | width: 1920px; 3 | height: 835px; 4 | position: fixed; 5 | bottom: 0; 6 | background-color: transparent; 7 | user-select: none; 8 | } 9 | .android * { 10 | box-sizing: border-box; 11 | margin: 0; 12 | padding: 0; 13 | list-style: none; 14 | } 15 | .android .kf-editor-edit-area { 16 | width: 100%; 17 | height: auto; 18 | float: left; 19 | padding: 40px; 20 | position: relative; 21 | } 22 | .android .kf-editor-edit-area .kf-editor-edit-scrollbar { 23 | bottom: 0px; 24 | } 25 | .android .kf-editor-edit-area .kf-editor-header-container { 26 | display: none; 27 | } 28 | .android .kf-editor-edit-area .kf-editor-canvas-wrapper { 29 | width: 1840px; 30 | height: 140px; 31 | background: #f8f8f8; 32 | padding: 0 40px; 33 | float: left; 34 | border-radius: 54px; 35 | } 36 | .android .kf-editor-edit-area .kf-editor-canvas-wrapper .kf-editor-canvas-container { 37 | width: 100%; 38 | height: 100%; 39 | } 40 | .android .kf-editor-edit-keyboard { 41 | width: 1920px; 42 | height: 615px; 43 | float: left; 44 | background: #eceff1; 45 | overflow: hidden; 46 | } 47 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-menu { 48 | width: 274px; 49 | height: 615px; 50 | float: left; 51 | border-right: 1px solid rgba(0, 0, 0, 0.1); 52 | } 53 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-menu .kf-editor-ui-keyboard-menu-list { 54 | width: 100%; 55 | height: 100%; 56 | padding: 26px; 57 | background: #eceff1; 58 | overflow: hidden; 59 | } 60 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-menu .kf-editor-ui-keyboard-menu-list .kf-editor-ui-keyboard-menu-list-item { 61 | width: 183px; 62 | height: 75px; 63 | margin: 19px; 64 | text-align: center; 65 | line-height: 75px; 66 | font-size: 16px; 67 | color: #49494d; 68 | cursor: pointer; 69 | float: left; 70 | font-size: 34px; 71 | } 72 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-menu .kf-editor-ui-keyboard-menu-list .kf-editor-ui-keyboard-menu-list-item-active { 73 | background: #ffab30; 74 | border-radius: 8px; 75 | color: #ffffff; 76 | } 77 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-panel { 78 | width: 1425px; 79 | height: 100%; 80 | float: left; 81 | } 82 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-panel .kf-editor-ui-keyboard-panel-list { 83 | width: 100%; 84 | height: 100%; 85 | padding: 14px 0 0 33px; 86 | overflow: hidden; 87 | position: relative; 88 | transition: top 0.3s linear; 89 | } 90 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-panel .kf-editor-ui-keyboard-panel-list .kf-editor-ui-keyboard-panel-list-item { 91 | width: 148px; 92 | height: 122px; 93 | margin: 12px; 94 | text-align: center; 95 | line-height: 121px; 96 | font-size: 16px; 97 | color: #49494d; 98 | cursor: pointer; 99 | float: left; 100 | position: relative; 101 | } 102 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-panel .kf-editor-ui-keyboard-panel-list .kf-editor-ui-keyboard-panel-list-item:active::before { 103 | content: ''; 104 | display: block; 105 | position: absolute; 106 | width: 100%; 107 | height: 100%; 108 | background-color: rgba(64, 75, 80, 0.15); 109 | } 110 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page { 111 | width: 212px; 112 | height: 100%; 113 | float: left; 114 | } 115 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list { 116 | width: 100%; 117 | height: 100%; 118 | padding: 14px 0px 26px 0; 119 | overflow: hidden; 120 | } 121 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item { 122 | width: 100%; 123 | height: 122px; 124 | margin: 12px; 125 | float: left; 126 | font-size: 46px; 127 | color: #ff9800; 128 | text-align: center; 129 | line-height: 122px; 130 | cursor: pointer; 131 | } 132 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-delete { 133 | width: 100%; 134 | height: 122px; 135 | background: url('https://store-g1.seewo.com/easiclass-public/a7ea5932b621487aaa1c8b9dd4e58cb0') center no-repeat; 136 | background-size: initial; 137 | } 138 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-delete:active { 139 | background-color: rgba(64, 75, 80, 0.15); 140 | } 141 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-prev { 142 | position: relative; 143 | } 144 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-prev::before { 145 | content: ' '; 146 | display: block; 147 | width: 100%; 148 | height: 100%; 149 | background: url('https://store-g1.seewo.com/easiclass-public/27257523137e47499311266d0f872c8b') center no-repeat; 150 | background-size: initial; 151 | } 152 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-prev:not(.kf-editor-ui-keyboard-page-list-item-disabled):active { 153 | background-color: rgba(64, 75, 80, 0.15); 154 | } 155 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-next { 156 | position: relative; 157 | } 158 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-next::before { 159 | content: ' '; 160 | display: block; 161 | width: 100%; 162 | height: 100%; 163 | background: url('https://store-g1.seewo.com/easiclass-public/3c2faacfac52472ea6cb522a34aa04db') center no-repeat; 164 | background-size: initial; 165 | } 166 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-next:not(.kf-editor-ui-keyboard-page-list-item-disabled):active { 167 | background-color: rgba(64, 75, 80, 0.15); 168 | } 169 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-disabled::before { 170 | opacity: 0.5; 171 | } 172 | .android .kf-editor-edit-keyboard .kf-editor-ui-keyboard-footer { 173 | display: none; 174 | } 175 | -------------------------------------------------------------------------------- /src/editor/editor.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 编辑器主体结构 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var kity = require( "kity" ), 8 | Utils = require( "base/utils" ), 9 | bundle = require('bundle'), 10 | Messager = require('editor/Message'), 11 | defaultOpt = { 12 | formula: { 13 | fontsize: 50, 14 | autoresize: false 15 | }, 16 | ui: { 17 | zoom: true, 18 | maxzoom: 2, 19 | minzoom: 1 20 | } 21 | 22 | }; 23 | 24 | // 同步组件列表 25 | var COMPONENTS = {}, 26 | // 异步组件列表 27 | ResourceManager = require( "kf" ).ResourceManager; 28 | 29 | var KFEditor = kity.createClass( 'KFEditor', { 30 | 31 | constructor: function ( container, opt ) { 32 | 33 | this.options = Utils.extend( true, {}, defaultOpt, opt ); 34 | 35 | this.FormulaClass = null; 36 | // 就绪状态 37 | this._readyState = false; 38 | this._callbacks = []; 39 | 40 | this.container = container; 41 | this.services = {}; 42 | this.commands = {}; 43 | 44 | this.initResource(); 45 | this.initWebService(); 46 | }, 47 | 48 | isReady: function () { 49 | 50 | return !!this._readyState; 51 | 52 | }, 53 | 54 | triggerReady: function () { 55 | 56 | var cb = null, 57 | _self = this; 58 | 59 | while ( cb = this._callbacks.shift() ) { 60 | cb.call( _self, _self ); 61 | } 62 | 63 | }, 64 | 65 | ready: function ( cb ) { 66 | 67 | if ( this._readyState ) { 68 | cb.call( this, this ); 69 | } else { 70 | this._callbacks.push( cb ); 71 | } 72 | 73 | }, 74 | 75 | getContainer: function () { 76 | return this.container; 77 | }, 78 | 79 | getDocument: function () { 80 | return this.container.ownerDocument; 81 | }, 82 | 83 | getFormulaClass: function () { 84 | return this.FormulaClass; 85 | }, 86 | 87 | getOptions: function () { 88 | return this.options; 89 | }, 90 | 91 | initResource: function () { 92 | 93 | var _self = this; 94 | 95 | ResourceManager.ready( function ( Formula ) { 96 | 97 | _self.FormulaClass = Formula; 98 | _self.initComponents(); 99 | _self._readyState = true; 100 | _self.triggerReady(); 101 | 102 | }, this.options.resource ); 103 | 104 | }, 105 | 106 | initWebService: function () { 107 | const { WebService, CustomWebService } = bundle; 108 | if (this.options.ui.protocol === 'webview') { 109 | this.eclassWebService = new WebService('webview'); 110 | } else if (this.options.ui.protocol === 'iframe') { 111 | this.eclassWebService = new WebService('iframe'); 112 | } else if (this.options.ui.protocol === 'documentEvent'){ 113 | this.eclassWebService = new CustomWebService({ messager: new Messager() }); 114 | } 115 | this.eclassWebService.on('common.readFormula', (msg) => { 116 | if (msg.body.formula) { 117 | this.execCommand('render', msg.body.formula); 118 | } 119 | this.execCommand('focus', true); 120 | }) 121 | this.eclassWebService.on('common.clearFormula', () => { 122 | this.execCommand('render', '\\placeholder'); 123 | this.execCommand('menu.clearType'); 124 | }) 125 | this.registerCommand('ready', this, function () { 126 | this.eclassWebService.send({ 127 | type: 'common.ready' 128 | }); 129 | }) 130 | }, 131 | 132 | /** 133 | * 初始化同步组件 134 | */ 135 | initComponents: function () { 136 | 137 | var _self = this; 138 | 139 | Utils.each( COMPONENTS, function ( Component, name ) { 140 | 141 | new Component( _self, _self.options[ name ] ); 142 | 143 | } ); 144 | 145 | }, 146 | 147 | requestService: function ( serviceName, args ) { 148 | var serviceObject = getService.call( this, serviceName ); 149 | 150 | return serviceObject.service[ serviceObject.key ].apply( serviceObject.provider, [].slice.call( arguments, 1 ) ); 151 | 152 | }, 153 | 154 | request: function ( serviceName ) { 155 | 156 | var serviceObject = getService.call( this, serviceName ); 157 | 158 | return serviceObject.service; 159 | 160 | }, 161 | 162 | registerService: function ( serviceName, provider, serviceObject ) { 163 | 164 | var key = null; 165 | 166 | for ( key in serviceObject ) { 167 | 168 | if ( serviceObject[ key ] && serviceObject.hasOwnProperty( key ) ) { 169 | serviceObject[ key ] = Utils.proxy( serviceObject[ key ], provider ); 170 | } 171 | 172 | } 173 | 174 | this.services[ serviceName ] = { 175 | provider: provider, 176 | key: key, 177 | service: serviceObject 178 | }; 179 | 180 | }, 181 | 182 | registerCommand: function ( commandName, executor, execFn ) { 183 | 184 | this.commands[ commandName ] = { 185 | executor: executor, 186 | execFn: execFn 187 | }; 188 | 189 | }, 190 | 191 | execCommand: function ( commandName, args ) { 192 | console.log('[execCommand]', commandName); 193 | var commandObject = this.commands[ commandName ]; 194 | 195 | if ( !commandObject ) { 196 | throw new Error( 'KFEditor: not found command, ' + commandName ); 197 | } 198 | 199 | return commandObject.execFn.apply( commandObject.executor, [].slice.call( arguments, 1 ) ); 200 | 201 | } 202 | 203 | } ); 204 | 205 | function getService ( serviceName ) { 206 | 207 | var serviceObject = this.services[ serviceName ]; 208 | 209 | if ( !serviceObject ) { 210 | throw new Error( 'KFEditor: not found service, ' + serviceName ); 211 | } 212 | 213 | return serviceObject; 214 | 215 | } 216 | 217 | Utils.extend( KFEditor, { 218 | 219 | registerComponents: function ( name, component ) { 220 | 221 | COMPONENTS[ name ] = component; 222 | 223 | } 224 | 225 | } ); 226 | 227 | return KFEditor; 228 | 229 | } ); 230 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function (grunt) { 3 | grunt.initConfig({ 4 | // Metadata. 5 | pkg: grunt.file.readJSON('package.json'), 6 | 7 | // npm模块处理 8 | browserify: { 9 | dist: { 10 | files: { 11 | 'dist/npmBundle.js': ['dev-lib/npmEntry.js'], 12 | }, 13 | }, 14 | }, 15 | copy: { 16 | image: { 17 | files: [ 18 | { 19 | expand: true, 20 | src: [ 21 | 'assets/images/**/*.{png,jpg,jpeg,gif,svg}', 22 | 'assets/images/*.{png,jpg,jpeg,gif,svg}', 23 | 'resource/*', 24 | ], 25 | dest: 'dist/', 26 | }, 27 | ], 28 | }, 29 | lib: { 30 | files: [ 31 | { 32 | expand: true, 33 | cwd: 'lib/', 34 | src: ['*.js'], 35 | dest: 'dist/', 36 | }, 37 | ], 38 | }, 39 | }, 40 | 41 | babel: { 42 | dev: { 43 | options: { 44 | sourceMap: true, 45 | }, 46 | files: [ 47 | { 48 | expand: true, 49 | src: ['src/**/*.js'], //所有js文件 50 | dest: '.tmp_build/', //输出到此临时目录下 51 | }, 52 | ], 53 | }, 54 | prod: { 55 | options: { 56 | // sourceMap: true, 57 | }, 58 | files: [ 59 | { 60 | expand: true, 61 | src: ['src/**/*.js'], //所有js文件 62 | dest: '.tmp_build/', //输出到此临时目录下 63 | }, 64 | ], 65 | }, 66 | }, 67 | 68 | less: { 69 | compile: { 70 | files: [ 71 | { 72 | expand: true, 73 | src: ['assets/styles/**/*.less'], 74 | dest: 'dist/', 75 | ext: '.css', 76 | }, 77 | ], 78 | }, 79 | }, 80 | cssmin: { 81 | options: { 82 | stripBanners: true, //合并时允许输出头部信息 83 | banner: 84 | '/*!<%= pkg.file %> - <%= pkg.version %>-' + 85 | '<%=grunt.template.today("yyyy-mm-dd") %> */\n', 86 | }, 87 | build: { 88 | src: 'dist/assets/styles/**/*.css', //压缩 89 | dest: 'dist/index.min.css', //dest 是目的地输出 90 | }, 91 | }, 92 | 93 | // 最终代码合并 94 | concat: { 95 | full: { 96 | options: { 97 | banner: 98 | '/*!\n' + 99 | ' * ====================================================\n' + 100 | ' * <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 101 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 102 | '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' + 103 | ' * GitHub: <%= pkg.repository.url %> \n' + 104 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + 105 | ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n' + 106 | ' * ====================================================\n' + 107 | ' */\n\n' + 108 | '(function () {\n', 109 | 110 | footer: '})();', 111 | }, 112 | 113 | dest: 'dist/' + getFileName(), 114 | src: ['.tmp_build/kf.tmp.js', 'dev-lib/exports.js'], 115 | }, 116 | }, 117 | 118 | // 压缩 119 | uglify: { 120 | options: { 121 | banner: 122 | '/*!\n' + 123 | ' * ====================================================\n' + 124 | ' * <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 125 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 126 | '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' + 127 | ' * GitHub: <%= pkg.repository.url %> \n' + 128 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + 129 | ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n' + 130 | ' * ====================================================\n' + 131 | ' */\n', 132 | 133 | beautify: { 134 | ascii_only: true, 135 | }, 136 | }, 137 | 138 | minimize: { 139 | dest: 'dist/' + getFileName(true), 140 | src: 'dist/' + getFileName(), 141 | }, 142 | lib: { 143 | expand: true, 144 | cwd: 'dev-lib/', 145 | src: ['kity-formula-render.all.js', 'kity-formula-parser.all.js'], 146 | dest: 'lib/', 147 | ext: '.all.min.js', 148 | }, 149 | }, 150 | 151 | // 模块依赖合并 152 | dependence: { 153 | replace: { 154 | options: { 155 | base: '.tmp_build/src', 156 | entrance: 'kf.start', 157 | }, 158 | 159 | files: [ 160 | { 161 | src: ['.tmp_build/**/*.js', 'dev-lib/start.js'], 162 | dest: '.tmp_build/kf.tmp.js', 163 | }, 164 | ], 165 | }, 166 | }, 167 | 168 | // hint检查 169 | jshint: { 170 | options: { 171 | ignores: [ 172 | '.tmp_build/src/base/*.js', 173 | '.tmp_build/src/parse/*.js', 174 | '.tmp_build/src/ui/ui-impl/**/*.js', 175 | ], 176 | jshintrc: '.jshintrc', 177 | }, 178 | check: ['.tmp_build/**/*.js'], 179 | }, 180 | 181 | // 临时目录清理 182 | clean: { 183 | temp: { 184 | src: ['.tmp_build'], 185 | }, 186 | dist: { 187 | src: ['dist'], 188 | }, 189 | }, 190 | }); 191 | 192 | function getFileName(isMin) { 193 | return isMin ? 'kityformula-editor.all.min.js' : 'kityformula-editor.all.js'; 194 | } 195 | 196 | // These plugins provide necessary tasks. 197 | grunt.loadNpmTasks('grunt-babel'); 198 | grunt.loadNpmTasks('grunt-contrib-concat'); 199 | grunt.loadNpmTasks('grunt-contrib-uglify'); 200 | grunt.loadNpmTasks('grunt-contrib-clean'); 201 | grunt.loadNpmTasks('grunt-contrib-jshint'); 202 | grunt.loadNpmTasks('grunt-module-dependence'); 203 | grunt.loadNpmTasks('grunt-contrib-less'); 204 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 205 | grunt.loadNpmTasks('grunt-contrib-copy'); 206 | grunt.loadNpmTasks('grunt-browserify'); 207 | 208 | // task list. 209 | grunt.registerTask('default', [ 210 | 'clean:dist', 211 | 'browserify', 212 | 'copy', 213 | 'less', 214 | 'cssmin', 215 | 'babel:dev', 216 | 'jshint', 217 | 'dependence:replace', 218 | 'concat:full', 219 | 'uglify:minimize', 220 | 'clean:temp', 221 | ]); 222 | grunt.registerTask('updateLib', ['uglify:lib']); 223 | grunt.registerTask('build', [ 224 | 'clean:dist', 225 | 'browserify', 226 | 'copy', 227 | 'less', 228 | 'cssmin', 229 | 'babel:prod', 230 | 'jshint', 231 | 'dependence:replace', 232 | 'concat:full', 233 | 'uglify:minimize', 234 | 'clean:temp', 235 | ]); 236 | }; 237 | -------------------------------------------------------------------------------- /assets/theme/default/fui.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * ==================================================== 3 | * Themes file * Flex UI - v1.0.0 - 2014-07-28 4 | * https://github.com/fex-team/fui 5 | * GitHub: https://github.com/fex-team/fui.git 6 | * Copyright (c) 2014 Baidu Kity Group; Licensed MIT 7 | * ==================================================== 8 | */ 9 | 10 | .fui-widget{-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-user-drag:none;color:#000;line-height:1.5;font-size:12px;font-family:ff-tisa-web-pro-1,ff-tisa-web-pro-2,"Lucida Grande","Hiragino Sans GB","Hiragino Sans GB W3","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif;-webkit-font-smoothing:antialiased;outline:0;display:inline-block;vertical-align:top;position:relative;top:0;left:0}.fui-widget.fui-selectable{-webkit-user-select:text;-khtml-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;-webkit-user-drag:text}.fui-widget *{-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-user-drag:none}.fui-widget.fui-disabled{opacity:.3!important}.fui-widget.fui-hide{display:none!important}.fui-widget.fui-mask-animate{-webkit-transition:all .2s}.fui-widget.fui-mask-hint{-webkit-transform:perspective(600px) translateZ(30px)}.fui-container{overflow:hidden;position:relative;top:0;left:0}.fui-container.fui-disabled{opacity:1!important}.fui-button-menu{border:1px solid #fff}.fui-button-menu:HOVER{border-color:#d5e1f2}.fui-button-menu.fui-button-active{border-color:#d5e1f2;background:#d5e1f2}.fui-button-menu.fui-button-active .fui-button{background:#d5e1f2}.fui-button-menu.fui-layout-bottom,.fui-button-menu.fui-layout-top{text-align:center}.fui-button-menu.fui-layout-bottom .fui-open-btn,.fui-button-menu.fui-layout-top .fui-open-btn{display:block}.fui-button{overflow:hidden;cursor:default;font-size:0}.fui-button ._layout .fui-icon,.fui-button ._layout .fui-label{display:block}.fui-button .fui-icon,.fui-button .fui-label{display:inline-block;vertical-align:middle}.fui-button.fui-button-layout-bottom .fui-icon,.fui-button.fui-button-layout-bottom .fui-label,.fui-button.fui-button-layout-top .fui-icon,.fui-button.fui-button-layout-top .fui-label{display:block}.fui-button:HOVER{background-color:#d5e1f2!important;color:#000!important}.fui-button:ACTIVE{background-color:#87a9da!important;color:#000!important}.fui-button.fui-disabled:ACTIVE,.fui-button.fui-disabled:HOVER{background-color:#fff!important;color:#000!important}.fui-colorpicker{background-color:#fff}.fui-colorpicker-container{border:1px solid #d3d3d3}.fui-colorpicker-container .fui-colorpicker-title{background:#eee;padding:2px 4px}.fui-colorpicker-container .fui-colorpicker-colors{margin:0;padding:0;font-size:0;line-height:0}.fui-colorpicker-container .fui-colorpicker-colors-line0{margin-bottom:3px}.fui-colorpicker-container .fui-colorpicker-item{display:inline-block;margin:0 2px;width:13px;height:13px;border-style:solid;border-width:1px}.fui-colorpicker-container .fui-colorpicker-commoncolor,.fui-colorpicker-container .fui-colorpicker-standardcolor{margin:4px 3px;white-space:nowrap}.fui-colorpicker-container .fui-colorpicker-toolbar{margin:4px;height:27px}.fui-colorpicker-container .fui-colorpicker-toolbar .fui-colorpicker-preview{display:inline-block;height:25px;line-height:25px;width:120px;border:1px solid #d3d3d3}.fui-colorpicker-container .fui-colorpicker-toolbar .fui-colorpicker-clear{display:inline-block;height:25px;line-height:25px;width:60px;border:1px solid #d3d3d3;font-size:12px;text-align:center;position:absolute;right:5px;cursor:pointer}.fui-dialog{position:fixed;top:-1000000px;left:-100000px;border:1px solid #B1B1B1;background:#fff}.fui-dialog .fui-panel-content{width:auto!important;height:auto!important;padding:2px}.fui-dialog .fui-dialog-caption{margin:0;padding:5px;font-size:16px;font-weight:400;line-height:1;display:inline-block}.fui-dialog .fui-dialog-head .fui-close-button{float:right}.fui-dialog .fui-dialog-head .fui-close-button .fui-close-button-icon{width:16px;height:16px;background:url(images/close.png) no-repeat}.fui-drop-panel{border:1px solid #d3d3d3;overflow:hidden;position:relative}.fui-drop-panel .fui-drop-panel-content{display:inline-block}.fui-drop-panel .fui-drop-panel-placeholder{display:none}.fui-drop-panel .fui-drop-panel-button{border-left:1px solid #d3d3d3;visibility:visible}.fui-drop-panel .fui-drop-panel-button:HOVER{border-color:#d5e1f2}.fui-drop-panel .fui-drop-panel-button:ACTIVE{border-color:#87a9da}.fui-drop-panel:HOVER{border-color:#d5e1f2}.fui-drop-panel:HOVER .fui-drop-panel-button{border-left-color:#d5e1f2}.fui-drop-panel:ACTIVE{border-color:#87a9da}.fui-drop-panel:ACTIVE .fui-drop-panel-button{border-left-color:#d5e1f2}.fui-drop-panel.fui-drop-panel-open{overflow:visible}.fui-drop-panel.fui-drop-panel-open .fui-drop-panel-content{border:1px solid #d3d3d3;position:absolute;top:-1px;left:-1px}.fui-drop-panel.fui-drop-panel-open .fui-drop-panel-button{visibility:hidden}.fui-drop-panel.fui-drop-panel-open .fui-drop-panel-placeholder{display:inline-block}.fui-drop-panel-popup{border:1px solid #d3d3d3}.fui-drop-panel-popup:HOVER{border-color:#d5e1f2}.fui-drop-panel-popup:HOVER .fui-drop-panel-button{border-left-color:#d5e1f2}.fui-drop-panel-popup:ACTIVE{border-color:#87a9da}.fui-drop-panel-popup:ACTIVE .fui-drop-panel-button{border-left-color:#d5e1f2}.fui-icon{text-align:center;font-size:0}.fui-icon img{display:inline-block}.fui-input-button{border:1px solid #ababab}.fui-input-button .fui-input{vertical-align:middle;border:none!important}.fui-input-button .fui-button{vertical-align:middle}.fui-input-button:ACTIVE,.fui-input-button:HOVER{border-color:#87a9da}.fui-input{border:1px solid #d3d3d3;padding:1px;margin:0}.fui-input:FOCUS,.fui-input:HOVER{border-color:#4d90fe!important}.fui-item{font-size:0}.fui-item .fui-icon,.fui-item .fui-label{vertical-align:middle}.fui-item.fui-item-selected{background:#87a9da}.fui-label-panel .fui-label-panel-label{width:100%;color:#666}.fui-label-panel.fui-no-position .fui-label-panel-label{position:static!important}.fui-label-panel.fui-layout-bottom .fui-label-panel-label{position:absolute;bottom:0;left:0;top:auto;z-index:2}.fui-label{cursor:default;display:inline-block;white-space:nowrap}.fui-mask{position:fixed;z-index:99998}.fui-menu{background-color:#fff;border:1px solid #d3d3d3}.fui-menu .fui-item{padding:2px 5px;display:block!important}.fui-menu .fui-item:HOVER{background:#d5e1f2}.fui-panel{display:inline-block;vertical-align:top;overflow-y:auto;overflow-x:hidden}.fui-panel .fui-panel-content{position:relative;top:0;left:0;width:100%;height:100%}.fui-panel.fui-container-column{font-size:0}.fui-panel.fui-container-column .fui-column{display:block}.fui-ppanel::-webkit-scrollbar{width:15px}.fui-ppanel::-webkit-scrollbar-button:end:decrement,.fui-ppanel::-webkit-scrollbar-button:end:increment,.fui-ppanel::-webkit-scrollbar-button:start:decrement,.fui-ppanel::-webkit-scrollbar-button:start:increment,.fui-ppanel::-webkit-scrollbar-thumb{border:1px solid #e7e7e7}.fui-ppanel.fui-ppanel-position{position:fixed;z-index:99999}.fui-separator{background:#6d6d6d}.fui-spin-button .fui-spin-down-btn .fui-icon,.fui-spin-button .fui-spin-up-btn .fui-icon{width:16px;height:9px;background:url(images/up.png) 3px 1.5px no-repeat}.fui-spin-button .fui-spin-down-btn .fui-icon{background-image:url(images/down.png)}.fui-tabs .fui-selected{background-color:#d5e1f2}.fui-toggle-button.fui-button-pressed{background-color:#aec5e6}.fui-toggle-button.fui-button-pressed.fui-disabled{background-color:#aec5e6!important} -------------------------------------------------------------------------------- /src/parse/parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 数学公式解析器 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var KFParser = require( "kf" ).Parser, 8 | kity = require( "kity" ), 9 | CURSOR_CHAR = require( "sysconf" ).cursorCharacter, 10 | VGROUP_LIST = require( "parse/vgroup-def" ), 11 | ROOT_P_TEXT = require( "sysconf" ).rootPlaceholder.content, 12 | COMBINATION_NAME = "combination", 13 | PID_PREFIX = "_kf_editor_", 14 | GROUP_TYPE = require( "def/group-type" ), 15 | PID = 0; 16 | 17 | var Parser = kity.createClass( "Parser", { 18 | 19 | constructor: function ( kfEditor ) { 20 | 21 | this.kfEditor = kfEditor; 22 | 23 | this.callBase(); 24 | // kityformula 解析器 25 | this.kfParser = KFParser.use( "latex" ); 26 | 27 | this.initKFormulExtension(); 28 | 29 | this.pid = generateId(); 30 | this.groupRecord = 0; 31 | 32 | this.tree = null; 33 | 34 | this.isResetId = true; 35 | 36 | this.initServices(); 37 | 38 | }, 39 | 40 | parse: function ( str, isResetId ) { 41 | 42 | var parsedResult = null; 43 | 44 | this.isResetId = !!isResetId; 45 | 46 | if ( this.isResetId ) { 47 | this.resetGroupId(); 48 | } 49 | 50 | parsedResult = this.kfParser.parse( str ); 51 | 52 | // 对解析出来的结果树做适当的处理,使得编辑器能够更容易地识别当前表达式的语义 53 | supplementTree( this, parsedResult.tree ); 54 | 55 | return parsedResult; 56 | 57 | }, 58 | 59 | // 序列化, parse的逆过程 60 | serialization: function ( tree ) { 61 | 62 | return this.kfParser.serialization( tree ); 63 | 64 | }, 65 | 66 | initServices: function () { 67 | 68 | this.kfEditor.registerService( "parser.parse", this, { 69 | parse: this.parse 70 | } ); 71 | 72 | this.kfEditor.registerService( "parser.latex.serialization", this, { 73 | serialization: this.serialization 74 | } ); 75 | 76 | }, 77 | 78 | getKFParser: function () { 79 | 80 | return this.kfParser; 81 | 82 | }, 83 | 84 | // 初始化KF扩展 85 | initKFormulExtension: function () { 86 | 87 | require( "kf-ext/extension" ).ext( this ); 88 | 89 | }, 90 | 91 | resetGroupId: function () { 92 | this.groupRecord = 0; 93 | }, 94 | 95 | getGroupId: function () { 96 | return this.pid + "_" + ( ++this.groupRecord ); 97 | } 98 | 99 | } ); 100 | 101 | // 把解析树丰富成公式编辑器的语义树, 该语义化的树同时也是合法的解析树 102 | function supplementTree ( parser, tree, parentTree ) { 103 | 104 | var currentOperand = null, 105 | // 只有根节点才没有parentTree 106 | isRoot = !parentTree; 107 | 108 | tree.attr = tree.attr || {}; 109 | 110 | tree.attr.id = parser.getGroupId(); 111 | 112 | if ( isRoot ) { 113 | processRootGroup( parser, tree ); 114 | // 根占位符处理, 附加label 115 | } else if ( parentTree.attr[ "data-root" ] && tree.name === "placeholder" && onlyPlaceholder( parentTree.operand ) ) { 116 | tree.attr.label = ROOT_P_TEXT; 117 | } 118 | 119 | for ( var i = 0, len= tree.operand.length; i < len; i++ ) { 120 | 121 | currentOperand = tree.operand[ i ]; 122 | 123 | if ( isVirtualGroup( tree ) ) { 124 | // 虚拟组处理 125 | processVirtualGroup( parser, i, tree, currentOperand ); 126 | } else { 127 | processGroup( parser, i, tree, currentOperand ); 128 | } 129 | 130 | } 131 | 132 | return tree; 133 | 134 | } 135 | 136 | function generateId () { 137 | return PID_PREFIX + ( ++PID ); 138 | } 139 | 140 | function processRootGroup ( parser, tree ) { 141 | 142 | // 如果isResetId为false, 表示当前生成的是子树 143 | // 则不做data-root标记, 同时更改该包裹的类型为GROUP_TYPE.VIRTUAL 144 | if ( !parser.isResetId ) { 145 | tree.attr[ "data-type" ] = GROUP_TYPE.VIRTUAL; 146 | } else { 147 | tree.attr[ "data-root" ] = "true"; 148 | } 149 | 150 | } 151 | 152 | /** 153 | * 虚拟组处理 154 | * @param parser 解析器实例 155 | * @param index 当前处理的子树所在其父节点的索引位置 156 | * @param tree 需要处理的树父树 157 | * @param subtree 当前需要处理的树 158 | */ 159 | function processVirtualGroup ( parser, index, tree, subtree ) { 160 | 161 | // 括号组的前两个元素不用处理 162 | if ( tree.name === "brackets" && index < 2 ) { 163 | return; 164 | // 函数的第一个参数不处理 165 | } else if ( tree.name === "function" && index === 0 ) { 166 | return; 167 | } 168 | 169 | tree.attr[ "data-type" ] = GROUP_TYPE.VIRTUAL; 170 | 171 | if ( !subtree ) { 172 | 173 | tree.operand[ index ] = subtree; 174 | 175 | } else if ( typeof subtree === "string" ) { 176 | 177 | tree.operand[ index ] = createGroup( parser ); 178 | 179 | tree.operand[ index ].operand[ 0 ] = subtree; 180 | 181 | } else if ( isPlaceholder( subtree ) ) { 182 | 183 | tree.operand[ index ] = createGroup( parser ); 184 | 185 | tree.operand[ index ].operand[ 0 ] = supplementTree( parser, subtree, tree.operand[ index ] ); 186 | 187 | } else { 188 | 189 | tree.operand[ index ] = supplementTree( parser, subtree, tree ); 190 | 191 | } 192 | 193 | } 194 | 195 | function processGroup ( parser, index, tree, subtree ) { 196 | 197 | tree.attr[ "data-type" ] = GROUP_TYPE.GROUP; 198 | 199 | if ( !subtree || typeof subtree === "string" ) { 200 | 201 | tree.operand[ index ] = subtree; 202 | 203 | // 特殊文本处理, 比如mathcal、mathrm等 204 | } else if ( subtree.name === "text" ) { 205 | 206 | tree.operand[ index ] = subtree; 207 | 208 | } else { 209 | 210 | tree.operand[ index ] = supplementTree( parser, subtree, tree ); 211 | 212 | } 213 | 214 | } 215 | 216 | /** 217 | * 判断给定的操作数列表内是否仅有一个占位符存在, 该判断仅支持对根内部的表达式做判断 218 | * @param operands 操作数列表 219 | * @returns {boolean} 220 | */ 221 | function onlyPlaceholder ( operands ) { 222 | 223 | var result = 1; 224 | 225 | if ( operands.length > 3 ) { 226 | return false; 227 | } 228 | 229 | for ( var i = 0, len = operands.length; i < len; i++ ) { 230 | 231 | if ( operands[ i ] === CURSOR_CHAR ) { 232 | continue; 233 | } 234 | 235 | if ( operands[ i ] && operands[ i ].name === "placeholder" ) { 236 | result--; 237 | } 238 | 239 | } 240 | 241 | return !result; 242 | 243 | } 244 | 245 | // 判断给定的树是否是一个虚拟组 246 | function isVirtualGroup ( tree ) { 247 | 248 | return !!VGROUP_LIST[ tree.name ]; 249 | 250 | } 251 | 252 | // 判断给定的树是否是一个占位符 253 | function isPlaceholder ( tree ) { 254 | 255 | return tree.name === "placeholder"; 256 | 257 | } 258 | 259 | // 创建一个新组, 组的内容是空 260 | function createGroup ( parser ) { 261 | 262 | return { 263 | name: COMBINATION_NAME, 264 | attr: { 265 | "data-type": GROUP_TYPE.GROUP, 266 | id: parser.getGroupId() 267 | }, 268 | operand: [] 269 | }; 270 | 271 | } 272 | 273 | return Parser; 274 | 275 | } ); 276 | 277 | -------------------------------------------------------------------------------- /src/position/position.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 定位模块 3 | */ 4 | 5 | 6 | define( function ( require ) { 7 | 8 | var kity = require( "kity" ), 9 | 10 | kfUtils = require( "base/utils" ), 11 | 12 | PositionComponenet = kity.createClass( 'PositionComponenet', { 13 | 14 | constructor: function ( kfEditor ) { 15 | 16 | this.kfEditor = kfEditor; 17 | 18 | this.initServices(); 19 | 20 | }, 21 | 22 | initServices: function () { 23 | 24 | this.kfEditor.registerService( "position.get.group", this, { 25 | getGroupByTarget: this.getGroupByTarget 26 | } ); 27 | 28 | this.kfEditor.registerService( "position.get.index", this, { 29 | getIndexByTargetInGroup: this.getIndexByTargetInGroup 30 | } ); 31 | 32 | this.kfEditor.registerService( "position.get.location.info", this, { 33 | getLocationInfo: this.getLocationInfo 34 | } ); 35 | 36 | this.kfEditor.registerService( "position.get.parent.group", this, { 37 | getParentGroupByTarget: this.getParentGroupByTarget 38 | } ); 39 | 40 | this.kfEditor.registerService( "position.get.wrap", this, { 41 | getWrap: this.getWrap 42 | } ); 43 | 44 | this.kfEditor.registerService( "position.get.area", this, { 45 | getAreaByCursorInGroup: this.getAreaByCursorInGroup 46 | } ); 47 | 48 | this.kfEditor.registerService( "position.get.group.info", this, { 49 | getGroupInfoByNode: this.getGroupInfoByNode 50 | } ); 51 | 52 | this.kfEditor.registerService( "position.get.parent.info", this, { 53 | getParentInfoByNode: this.getParentInfoByNode 54 | } ); 55 | 56 | }, 57 | 58 | getGroupByTarget: function ( target ) { 59 | 60 | var groupDom = getGroup( target, false, false ); 61 | 62 | if ( groupDom ) { 63 | return this.kfEditor.requestService( "syntax.get.group.content", groupDom.id ); 64 | } 65 | 66 | return null; 67 | 68 | }, 69 | 70 | /** 71 | * 根据给定的组节点和目标节点, 获取目标节点在组节点内部的索引 72 | * @param groupNode 组节点 73 | * @param targetNode 目标节点 74 | */ 75 | getIndexByTargetInGroup: function ( groupNode, targetNode ) { 76 | 77 | var groupInfo = this.kfEditor.requestService( "syntax.get.group.content", groupNode.id ), 78 | index = -1; 79 | 80 | kity.Utils.each( groupInfo.content, function ( child, i ) { 81 | 82 | index = i; 83 | 84 | if ( kfUtils.contains( child, targetNode ) ) { 85 | return false; 86 | } 87 | 88 | } ); 89 | 90 | return index; 91 | 92 | }, 93 | 94 | /** 95 | * 根据给定的组节点和给定的偏移值,获取当前偏移值在组中的区域值。 96 | * 该区域值的取值为true时, 表示在右区域, 反之则在左区域 97 | * @param groupNode 组节点 98 | * @param offset 偏移值 99 | */ 100 | getAreaByCursorInGroup: function ( groupNode, offset ) { 101 | 102 | var groupRect = kfUtils.getRect( groupNode ); 103 | 104 | return groupRect.left + groupRect.width / 2 < offset; 105 | 106 | }, 107 | 108 | getLocationInfo: function ( distance, groupInfo ) { 109 | 110 | var index = -1, 111 | children = groupInfo.content, 112 | boundingRect = null; 113 | 114 | for ( var i = children.length - 1, child = null; i >= 0; i-- ) { 115 | 116 | index = i; 117 | 118 | child = children[ i ]; 119 | 120 | boundingRect = kfUtils.getRect( child ); 121 | 122 | if ( boundingRect.left < distance ) { 123 | 124 | if ( boundingRect.left + boundingRect.width / 2 < distance ) { 125 | index += 1; 126 | } 127 | 128 | break; 129 | 130 | } 131 | 132 | } 133 | 134 | return index; 135 | 136 | }, 137 | 138 | getParentGroupByTarget: function ( target ) { 139 | 140 | var groupDom = getGroup( target, true, false ); 141 | 142 | if ( groupDom ) { 143 | return this.kfEditor.requestService( "syntax.get.group.content", groupDom.id ); 144 | } 145 | 146 | return null; 147 | 148 | }, 149 | 150 | getWrap: function ( node ) { 151 | 152 | return getGroup( node, true, true ); 153 | 154 | }, 155 | 156 | /** 157 | * 给定一个节点, 获取其节点所属的组及其在该组内的偏移 158 | * @param target 目标节点 159 | */ 160 | getGroupInfoByNode: function ( target ) { 161 | 162 | var result = {}, 163 | containerNode = getGroup( target, false, false ), 164 | containerInfo = null; 165 | 166 | if ( !containerNode ) { 167 | return null; 168 | } 169 | 170 | containerInfo = this.kfEditor.requestService( "syntax.get.group.content", containerNode.id ); 171 | 172 | for ( var i = 0, len = containerInfo.content.length; i < len; i++) { 173 | 174 | result.index = i; 175 | 176 | if ( kfUtils.contains( containerInfo.content[ i ], target ) ) { 177 | break; 178 | } 179 | 180 | } 181 | 182 | result.group = containerInfo; 183 | 184 | return result; 185 | 186 | }, 187 | 188 | /** 189 | * 给定一个节点, 获取其节点所属的直接包含组及其在该直接包含组内的偏移 190 | * @param target 目标节点 191 | */ 192 | getParentInfoByNode: function ( target ) { 193 | 194 | var group = getGroup( target, true, false ); 195 | 196 | group = this.kfEditor.requestService( "syntax.get.group.content", group.id ); 197 | 198 | return { 199 | group: group, 200 | index: group.content.indexOf( target ) 201 | }; 202 | 203 | } 204 | 205 | } ); 206 | 207 | /** 208 | * 获取给定节点元素所属的组 209 | * @param node 当前点击的节点 210 | * @param isAllowVirtual 是否允许选择虚拟组 211 | * @param isAllowWrap 是否允许选择目标节点的最小包裹单位 212 | * @returns {*} 213 | */ 214 | function getGroup ( node, isAllowVirtual, isAllowWrap ) { 215 | 216 | var tagName = null; 217 | 218 | if ( !node.ownerSVGElement ) { 219 | return null; 220 | } 221 | 222 | node = node.parentNode; 223 | 224 | tagName = node.tagName.toLowerCase(); 225 | 226 | if ( node && tagName !== "body" && tagName !== "svg" ) { 227 | 228 | if ( node.getAttribute( "data-type" ) === "kf-editor-group" ) { 229 | return node; 230 | } 231 | 232 | if ( isAllowVirtual && node.getAttribute( "data-type" ) === "kf-editor-virtual-group" ) { 233 | return node; 234 | } 235 | 236 | if ( isAllowWrap && node.getAttribute( "data-flag" ) !== null ) { 237 | return node; 238 | } 239 | 240 | return getGroup( node, isAllowVirtual, isAllowWrap ); 241 | 242 | } else { 243 | return null; 244 | } 245 | 246 | } 247 | 248 | return PositionComponenet; 249 | 250 | } ); -------------------------------------------------------------------------------- /src/control/location.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 光标定位组件 3 | */ 4 | 5 | define( function ( require, exports, module ) { 6 | 7 | var kity = require( "kity" ); 8 | 9 | return kity.createClass( "LocationComponent", { 10 | 11 | constructor: function ( parentComponent, kfEditor ) { 12 | 13 | this.parentComponent = parentComponent; 14 | this.kfEditor = kfEditor; 15 | 16 | // 创建光标 17 | this.paper = this.getPaper(); 18 | this.cursorShape = this.createCursor(); 19 | 20 | this.initServices(); 21 | 22 | this.initEvent(); 23 | 24 | }, 25 | 26 | getPaper: function () { 27 | return this.kfEditor.requestService( "render.get.paper" ); 28 | }, 29 | 30 | initServices: function () { 31 | 32 | // 重定位光标 33 | this.kfEditor.registerService( "control.cursor.relocation", this, { 34 | relocationCursor: this.updateCursor 35 | } ); 36 | 37 | // 清除光标 38 | this.kfEditor.registerService( "control.cursor.hide", this, { 39 | hideCursor: this.hideCursor 40 | } ); 41 | 42 | this.kfEditor.registerService( "control.reselect", this, { 43 | reselect: this.reselect 44 | } ); 45 | 46 | this.kfEditor.registerService( "control.get.cursor.location", this, { 47 | getCursorLocation: this.getCursorLocation 48 | } ); 49 | 50 | }, 51 | 52 | createCursor: function () { 53 | 54 | var cursorShape = new kity.Rect( 1, 0, 0, 0 ).fill( "black" ); 55 | 56 | cursorShape.setAttr( "style", "display: none" ); 57 | 58 | this.paper.addShape( cursorShape ); 59 | 60 | return cursorShape; 61 | 62 | }, 63 | 64 | // 光标定位监听 65 | initEvent: function () { 66 | 67 | var eventServiceObject = this.kfEditor.request( "ui.canvas.container.event" ), 68 | _self = this; 69 | 70 | eventServiceObject.on( "mousedown", function ( e ) { 71 | 72 | e.preventDefault(); 73 | 74 | _self.updateCursorInfo( e ); 75 | _self.kfEditor.requestService( "control.update.input" ); 76 | _self.reselect(); 77 | 78 | } ); 79 | 80 | }, 81 | 82 | updateCursorInfo: function ( evt ) { 83 | 84 | var wrapNode = null, 85 | groupInfo = null, 86 | index = -1; 87 | 88 | // 有根占位符存在, 所有定位到定位到根占位符内部 89 | if ( this.kfEditor.requestService( "syntax.has.root.placeholder" ) ) { 90 | 91 | this.kfEditor.requestService( "syntax.update.record.cursor", { 92 | groupId: this.kfEditor.requestService( "syntax.get.root.group.info" ).id, 93 | startOffset: 0, 94 | endOffset: 1 95 | } ); 96 | 97 | return false; 98 | } 99 | 100 | wrapNode = this.kfEditor.requestService( "position.get.wrap", evt.target ); 101 | 102 | // 占位符处理, 选中该占位符 103 | if ( wrapNode && this.kfEditor.requestService( "syntax.is.placeholder.node", wrapNode.id ) ) { 104 | groupInfo = this.kfEditor.requestService( "position.get.group.info", wrapNode ); 105 | this.kfEditor.requestService( "syntax.update.record.cursor", groupInfo.group.id, groupInfo.index, groupInfo.index + 1 ); 106 | return; 107 | } 108 | 109 | groupInfo = this.kfEditor.requestService( "position.get.group", evt.target ); 110 | 111 | if ( groupInfo === null ) { 112 | groupInfo = this.kfEditor.requestService( "syntax.get.root.group.info" ); 113 | } 114 | 115 | index = this.getIndex( evt.clientX, groupInfo ); 116 | 117 | this.kfEditor.requestService( "syntax.update.record.cursor", groupInfo.id, index ); 118 | 119 | }, 120 | 121 | hideCursor: function () { 122 | this.cursorShape.setAttr( "style", "display: none" ); 123 | }, 124 | 125 | // 根据当前的光标信息, 对选区和光标进行更新 126 | reselect: function () { 127 | 128 | var cursorInfo = this.kfEditor.requestService( "syntax.get.record.cursor" ), 129 | groupInfo = null; 130 | 131 | this.hideCursor(); 132 | 133 | // 根节点单独处理 134 | if ( this.kfEditor.requestService( "syntax.is.select.placeholder" ) ) { 135 | 136 | groupInfo = this.kfEditor.requestService( "syntax.get.group.content", cursorInfo.groupId ); 137 | this.kfEditor.requestService( "render.select.group", groupInfo.content[ cursorInfo.startOffset ].id ); 138 | return; 139 | 140 | } 141 | 142 | if ( cursorInfo.startOffset === cursorInfo.endOffset ) { 143 | // 更新光标位置 144 | this.updateCursor(); 145 | // 请求背景着色 146 | this.kfEditor.requestService( "render.tint.current.cursor" ); 147 | } else { 148 | this.kfEditor.requestService( "render.select.current.cursor" ); 149 | } 150 | 151 | }, 152 | 153 | updateCursor: function () { 154 | 155 | var cursorInfo = this.kfEditor.requestService( "syntax.get.record.cursor" ); 156 | 157 | if ( cursorInfo.startOffset !== cursorInfo.endOffset ) { 158 | this.hideCursor(); 159 | return; 160 | } 161 | 162 | var groupInfo = this.kfEditor.requestService( "syntax.get.group.content", cursorInfo.groupId ), 163 | isBefore = cursorInfo.endOffset === 0, 164 | index = isBefore ? 0 : cursorInfo.endOffset - 1, 165 | focusChild = groupInfo.content[ index ], 166 | paperContainerRect = getRect( this.paper.container.node ), 167 | cursorOffset = 0, 168 | focusChildRect = getRect( focusChild ), 169 | cursorTransform = this.cursorShape.getTransform( this.cursorShape ), 170 | canvasZoom = this.kfEditor.requestService( "render.get.canvas.zoom" ), 171 | formulaZoom = this.paper.getZoom(); 172 | 173 | this.cursorShape.setHeight( focusChildRect.height / canvasZoom / formulaZoom ); 174 | 175 | // 计算光标偏移位置 176 | cursorOffset = isBefore ? ( focusChildRect.left - 2 ) : ( focusChildRect.left + focusChildRect.width - 2 ); 177 | cursorOffset -= paperContainerRect.left; 178 | 179 | // 定位光标 180 | cursorTransform.m.e = Math.floor( cursorOffset / canvasZoom / formulaZoom ) + 0.5 ; 181 | cursorTransform.m.f = ( focusChildRect.top - paperContainerRect.top ) / canvasZoom / formulaZoom; 182 | 183 | this.cursorShape.setMatrix( cursorTransform ); 184 | this.cursorShape.setAttr( "style", "display: block" ); 185 | 186 | }, 187 | 188 | getCursorLocation: function () { 189 | 190 | var rect = this.cursorShape.getRenderBox( "paper" ); 191 | 192 | return { 193 | x: rect.x, 194 | y: rect.y 195 | }; 196 | 197 | }, 198 | 199 | getIndex: function ( distance, groupInfo ) { 200 | 201 | var index = -1, 202 | children = groupInfo.content, 203 | boundingRect = null; 204 | 205 | for ( var i = children.length - 1, child = null; i >= 0; i-- ) { 206 | 207 | index = i; 208 | 209 | child = children[ i ]; 210 | 211 | boundingRect = getRect( child ); 212 | 213 | if ( boundingRect.left < distance ) { 214 | 215 | if ( boundingRect.left + boundingRect.width / 2 < distance ) { 216 | index += 1; 217 | } 218 | 219 | break; 220 | 221 | } 222 | 223 | } 224 | 225 | return index; 226 | 227 | } 228 | 229 | } ); 230 | 231 | function getRect ( node ) { 232 | return node.getBoundingClientRect(); 233 | } 234 | 235 | } ); -------------------------------------------------------------------------------- /src/ui/ui-impl/keyboard/keyboard.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 特殊字符区域 3 | */ 4 | 5 | define(function (require) { 6 | var kity = require('kity'), 7 | PREFIX = 'kf-editor-ui-', 8 | // UiUitls 9 | $$ = require('ui/ui-impl/ui-utils'), 10 | Menu = require('ui/ui-impl/keyboard/menu/index'), 11 | Panel = require('ui/ui-impl/keyboard/panel/index'), 12 | Page = require('ui/ui-impl/keyboard/page/index'), 13 | Constant = require('ui/ui-impl/keyboard/const'), 14 | PcPanelConstant = require('ui/ui-impl/keyboard/panel/pcConst'), 15 | AndroidPanelConstant = require('ui/ui-impl/keyboard/panel/androidConst'), 16 | Footer = require('ui/ui-impl/keyboard/footer/index'), 17 | Keyboard = kity.createClass('Keyboard', { 18 | constructor: function (doc, kfEditor) { 19 | this.doc = doc; 20 | this.kfEditor = kfEditor; 21 | this.pageSize = this.getDeviceType() === 'android' ? 32 : 40; 22 | this.panelConstant = this.getConstant(); 23 | this.typeEnum = { 24 | [Constant.Type.Common]: 0, 25 | [Constant.Type.Algebra]: 1, 26 | [Constant.Type.Geometry]: 2, 27 | [Constant.Type.Letter]: 3, 28 | [Constant.Type.Other]: 4, 29 | 0: Constant.Type.Common, 30 | 1: Constant.Type.Algebra, 31 | 2: Constant.Type.Geometry, 32 | 3: Constant.Type.Letter, 33 | 4: Constant.Type.Other, 34 | }; 35 | 36 | this.state = { 37 | type: Constant.Type.Common, 38 | page: 0, 39 | totalPage: this.getTotalPage(this.panelConstant[0].items.length), 40 | }; 41 | 42 | this.element = this.render(); 43 | 44 | // 完成组件渲染 45 | this.menuChild = new Menu(this.element, { 46 | type: this.state.type, 47 | prefix: PREFIX, 48 | doc: this.doc, 49 | kfEditor: this.kfEditor, 50 | onClick: this.onMenuClick.bind(this), 51 | }); 52 | this.panelChild = new Panel(this.element, { 53 | type: this.state.type, 54 | page: this.state.page, 55 | prefix: PREFIX, 56 | doc: this.doc, 57 | kfEditor: this.kfEditor, 58 | panelConstant: this.panelConstant, 59 | rowHeight: this.getDeviceType() === 'android' ? 146 : 63, 60 | scrollHeight: this.getDeviceType() === 'android' ? 146 * 4 : 63 * 5, 61 | onClick: this.onPanelClick.bind(this), 62 | }); 63 | this.pageChild = new Page(this.element, { 64 | type: this.state.type, 65 | page: this.state.page, 66 | totalPage: this.state.totalPage, 67 | prefix: PREFIX, 68 | doc: this.doc, 69 | kfEditor: this.kfEditor, 70 | onPrevPage: this.onPrevPage.bind(this), 71 | onNextPage: this.onNextPage.bind(this), 72 | onDelete: this.onDelete.bind(this), 73 | onSubmit: this.onSubmit.bind(this), 74 | }); 75 | this.footerChild = new Footer(this.element, { 76 | prefix: PREFIX, 77 | doc: this.doc, 78 | onSubmit: this.onSubmit.bind(this), 79 | onCancel: this.onCancel.bind(this), 80 | }); 81 | this.renderKeyboard(); 82 | // 通知当前类型 83 | this.sendService(); 84 | }, 85 | 86 | sendService: function () { 87 | this.kfEditor.eclassWebService.send({ 88 | type: 'common.setType', 89 | data: { 90 | body: { 91 | type: this.state.type, 92 | }, 93 | }, 94 | }); 95 | }, 96 | 97 | renderKeyboard: function () { 98 | this.menuChild.mount(); 99 | this.panelChild.mount(); 100 | this.pageChild.mount(); 101 | this.footerChild.mount(); 102 | }, 103 | 104 | onMenuClick: function (val) { 105 | const charCollection = this.panelConstant.find((x) => x.type === val) || {}; 106 | const len = charCollection.items ? charCollection.items.length : 0; 107 | this.kfEditor.eclassWebService.send({ 108 | type: 'common.setType', 109 | data: { 110 | body: { 111 | type: val, 112 | }, 113 | }, 114 | }); 115 | this.setState({ 116 | type: val, 117 | page: 0, 118 | totalPage: this.getTotalPage(len), 119 | }); 120 | }, 121 | 122 | onPanelClick: function (val) { 123 | $$.publish('panel.select', val); 124 | }, 125 | 126 | onPrevPage: function () { 127 | const { page, type } = this.state; 128 | // 如果已到第一页,则自动切换至上一个模式,若已到最顶部模式,则禁止翻页 129 | if (page === 0 && type === Constant.Type.Common) { 130 | return; 131 | } 132 | if (page === 0) { 133 | this._prevMode(type); 134 | return; 135 | } 136 | this.setState({ 137 | page: page - 1, 138 | }); 139 | }, 140 | onNextPage: function () { 141 | const { page, type, totalPage } = this.state; 142 | // 如果已到最后一页,则自动切换至下一个模式,若已到最底部模式,则禁止翻页 143 | if (page === totalPage - 1 && type === Constant.Type.Other) { 144 | return; 145 | } 146 | if (page === totalPage - 1) { 147 | this._nextMode(type); 148 | return; 149 | } 150 | this.setState({ 151 | page: page + 1, 152 | }); 153 | }, 154 | 155 | onDelete: function () { 156 | this.kfEditor.requestService('control.delete.string'); 157 | }, 158 | onSubmit: function () { 159 | this.kfEditor.execCommand('get.image.data', (data) => { 160 | const formula = this.kfEditor.execCommand('get.source'); 161 | this.kfEditor.eclassWebService.send({ 162 | type: 'common.setFormula', 163 | data: { 164 | body: { 165 | formulaSrc: data.img, 166 | formula, 167 | }, 168 | }, 169 | }); 170 | }); 171 | }, 172 | onCancel: function () { 173 | this.kfEditor.eclassWebService.send({ 174 | type: 'common.closeModal', 175 | }); 176 | }, 177 | 178 | render: function () { 179 | const keyboardNode = $$.ele(this.doc, 'div', { 180 | className: PREFIX + 'keyboard', 181 | }); 182 | 183 | return keyboardNode; 184 | }, 185 | 186 | setState: function (nextState) { 187 | this.state = { 188 | ...this.state, 189 | ...nextState, 190 | }; 191 | this.menuChild.update(this.state); 192 | this.panelChild.update(this.state); 193 | this.pageChild.update(this.state); 194 | }, 195 | 196 | getTotalPage: function (len) { 197 | return Math.ceil(len / this.pageSize) || 1; 198 | }, 199 | 200 | getDeviceType: function () { 201 | return this.kfEditor.options.ui.device; 202 | }, 203 | 204 | getConstant: function () { 205 | const deviceType = this.getDeviceType(); 206 | switch (deviceType) { 207 | case 'android': 208 | return AndroidPanelConstant; 209 | case 'pc': 210 | return PcPanelConstant; 211 | } 212 | }, 213 | 214 | attachTo: function (container) { 215 | container.appendChild(this.element); 216 | }, 217 | 218 | _prevMode: function (curType) { 219 | const prevType = this.typeEnum[this.typeEnum[curType] - 1]; 220 | const charCollection = this.panelConstant.find((x) => x.type === prevType) || {}; 221 | const len = charCollection.items ? charCollection.items.length : 0; 222 | this.setState({ 223 | type: prevType, 224 | page: 0, 225 | totalPage: this.getTotalPage(len), 226 | }); 227 | return; 228 | }, 229 | _nextMode: function (curType) { 230 | const nextType = this.typeEnum[this.typeEnum[curType] + 1]; 231 | const charCollection = this.panelConstant.find((x) => x.type === nextType) || {}; 232 | const len = charCollection.items ? charCollection.items.length : 0; 233 | this.setState({ 234 | type: nextType, 235 | page: 0, 236 | totalPage: this.getTotalPage(len), 237 | }); 238 | return; 239 | }, 240 | }); 241 | 242 | return Keyboard; 243 | }); 244 | -------------------------------------------------------------------------------- /assets/styles/theme/pc.less: -------------------------------------------------------------------------------- 1 | .pc { 2 | width: 864px; 3 | height: 580px; 4 | position: relative; 5 | top: 0; 6 | left: 0; 7 | * { 8 | box-sizing: border-box; 9 | margin: 0; 10 | padding: 0; 11 | list-style: none; 12 | } 13 | .kf-editor-edit-area { 14 | width: 100%; 15 | height: 168px; 16 | float: left; 17 | .kf-editor-edit-scrollbar { 18 | bottom: 0px; 19 | } 20 | .kf-editor-header-container { 21 | width: 100%; 22 | height: 48px; 23 | background: #eeeeee; 24 | overflow: hidden; 25 | padding-left: 16px; 26 | .kf-editor-header-container-title { 27 | font-size: 16px; 28 | color: #616266; 29 | line-height: 48px; 30 | float: left; 31 | } 32 | .kf-editor-header-container-close { 33 | width: 48px; 34 | height: 48px; 35 | float: right; 36 | cursor: pointer; 37 | position: relative; 38 | &::before { 39 | content: ''; 40 | display: block; 41 | background: url('https://store-g1.seewo.com/easiclass-public/8747fc92a01246e19d7f9bc9f3868652') 42 | center no-repeat; 43 | background-size: 100%; 44 | position: absolute; 45 | width: 16px; 46 | height: 16px; 47 | top: 16px; 48 | right: 16px; 49 | } 50 | &:hover { 51 | &::before { 52 | background: url('https://store-g1.seewo.com/easiclass-public/c2682d9dcc844986898be60b26ee5823'); 53 | } 54 | } 55 | &:active { 56 | &::before { 57 | background: url('https://store-g1.seewo.com/easiclass-public/9550aaa080574f739425122e7908e012'); 58 | } 59 | } 60 | } 61 | } 62 | .kf-editor-canvas-wrapper { 63 | width: 100%; 64 | height: calc(100% - 48px); 65 | background: #fff; 66 | float: left; 67 | padding: 0 40px; 68 | 69 | .kf-editor-canvas-container { 70 | width: 100%; 71 | height: 100%; 72 | } 73 | } 74 | } 75 | 76 | .kf-editor-edit-keyboard { 77 | width: 100%; 78 | height: 411px; 79 | border-top: 1px solid #ddd; 80 | box-sizing: border-box; 81 | background-color: white; 82 | * { 83 | list-style: none; 84 | padding: 0; 85 | margin: 0; 86 | box-sizing: border-box; 87 | } 88 | } 89 | 90 | .kf-editor-ui-keyboard { 91 | width: 100%; 92 | height: 100%; 93 | overflow: hidden; 94 | background: #fff; 95 | border-top: 1px solid #e4e4e4; 96 | .kf-editor-ui-keyboard-menu { 97 | width: 118px; 98 | height: 347px; 99 | border-right: 1px solid #e4e4e4; 100 | float: left; 101 | .kf-editor-ui-keyboard-menu-list { 102 | width: 100%; 103 | height: 100%; 104 | padding: 11px; 105 | background: #f4f4f4; 106 | overflow: hidden; 107 | .kf-editor-ui-keyboard-menu-list-item { 108 | width: 72px; 109 | height: 40px; 110 | margin: 12px; 111 | text-align: center; 112 | line-height: 40px; 113 | font-size: 16px; 114 | color: #49494d; 115 | cursor: pointer; 116 | float: left; 117 | } 118 | .kf-editor-ui-keyboard-menu-list-item-active { 119 | background: #198cff; 120 | border-radius: 4px; 121 | color: #ffffff; 122 | } 123 | } 124 | } 125 | .kf-editor-ui-keyboard-panel { 126 | width: 664px; 127 | height: 347px; 128 | float: left; 129 | overflow: hidden; 130 | .kf-editor-ui-keyboard-panel-list { 131 | width: 100%; 132 | position: relative; 133 | top: 0; 134 | transition: 0.3s top linear; 135 | .kf-editor-ui-keyboard-panel-list-item { 136 | width: 83px; 137 | height: 64px; 138 | cursor: pointer; 139 | float: left; 140 | border: solid #e4e4e4; 141 | border-width: 1px 1px 1px 0px; 142 | margin-top: -1px; 143 | position: relative; 144 | &:hover { 145 | &::before { 146 | content: ''; 147 | display: block; 148 | position: absolute; 149 | width: 100%; 150 | height: 100%; 151 | background-color: rgba(73, 73, 77, 0.1); 152 | } 153 | } 154 | } 155 | } 156 | } 157 | .kf-editor-ui-keyboard-page { 158 | width: 81px; 159 | height: 347px; 160 | float: left; 161 | margin-left: -1px; 162 | border-left: 1px solid #e4e4e4; 163 | .kf-editor-ui-keyboard-page-list { 164 | .kf-editor-ui-keyboard-page-list-item { 165 | width: 81px; 166 | height: 128px; 167 | cursor: pointer; 168 | } 169 | .kf-editor-ui-keyboard-page-list-item-delete { 170 | width: 81px; 171 | height: 64px; 172 | background: url('https://store-g1.seewo.com/easiclass-public/78c93d3c1204445982c6b5700e1442eb') 173 | center no-repeat; 174 | background-size: initial; 175 | border-bottom: 1px solid #e4e4e4; 176 | &:hover { 177 | background-color: rgba(73, 73, 77, 0.1); 178 | } 179 | } 180 | .kf-editor-ui-keyboard-page-list-item-prev { 181 | border-bottom: 1px solid #e4e4e4; 182 | position: relative; 183 | &::before { 184 | content: ' '; 185 | display: block; 186 | width: 100%; 187 | height: 100%; 188 | background: url('https://store-g1.seewo.com/easiclass-public/4993661fa54249249b152a89cb1cec6b') 189 | center no-repeat; 190 | background-size: initial; 191 | } 192 | &:not(.kf-editor-ui-keyboard-page-list-item-disabled):hover { 193 | background-color: rgba(73, 73, 77, 0.1); 194 | } 195 | } 196 | .kf-editor-ui-keyboard-page-list-item-next { 197 | position: relative; 198 | &:not(.kf-editor-ui-keyboard-page-list-item-disabled):hover { 199 | background-color: rgba(73, 73, 77, 0.1); 200 | } 201 | &::before { 202 | content: ' '; 203 | display: block; 204 | width: 100%; 205 | height: 100%; 206 | background: url('https://store-g1.seewo.com/easiclass-public/f4df574b99d748f0823ccd36362d1a9c') 207 | center no-repeat; 208 | background-size: initial; 209 | } 210 | } 211 | .kf-editor-ui-keyboard-page-list-item-disabled::before { 212 | opacity: 0.5; 213 | } 214 | .kf-editor-ui-keyboard-page-list-item-ok { 215 | display: none; 216 | } 217 | } 218 | } 219 | .kf-editor-ui-keyboard-footer { 220 | width: 100%; 221 | height: 64px; 222 | padding: 14px 0; 223 | float: left; 224 | border-top: 1px solid #e4e4e4; 225 | #kf-editor-ui-keyboard-footer-button-cancel { 226 | width: 120px; 227 | height: 36px; 228 | background: #ffffff; 229 | border: 1px solid #dddddd; 230 | border-radius: 18px; 231 | font-size: 16px; 232 | color: #616266; 233 | text-align: center; 234 | line-height: 34px; 235 | float: left; 236 | margin-left: calc(50% - 128px); 237 | cursor: pointer; 238 | user-select: none; 239 | &:not(:disabled) { 240 | cursor: pointer; 241 | } 242 | &:not(:disabled):hover { 243 | background-color: #ebf5ff; 244 | } 245 | &:not(:disabled):active { 246 | background-color: #dce8f5; 247 | } 248 | } 249 | #kf-editor-ui-keyboard-footer-button-submit { 250 | width: 120px; 251 | height: 36px; 252 | background: #198cff; 253 | border-radius: 18px; 254 | font-size: 16px; 255 | color: #ffffff; 256 | text-align: center; 257 | line-height: 36px; 258 | float: left; 259 | margin-left: 16px; 260 | cursor: pointer; 261 | &:not(:disabled) { 262 | cursor: pointer; 263 | } 264 | &:not(:disabled):hover { 265 | background-color: #3399ff; 266 | } 267 | &:not(:disabled):active { 268 | background-color: #1885f2; 269 | } 270 | } 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/ui/ui-impl/button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hn on 14-3-31. 3 | */ 4 | 5 | define( function ( require ) { 6 | 7 | var kity = require( "kity" ), 8 | 9 | PREFIX = "kf-editor-ui-", 10 | 11 | LIST_OFFSET = 7, 12 | 13 | DEFAULT_OPTIONS = { 14 | iconSize: { 15 | w: 32, 16 | h: 32 17 | } 18 | }, 19 | 20 | // UiUitls 21 | $$ = require( "ui/ui-impl/ui-utils" ), 22 | 23 | Button = kity.createClass( "Button", { 24 | 25 | constructor: function ( doc, options ) { 26 | 27 | this.options = kity.Utils.extend( {}, DEFAULT_OPTIONS, options ); 28 | 29 | // 事件状态, 是否已经初始化 30 | this.eventState = false; 31 | this.toolbar = null; 32 | this.displayState = false; 33 | this.fixOffset = options.fixOffset || false; 34 | 35 | this.doc = doc; 36 | 37 | this.element = this.createButton(); 38 | this.disabled = true; 39 | 40 | // 挂载的对象 41 | this.mountElement = null; 42 | 43 | this.icon = this.createIcon(); 44 | this.label = this.createLabel(); 45 | this.sign = this.createSign(); 46 | this.mountPoint = this.createMountPoint(); 47 | 48 | this.mergeElement(); 49 | 50 | }, 51 | 52 | initEvent: function () { 53 | 54 | var _self = this; 55 | 56 | if ( this.eventState ) { 57 | return; 58 | } 59 | 60 | this.eventState = true; 61 | 62 | $$.on( this.element, "mousedown", function ( e ) { 63 | 64 | e.preventDefault(); 65 | e.stopPropagation(); 66 | 67 | if ( e.which !== 1 ) { 68 | return; 69 | } 70 | 71 | if ( _self.disabled ) { 72 | return; 73 | } 74 | 75 | _self.toggleSelect(); 76 | _self.toggleMountElement(); 77 | 78 | } ); 79 | 80 | }, 81 | 82 | setToolbar: function ( toolbar ) { 83 | this.toolbar = toolbar; 84 | }, 85 | 86 | toggleMountElement: function () { 87 | 88 | if ( this.displayState ) { 89 | this.hideMount(); 90 | } else { 91 | this.showMount(); 92 | } 93 | 94 | }, 95 | 96 | setLabel: function ( labelText ) { 97 | var signText = ""; 98 | if ( this.sign ) { 99 | signText = '
'; 100 | } 101 | this.label.innerHTML = labelText + signText; 102 | }, 103 | 104 | toggleSelect: function () { 105 | $$.getClassList( this.element ).toggle( PREFIX + "button-in" ); 106 | }, 107 | 108 | unselect: function () { 109 | $$.getClassList( this.element ).remove( PREFIX + "button-in" ); 110 | }, 111 | 112 | select: function () { 113 | $$.getClassList( this.element ).add( PREFIX + "button-in" ); 114 | }, 115 | 116 | show: function () { 117 | this.select(); 118 | this.showMount(); 119 | }, 120 | 121 | hide: function () { 122 | this.unselect(); 123 | this.hideMount(); 124 | }, 125 | 126 | showMount: function () { 127 | 128 | this.displayState = true; 129 | this.mountPoint.style.display = "block"; 130 | 131 | if ( this.fixOffset ) { 132 | 133 | var elementRect = this.element.getBoundingClientRect(); 134 | this.mountElement.setOffset( elementRect.left + LIST_OFFSET, elementRect.bottom ); 135 | 136 | } 137 | 138 | var editorContainer = this.toolbar.getContainer(), 139 | currentBox = null, 140 | containerBox = $$.getRectBox( editorContainer ), 141 | mountEleBox = this.mountElement.getPositionInfo(); 142 | 143 | // 修正偏移 144 | if ( mountEleBox.right > containerBox.right ) { 145 | currentBox = $$.getRectBox( this.element ); 146 | // 对齐到按钮的右边界 147 | this.mountPoint.style.left = currentBox.right - mountEleBox.right - 1 + "px"; 148 | } 149 | 150 | this.mountElement.updateSize && this.mountElement.updateSize(); 151 | 152 | }, 153 | 154 | hideMount: function () { 155 | this.displayState = false; 156 | this.mountPoint.style.display = "none"; 157 | }, 158 | 159 | getNode: function () { 160 | return this.element; 161 | }, 162 | 163 | mount: function ( element ) { 164 | this.mountElement = element; 165 | element.mountTo( this.mountPoint ); 166 | }, 167 | 168 | createButton: function () { 169 | 170 | var buttonNode = $$.ele( this.doc, "div", { 171 | className: PREFIX + "button" 172 | } ); 173 | 174 | // 附加className 175 | if ( this.options.className ) { 176 | buttonNode.className += " " + PREFIX + this.options.className; 177 | } 178 | 179 | return buttonNode; 180 | 181 | }, 182 | 183 | createIcon: function () { 184 | 185 | if ( !this.options.icon ) { 186 | return null; 187 | } 188 | 189 | var iconNode = $$.ele( this.doc, "div", { 190 | className: PREFIX + "button-icon" 191 | } ); 192 | 193 | if ( typeof this.options.icon === "string" ) { 194 | iconNode.style.backgroundImage = "url(" + this.options.icon + ") no-repeat"; 195 | } else { 196 | iconNode.style.background = getBackgroundStyle( this.options.icon ); 197 | } 198 | 199 | if ( this.options.iconSize.w ) { 200 | iconNode.style.width = this.options.iconSize.w + "px"; 201 | } 202 | 203 | if ( this.options.iconSize.h ) { 204 | iconNode.style.height = this.options.iconSize.h + "px"; 205 | } 206 | 207 | return iconNode; 208 | 209 | }, 210 | 211 | createLabel: function () { 212 | 213 | var labelNode = $$.ele( this.doc, "div", { 214 | className: PREFIX + "button-label", 215 | content: this.options.label 216 | } ); 217 | 218 | 219 | return labelNode; 220 | 221 | }, 222 | 223 | createSign: function () { 224 | 225 | if ( this.options.sign === false ) { 226 | return null; 227 | } 228 | 229 | return $$.ele( this.doc, "div", { 230 | className: PREFIX + "button-sign" 231 | } ); 232 | 233 | }, 234 | 235 | createMountPoint: function () { 236 | 237 | return $$.ele( this.doc, "div", { 238 | className: PREFIX + "button-mount-point" 239 | } ); 240 | 241 | }, 242 | 243 | disable: function () { 244 | this.disabled = true; 245 | $$.getClassList( this.element ).remove( PREFIX + "enabled" ); 246 | }, 247 | 248 | enable: function () { 249 | this.disabled = false; 250 | $$.getClassList( this.element ).add( PREFIX + "enabled" ); 251 | }, 252 | 253 | mergeElement: function () { 254 | 255 | this.icon && this.element.appendChild( this.icon ); 256 | this.element.appendChild( this.label ); 257 | this.sign && this.label.appendChild( this.sign ); 258 | this.element.appendChild( this.mountPoint ); 259 | 260 | } 261 | 262 | } ); 263 | 264 | function getBackgroundStyle ( data ) { 265 | 266 | var style = "url( " + data.src + " ) no-repeat "; 267 | 268 | style += -data.x + 'px '; 269 | style += -data.y + 'px'; 270 | 271 | return style; 272 | 273 | } 274 | 275 | return Button; 276 | 277 | } ); -------------------------------------------------------------------------------- /src/ui/ui-impl/keyboard/panel/position.data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: Demian 3 | * @Date: 2020-04-22 14:54:26 4 | * @LastEditor: Demian 5 | * @LastEditTime: 2020-05-18 14:37:05 6 | */ 7 | define(function () { 8 | return { 9 | '<': { 10 | x: 0, 11 | y: 0, 12 | }, 13 | '\\frac \\placeholder\\placeholder': { 14 | x: 1, 15 | y: 0, 16 | }, 17 | '\\sqrt \\placeholder': { 18 | x: 2, 19 | y: 0, 20 | }, 21 | a: { 22 | x: 3, 23 | y: 0, 24 | }, 25 | '+': { 26 | x: 4, 27 | y: 0, 28 | }, 29 | '7': { 30 | x: 5, 31 | y: 0, 32 | }, 33 | '8': { 34 | x: 6, 35 | y: 0, 36 | }, 37 | '9': { 38 | x: 7, 39 | y: 0, 40 | }, 41 | '>': { 42 | x: 0, 43 | y: 1, 44 | }, 45 | '\\left|\\placeholder\\right|': { 46 | x: 1, 47 | y: 1, 48 | }, 49 | '\\placeholder^2': { 50 | x: 2, 51 | y: 1, 52 | }, 53 | b: { 54 | x: 3, 55 | y: 1, 56 | }, 57 | '-': { 58 | x: 4, 59 | y: 1, 60 | }, 61 | '4': { 62 | x: 5, 63 | y: 1, 64 | }, 65 | '5': { 66 | x: 6, 67 | y: 1, 68 | }, 69 | '6': { 70 | x: 7, 71 | y: 1, 72 | }, 73 | '\\leq': { 74 | x: 0, 75 | y: 2, 76 | }, 77 | '\\left(\\placeholder\\right)': { 78 | x: 1, 79 | y: 2, 80 | }, 81 | '\\sqrt [3] \\placeholder': { 82 | x: 2, 83 | y: 2, 84 | }, 85 | x: { 86 | x: 3, 87 | y: 2, 88 | }, 89 | '\\pm': { 90 | x: 4, 91 | y: 2, 92 | }, 93 | '1': { 94 | x: 5, 95 | y: 2, 96 | }, 97 | '2': { 98 | x: 6, 99 | y: 2, 100 | }, 101 | '3': { 102 | x: 7, 103 | y: 2, 104 | }, 105 | '\\geq': { 106 | x: 0, 107 | y: 3, 108 | }, 109 | '%': { 110 | x: 1, 111 | y: 3, 112 | }, 113 | '\\placeholder^3': { 114 | x: 2, 115 | y: 3, 116 | }, 117 | y: { 118 | x: 3, 119 | y: 3, 120 | }, 121 | ',': { 122 | x: 4, 123 | y: 3, 124 | }, 125 | '0': { 126 | x: 5, 127 | y: 3, 128 | }, 129 | '.': { 130 | x: 6, 131 | y: 3, 132 | }, 133 | '=': { 134 | x: 7, 135 | y: 3, 136 | }, 137 | '\\times': { 138 | x: 0, 139 | y: 4, 140 | }, 141 | '\\div': { 142 | x: 1, 143 | y: 4, 144 | }, 145 | '\\approx': { 146 | x: 2, 147 | y: 4, 148 | }, 149 | '\\neq': { 150 | x: 3, 151 | y: 4, 152 | }, 153 | '\\sqrt [\\placeholder] \\placeholder': { 154 | x: 4, 155 | y: 4, 156 | }, 157 | '\\pi': { 158 | x: 5, 159 | y: 4, 160 | }, 161 | '\\delta': { 162 | x: 6, 163 | y: 4, 164 | }, 165 | '\\left[\\placeholder\\right]': { 166 | x: 7, 167 | y: 4, 168 | }, 169 | '\\placeholder^\\placeholder': { 170 | x: 0, 171 | y: 5, 172 | }, 173 | '\\placeholder_\\placeholder': { 174 | x: 1, 175 | y: 5, 176 | }, 177 | '{^\\placeholder_\\placeholder\\placeholder}': { 178 | x: 2, 179 | y: 5, 180 | }, 181 | '\\placeholder^\\placeholder_\\placeholder': { 182 | x: 3, 183 | y: 5, 184 | }, 185 | '\\sum\\placeholder': { 186 | x: 4, 187 | y: 5, 188 | }, 189 | '\\sum_\\placeholder\\placeholder': { 190 | x: 5, 191 | y: 5, 192 | }, 193 | '\\sum^\\placeholder_\\placeholder\\placeholder': { 194 | x: 6, 195 | y: 5, 196 | }, 197 | '\\int \\placeholder': { 198 | x: 7, 199 | y: 5, 200 | }, 201 | '\\int^\\placeholder_\\placeholder\\placeholder': { 202 | x: 0, 203 | y: 6, 204 | }, 205 | '\\iint\\placeholder': { 206 | x: 1, 207 | y: 6, 208 | }, 209 | '\\iint^\\placeholder_\\placeholder\\placeholder': { 210 | x: 2, 211 | y: 6, 212 | }, 213 | '\\iiint\\placeholder': { 214 | x: 3, 215 | y: 6, 216 | }, 217 | '\\iiint^\\placeholder_\\placeholder\\placeholder': { 218 | x: 4, 219 | y: 6, 220 | }, 221 | '\\log\\placeholder': { 222 | x: 5, 223 | y: 6, 224 | }, 225 | '\\ln\\placeholder': { 226 | x: 6, 227 | y: 6, 228 | }, 229 | '\\land': { 230 | x: 7, 231 | y: 6, 232 | }, 233 | '\\lor': { 234 | x: 0, 235 | y: 7, 236 | }, 237 | '\\neg': { 238 | x: 1, 239 | y: 7, 240 | }, 241 | '\\forall': { 242 | x: 2, 243 | y: 7, 244 | }, 245 | '\\exists': { 246 | x: 3, 247 | y: 7, 248 | }, 249 | '\\infty': { 250 | x: 4, 251 | y: 7, 252 | }, 253 | '\\cup': { 254 | x: 5, 255 | y: 7, 256 | }, 257 | '\\cap': { 258 | x: 6, 259 | y: 7, 260 | }, 261 | '\\in': { 262 | x: 7, 263 | y: 7, 264 | }, 265 | '\\notin': { 266 | x: 0, 267 | y: 8, 268 | }, 269 | '\\subset': { 270 | x: 1, 271 | y: 8, 272 | }, 273 | '\\subseteq': { 274 | x: 2, 275 | y: 8, 276 | }, 277 | '\\supset': { 278 | x: 3, 279 | y: 8, 280 | }, 281 | '\\supseteq': { 282 | x: 4, 283 | y: 8, 284 | }, 285 | '\\varnothing': { 286 | x: 5, 287 | y: 8, 288 | }, 289 | '\\cdot': { 290 | x: 6, 291 | y: 8, 292 | }, 293 | '\\colon': { 294 | x: 7, 295 | y: 8, 296 | }, 297 | '\\sin\\placeholder': { 298 | x: 0, 299 | y: 9, 300 | }, 301 | '\\cos\\placeholder': { 302 | x: 1, 303 | y: 9, 304 | }, 305 | '\\tan\\placeholder': { 306 | x: 2, 307 | y: 9, 308 | }, 309 | '\\sec\\placeholder': { 310 | x: 3, 311 | y: 9, 312 | }, 313 | '\\csc\\placeholder': { 314 | x: 4, 315 | y: 9, 316 | }, 317 | '\\cot\\placeholder': { 318 | x: 5, 319 | y: 9, 320 | }, 321 | '\\arcsin\\placeholder': { 322 | x: 6, 323 | y: 9, 324 | }, 325 | '\\arccos\\placeholder': { 326 | x: 7, 327 | y: 9, 328 | }, 329 | '\\arctan\\placeholder': { 330 | x: 0, 331 | y: 10, 332 | }, 333 | '\\triangle': { 334 | x: 1, 335 | y: 10, 336 | }, 337 | '\\sim': { 338 | x: 2, 339 | y: 10, 340 | }, 341 | '\\cong': { 342 | x: 3, 343 | y: 10, 344 | }, 345 | '\\angle': { 346 | x: 4, 347 | y: 10, 348 | }, 349 | '\\bot': { 350 | x: 5, 351 | y: 10, 352 | }, 353 | '\\alpha': { 354 | x: 6, 355 | y: 10, 356 | }, 357 | '\\beta': { 358 | x: 7, 359 | y: 10, 360 | }, 361 | '\\gamma': { 362 | x: 0, 363 | y: 11, 364 | }, 365 | '\\theta': { 366 | x: 1, 367 | y: 11, 368 | }, 369 | '\\degree': { 370 | x: 2, 371 | y: 11, 372 | }, 373 | '\\bigcirc': { 374 | x: 3, 375 | y: 11, 376 | }, 377 | a: { x: 0, y: 12 }, 378 | b: { x: 1, y: 12 }, 379 | c: { x: 2, y: 12 }, 380 | d: { x: 3, y: 12 }, 381 | e: { x: 4, y: 12 }, 382 | f: { x: 5, y: 12 }, 383 | g: { x: 6, y: 12 }, 384 | h: { x: 7, y: 12 }, 385 | i: { x: 0, y: 13 }, 386 | j: { x: 1, y: 13 }, 387 | k: { x: 2, y: 13 }, 388 | l: { x: 3, y: 13 }, 389 | m: { x: 4, y: 13 }, 390 | n: { x: 5, y: 13 }, 391 | o: { x: 6, y: 13 }, 392 | p: { x: 7, y: 13 }, 393 | q: { x: 0, y: 14 }, 394 | r: { x: 1, y: 14 }, 395 | s: { x: 2, y: 14 }, 396 | t: { x: 3, y: 14 }, 397 | u: { x: 4, y: 14 }, 398 | v: { x: 5, y: 14 }, 399 | w: { x: 6, y: 14 }, 400 | x: { x: 7, y: 14 }, 401 | y: { x: 0, y: 15 }, 402 | z: { x: 1, y: 15 }, 403 | '\\Omega': { 404 | x: 0, 405 | y: 16, 406 | }, 407 | '\\because': { 408 | x: 1, 409 | y: 16, 410 | }, 411 | '\\therefore': { 412 | x: 2, 413 | y: 16, 414 | }, 415 | '\\Longrightarrow': { 416 | x: 3, 417 | y: 16, 418 | }, 419 | '\\Leftrightarrow': { 420 | x: 4, 421 | y: 16, 422 | }, 423 | '\\uparrow': { 424 | x: 5, 425 | y: 16, 426 | }, 427 | '\\downarrow': { 428 | x: 6, 429 | y: 16, 430 | }, 431 | '\\lambda': { 432 | x: 7, 433 | y: 16, 434 | }, 435 | '\\kappa': { 436 | x: 0, 437 | y: 17, 438 | }, 439 | '\\mu': { 440 | x: 1, 441 | y: 17, 442 | }, 443 | '\\rho': { 444 | x: 2, 445 | y: 17, 446 | }, 447 | '\\sigma': { 448 | x: 3, 449 | y: 17, 450 | }, 451 | '\\tau': { 452 | x: 4, 453 | y: 17, 454 | }, 455 | '\\upsilon': { 456 | x: 5, 457 | y: 17, 458 | }, 459 | '\\varphi': { 460 | x: 6, 461 | y: 17, 462 | }, 463 | '\\Psi': { 464 | x: 7, 465 | y: 17, 466 | }, 467 | '\\omega': { 468 | x: 0, 469 | y: 18, 470 | }, 471 | '\\varepsilon': { 472 | x: 1, 473 | y: 18, 474 | }, 475 | '\\zeta': { 476 | x: 2, 477 | y: 18, 478 | }, 479 | '\\eta': { 480 | x: 3, 481 | y: 18, 482 | }, 483 | '\\nu': { 484 | x: 4, 485 | y: 18, 486 | }, 487 | '\\xi': { 488 | x: 5, 489 | y: 18, 490 | }, 491 | '\\chi': { 492 | x: 6, 493 | y: 18, 494 | }, 495 | }; 496 | }); 497 | -------------------------------------------------------------------------------- /dist/assets/styles/theme/pc.css: -------------------------------------------------------------------------------- 1 | .pc { 2 | width: 864px; 3 | height: 580px; 4 | position: relative; 5 | top: 0; 6 | left: 0; 7 | } 8 | .pc * { 9 | box-sizing: border-box; 10 | margin: 0; 11 | padding: 0; 12 | list-style: none; 13 | } 14 | .pc .kf-editor-edit-area { 15 | width: 100%; 16 | height: 168px; 17 | float: left; 18 | } 19 | .pc .kf-editor-edit-area .kf-editor-edit-scrollbar { 20 | bottom: 0px; 21 | } 22 | .pc .kf-editor-edit-area .kf-editor-header-container { 23 | width: 100%; 24 | height: 48px; 25 | background: #eeeeee; 26 | overflow: hidden; 27 | padding-left: 16px; 28 | } 29 | .pc .kf-editor-edit-area .kf-editor-header-container .kf-editor-header-container-title { 30 | font-size: 16px; 31 | color: #616266; 32 | line-height: 48px; 33 | float: left; 34 | } 35 | .pc .kf-editor-edit-area .kf-editor-header-container .kf-editor-header-container-close { 36 | width: 48px; 37 | height: 48px; 38 | float: right; 39 | cursor: pointer; 40 | position: relative; 41 | } 42 | .pc .kf-editor-edit-area .kf-editor-header-container .kf-editor-header-container-close::before { 43 | content: ''; 44 | display: block; 45 | background: url('https://store-g1.seewo.com/easiclass-public/8747fc92a01246e19d7f9bc9f3868652') center no-repeat; 46 | background-size: 100%; 47 | position: absolute; 48 | width: 16px; 49 | height: 16px; 50 | top: 16px; 51 | right: 16px; 52 | } 53 | .pc .kf-editor-edit-area .kf-editor-header-container .kf-editor-header-container-close:hover::before { 54 | background: url('https://store-g1.seewo.com/easiclass-public/c2682d9dcc844986898be60b26ee5823'); 55 | } 56 | .pc .kf-editor-edit-area .kf-editor-header-container .kf-editor-header-container-close:active::before { 57 | background: url('https://store-g1.seewo.com/easiclass-public/9550aaa080574f739425122e7908e012'); 58 | } 59 | .pc .kf-editor-edit-area .kf-editor-canvas-wrapper { 60 | width: 100%; 61 | height: calc(100% - 48px); 62 | background: #fff; 63 | float: left; 64 | padding: 0 40px; 65 | } 66 | .pc .kf-editor-edit-area .kf-editor-canvas-wrapper .kf-editor-canvas-container { 67 | width: 100%; 68 | height: 100%; 69 | } 70 | .pc .kf-editor-edit-keyboard { 71 | width: 100%; 72 | height: 411px; 73 | border-top: 1px solid #ddd; 74 | box-sizing: border-box; 75 | background-color: white; 76 | } 77 | .pc .kf-editor-edit-keyboard * { 78 | list-style: none; 79 | padding: 0; 80 | margin: 0; 81 | box-sizing: border-box; 82 | } 83 | .pc .kf-editor-ui-keyboard { 84 | width: 100%; 85 | height: 100%; 86 | overflow: hidden; 87 | background: #fff; 88 | border-top: 1px solid #e4e4e4; 89 | } 90 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-menu { 91 | width: 118px; 92 | height: 347px; 93 | border-right: 1px solid #e4e4e4; 94 | float: left; 95 | } 96 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-menu .kf-editor-ui-keyboard-menu-list { 97 | width: 100%; 98 | height: 100%; 99 | padding: 11px; 100 | background: #f4f4f4; 101 | overflow: hidden; 102 | } 103 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-menu .kf-editor-ui-keyboard-menu-list .kf-editor-ui-keyboard-menu-list-item { 104 | width: 72px; 105 | height: 40px; 106 | margin: 12px; 107 | text-align: center; 108 | line-height: 40px; 109 | font-size: 16px; 110 | color: #49494d; 111 | cursor: pointer; 112 | float: left; 113 | } 114 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-menu .kf-editor-ui-keyboard-menu-list .kf-editor-ui-keyboard-menu-list-item-active { 115 | background: #198cff; 116 | border-radius: 4px; 117 | color: #ffffff; 118 | } 119 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-panel { 120 | width: 664px; 121 | height: 347px; 122 | float: left; 123 | overflow: hidden; 124 | } 125 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-panel .kf-editor-ui-keyboard-panel-list { 126 | width: 100%; 127 | position: relative; 128 | top: 0; 129 | transition: 0.3s top linear; 130 | } 131 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-panel .kf-editor-ui-keyboard-panel-list .kf-editor-ui-keyboard-panel-list-item { 132 | width: 83px; 133 | height: 64px; 134 | cursor: pointer; 135 | float: left; 136 | border: solid #e4e4e4; 137 | border-width: 1px 1px 1px 0px; 138 | margin-top: -1px; 139 | position: relative; 140 | } 141 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-panel .kf-editor-ui-keyboard-panel-list .kf-editor-ui-keyboard-panel-list-item:hover::before { 142 | content: ''; 143 | display: block; 144 | position: absolute; 145 | width: 100%; 146 | height: 100%; 147 | background-color: rgba(73, 73, 77, 0.1); 148 | } 149 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page { 150 | width: 81px; 151 | height: 347px; 152 | float: left; 153 | margin-left: -1px; 154 | border-left: 1px solid #e4e4e4; 155 | } 156 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item { 157 | width: 81px; 158 | height: 128px; 159 | cursor: pointer; 160 | } 161 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-delete { 162 | width: 81px; 163 | height: 64px; 164 | background: url('https://store-g1.seewo.com/easiclass-public/78c93d3c1204445982c6b5700e1442eb') center no-repeat; 165 | background-size: initial; 166 | border-bottom: 1px solid #e4e4e4; 167 | } 168 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-delete:hover { 169 | background-color: rgba(73, 73, 77, 0.1); 170 | } 171 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-prev { 172 | border-bottom: 1px solid #e4e4e4; 173 | position: relative; 174 | } 175 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-prev::before { 176 | content: ' '; 177 | display: block; 178 | width: 100%; 179 | height: 100%; 180 | background: url('https://store-g1.seewo.com/easiclass-public/4993661fa54249249b152a89cb1cec6b') center no-repeat; 181 | background-size: initial; 182 | } 183 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-prev:not(.kf-editor-ui-keyboard-page-list-item-disabled):hover { 184 | background-color: rgba(73, 73, 77, 0.1); 185 | } 186 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-next { 187 | position: relative; 188 | } 189 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-next:not(.kf-editor-ui-keyboard-page-list-item-disabled):hover { 190 | background-color: rgba(73, 73, 77, 0.1); 191 | } 192 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-next::before { 193 | content: ' '; 194 | display: block; 195 | width: 100%; 196 | height: 100%; 197 | background: url('https://store-g1.seewo.com/easiclass-public/f4df574b99d748f0823ccd36362d1a9c') center no-repeat; 198 | background-size: initial; 199 | } 200 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-disabled::before { 201 | opacity: 0.5; 202 | } 203 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-page .kf-editor-ui-keyboard-page-list .kf-editor-ui-keyboard-page-list-item-ok { 204 | display: none; 205 | } 206 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-footer { 207 | width: 100%; 208 | height: 64px; 209 | padding: 14px 0; 210 | float: left; 211 | border-top: 1px solid #e4e4e4; 212 | } 213 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-footer #kf-editor-ui-keyboard-footer-button-cancel { 214 | width: 120px; 215 | height: 36px; 216 | background: #ffffff; 217 | border: 1px solid #dddddd; 218 | border-radius: 18px; 219 | font-size: 16px; 220 | color: #616266; 221 | text-align: center; 222 | line-height: 34px; 223 | float: left; 224 | margin-left: calc(50% - 128px); 225 | cursor: pointer; 226 | user-select: none; 227 | } 228 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-footer #kf-editor-ui-keyboard-footer-button-cancel:not(:disabled) { 229 | cursor: pointer; 230 | } 231 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-footer #kf-editor-ui-keyboard-footer-button-cancel:not(:disabled):hover { 232 | background-color: #ebf5ff; 233 | } 234 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-footer #kf-editor-ui-keyboard-footer-button-cancel:not(:disabled):active { 235 | background-color: #dce8f5; 236 | } 237 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-footer #kf-editor-ui-keyboard-footer-button-submit { 238 | width: 120px; 239 | height: 36px; 240 | background: #198cff; 241 | border-radius: 18px; 242 | font-size: 16px; 243 | color: #ffffff; 244 | text-align: center; 245 | line-height: 36px; 246 | float: left; 247 | margin-left: 16px; 248 | cursor: pointer; 249 | } 250 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-footer #kf-editor-ui-keyboard-footer-button-submit:not(:disabled) { 251 | cursor: pointer; 252 | } 253 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-footer #kf-editor-ui-keyboard-footer-button-submit:not(:disabled):hover { 254 | background-color: #3399ff; 255 | } 256 | .pc .kf-editor-ui-keyboard .kf-editor-ui-keyboard-footer #kf-editor-ui-keyboard-footer-button-submit:not(:disabled):active { 257 | background-color: #1885f2; 258 | } 259 | --------------------------------------------------------------------------------