diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index de5d954..b09db87 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ -.bundle/ -log/*.log -pkg/ -test/dummy/db/*.sqlite3 -test/dummy/db/*.sqlite3-journal -test/dummy/log/*.log -test/dummy/tmp/ -test/dummy/.sass-cache +.bundle/ +log/*.log +pkg/ +test/dummy/db/*.sqlite3 +test/dummy/db/*.sqlite3-journal +test/dummy/log/*.log +test/dummy/tmp/ +test/dummy/.sass-cache diff --git a/Gemfile b/Gemfile old mode 100644 new mode 100755 index a98e254..85319f2 --- a/Gemfile +++ b/Gemfile @@ -1,14 +1,14 @@ -source "https://rubygems.org" - -# Declare your gem's dependencies in universal_table.gemspec. -# Bundler will treat runtime dependencies like base dependencies, and -# development dependencies will be added by default to the :development group. -gemspec - -# Declare any dependencies that are still in development here instead of in -# your gemspec. These might include edge Rails or gems from your path or -# Git. Remember to move these dependencies to your gemspec before releasing -# your gem to rubygems.org. - -# To use debugger -# gem 'debugger' +source "https://rubygems.org" + +# Declare your gem's dependencies in universal_table.gemspec. +# Bundler will treat runtime dependencies like base dependencies, and +# development dependencies will be added by default to the :development group. +gemspec + +# Declare any dependencies that are still in development here instead of in +# your gemspec. These might include edge Rails or gems from your path or +# Git. Remember to move these dependencies to your gemspec before releasing +# your gem to rubygems.org. + +# To use debugger +# gem 'debugger' diff --git a/MIT-LICENSE b/MIT-LICENSE old mode 100644 new mode 100755 index 1e4beb8..ce19b8a --- a/MIT-LICENSE +++ b/MIT-LICENSE @@ -1,20 +1,20 @@ -Copyright 2015 YOURNAME - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright 2015 YOURNAME + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.rdoc b/README.rdoc old mode 100644 new mode 100755 index 690ede5..ebfafdb --- a/README.rdoc +++ b/README.rdoc @@ -1,3 +1,3 @@ -= UniversalTable - += UniversalTable + This project rocks and uses MIT-LICENSE. \ No newline at end of file diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 index e76c625..58babef --- a/Rakefile +++ b/Rakefile @@ -1,34 +1,34 @@ -begin - require 'bundler/setup' -rescue LoadError - puts 'You must `gem install bundler` and `bundle install` to run rake tasks' -end - -require 'rdoc/task' - -RDoc::Task.new(:rdoc) do |rdoc| - rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'UniversalTable' - rdoc.options << '--line-numbers' - rdoc.rdoc_files.include('README.rdoc') - rdoc.rdoc_files.include('lib/**/*.rb') -end - -APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) -load 'rails/tasks/engine.rake' - - - -Bundler::GemHelper.install_tasks - -require 'rake/testtask' - -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.libs << 'test' - t.pattern = 'test/**/*_test.rb' - t.verbose = false -end - - -task default: :test +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end + +require 'rdoc/task' + +RDoc::Task.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'UniversalTable' + rdoc.options << '--line-numbers' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +load 'rails/tasks/engine.rake' + + + +Bundler::GemHelper.install_tasks + +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + + +task default: :test diff --git a/app/assets/images/universal_table/.keep b/app/assets/images/universal_table/.keep old mode 100644 new mode 100755 diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.common.js b/app/assets/javascripts/mind_map/jsmind/jsmind.common.js old mode 100644 new mode 100755 index 0a13da6..43d3fad --- a/app/assets/javascripts/mind_map/jsmind/jsmind.common.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.common.js @@ -1,78 +1,78 @@ -export const __version__ = '1.0.0' -export const __author__ = 'author' - -if (typeof String.prototype.startsWith != 'function') { - String.prototype.startsWith = function (p) { - return this.slice(0, p.length) === p - } -} - -export const Direction = { - left: -1, - center: 0, - right: 1, - of: function (dir) { - if (!dir || dir === -1 || dir === 0 || dir === 1) { - return dir - } - if (dir === '-1' || dir === '0' || dir === '1') { - return parseInt(dir) - } - if (dir.toLowerCase() === 'left') { - return this.left - } - if (dir.toLowerCase() === 'right') { - return this.right - } - if (dir.toLowerCase() === 'center') { - return this.center - } - }, -} -export const EventType = { show: 1, resize: 2, edit: 3, select: 4 } -export const Key = { meta: 1 << 13, ctrl: 1 << 12, alt: 1 << 11, shift: 1 << 10 } -export const LogLevel = { debug: 1, info: 2, warn: 3, error: 4, disable: 9 } - -// an noop function define -var _noop = function () {} -export let logger = - typeof console === 'undefined' - ? { - level: _noop, - log: _noop, - debug: _noop, - info: _noop, - warn: _noop, - error: _noop, - } - : { - level: setup_logger_level, - log: console.log, - debug: console.debug, - info: console.info, - warn: console.warn, - error: console.error, - } - -function setup_logger_level(log_level) { - if (log_level > LogLevel.debug) { - logger.debug = _noop - } else { - logger.debug = console.debug - } - if (log_level > LogLevel.info) { - logger.info = _noop - } else { - logger.info = console.info - } - if (log_level > LogLevel.warn) { - logger.warn = _noop - } else { - logger.warn = console.warn - } - if (log_level > LogLevel.error) { - logger.error = _noop - } else { - logger.error = console.error - } -} +export const __version__ = '1.0.0' +export const __author__ = 'author' + +if (typeof String.prototype.startsWith != 'function') { + String.prototype.startsWith = function (p) { + return this.slice(0, p.length) === p + } +} + +export const Direction = { + left: -1, + center: 0, + right: 1, + of: function (dir) { + if (!dir || dir === -1 || dir === 0 || dir === 1) { + return dir + } + if (dir === '-1' || dir === '0' || dir === '1') { + return parseInt(dir) + } + if (dir.toLowerCase() === 'left') { + return this.left + } + if (dir.toLowerCase() === 'right') { + return this.right + } + if (dir.toLowerCase() === 'center') { + return this.center + } + }, +} +export const EventType = { show: 1, resize: 2, edit: 3, select: 4 } +export const Key = { meta: 1 << 13, ctrl: 1 << 12, alt: 1 << 11, shift: 1 << 10 } +export const LogLevel = { debug: 1, info: 2, warn: 3, error: 4, disable: 9 } + +// an noop function define +var _noop = function () {} +export let logger = + typeof console === 'undefined' + ? { + level: _noop, + log: _noop, + debug: _noop, + info: _noop, + warn: _noop, + error: _noop, + } + : { + level: setup_logger_level, + log: console.log, + debug: console.debug, + info: console.info, + warn: console.warn, + error: console.error, + } + +function setup_logger_level(log_level) { + if (log_level > LogLevel.debug) { + logger.debug = _noop + } else { + logger.debug = console.debug + } + if (log_level > LogLevel.info) { + logger.info = _noop + } else { + logger.info = console.info + } + if (log_level > LogLevel.warn) { + logger.warn = _noop + } else { + logger.warn = console.warn + } + if (log_level > LogLevel.error) { + logger.error = _noop + } else { + logger.error = console.error + } +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.css b/app/assets/javascripts/mind_map/jsmind/jsmind.css old mode 100644 new mode 100755 index 427fedf..204880a --- a/app/assets/javascripts/mind_map/jsmind/jsmind.css +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.css @@ -1,400 +1,400 @@ -/* important section */ -.jsmind-inner { - position: relative; - overflow: auto; - width: 100%; - height: 100%; - outline: none; -} /*box-shadow:0 0 2px #000;*/ -.jsmind-inner { - moz-user-select: -moz-none; - -moz-user-select: none; - -o-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.jsmind-inner canvas { - position: absolute; -} - -/* z-index:1 */ -svg.jsmind { - position: absolute; - z-index: 1; -} -canvas.jsmind { - position: absolute; - z-index: 1; -} - -/* z-index:2 */ -jmnodes { - position: absolute; - z-index: 2; - background-color: rgba(0, 0, 0, 0); -} /*background color is necessary*/ -jmnode { - position: absolute; - cursor: default; - max-width: 400px; -} -jmexpander { - position: absolute; - width: 11px; - height: 11px; - display: block; - overflow: hidden; - line-height: 12px; - font-size: 10px; - text-align: center; - border-radius: 6px; - border-width: 1px; - border-style: solid; - cursor: pointer; -} - -.jmnode-overflow-wrap jmnodes { - min-width: 420px; -} - -.jmnode-overflow-hidden jmnode { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* default theme */ -jmnode { - padding: 10px; - background-color: #fff; - color: #333; - border-radius: 5px; - box-shadow: 1px 1px 1px #666; - font: 16px/1.125 Verdana, Arial, Helvetica, sans-serif; -} -jmnode:hover { - box-shadow: 2px 2px 8px #000; - background-color: #ebebeb; - color: #333; -} -jmnode.selected { - background-color: #11f; - color: #fff; - box-shadow: 2px 2px 8px #000; -} -jmnode.root { - font-size: 24px; -} -jmexpander { - border-color: gray; -} -jmexpander:hover { - border-color: #000; -} - -@media screen and (max-device-width: 1024px) { - jmnode { - padding: 5px; - border-radius: 3px; - font-size: 14px; - } - jmnode.root { - font-size: 21px; - } -} -/* primary theme */ -jmnodes.theme-primary jmnode { - background-color: #428bca; - color: #fff; - border-color: #357ebd; -} -jmnodes.theme-primary jmnode:hover { - background-color: #3276b1; - border-color: #285e8e; -} -jmnodes.theme-primary jmnode.selected { - background-color: #f1c40f; - color: #fff; -} -jmnodes.theme-primary jmnode.root { -} -jmnodes.theme-primary jmexpander { -} -jmnodes.theme-primary jmexpander:hover { -} - -/* warning theme */ -jmnodes.theme-warning jmnode { - background-color: #f0ad4e; - border-color: #eea236; - color: #fff; -} -jmnodes.theme-warning jmnode:hover { - background-color: #ed9c28; - border-color: #d58512; -} -jmnodes.theme-warning jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-warning jmnode.root { -} -jmnodes.theme-warning jmexpander { -} -jmnodes.theme-warning jmexpander:hover { -} - -/* danger theme */ -jmnodes.theme-danger jmnode { - background-color: #d9534f; - border-color: #d43f3a; - color: #fff; -} -jmnodes.theme-danger jmnode:hover { - background-color: #d2322d; - border-color: #ac2925; -} -jmnodes.theme-danger jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-danger jmnode.root { -} -jmnodes.theme-danger jmexpander { -} -jmnodes.theme-danger jmexpander:hover { -} - -/* success theme */ -jmnodes.theme-success jmnode { - background-color: #5cb85c; - border-color: #4cae4c; - color: #fff; -} -jmnodes.theme-success jmnode:hover { - background-color: #47a447; - border-color: #398439; -} -jmnodes.theme-success jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-success jmnode.root { -} -jmnodes.theme-success jmexpander { -} -jmnodes.theme-success jmexpander:hover { -} - -/* info theme */ -jmnodes.theme-info jmnode { - background-color: #5dc0de; - border-color: #46b8da; - color: #fff; -} -jmnodes.theme-info jmnode:hover { - background-color: #39b3d7; - border-color: #269abc; -} -jmnodes.theme-info jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-info jmnode.root { -} -jmnodes.theme-info jmexpander { -} -jmnodes.theme-info jmexpander:hover { -} - -/* greensea theme */ -jmnodes.theme-greensea jmnode { - background-color: #1abc9c; - color: #fff; -} -jmnodes.theme-greensea jmnode:hover { - background-color: #16a085; -} -jmnodes.theme-greensea jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-greensea jmnode.root { -} -jmnodes.theme-greensea jmexpander { -} -jmnodes.theme-greensea jmexpander:hover { -} - -/* nephrite theme */ -jmnodes.theme-nephrite jmnode { - background-color: #2ecc71; - color: #fff; -} -jmnodes.theme-nephrite jmnode:hover { - background-color: #27ae60; -} -jmnodes.theme-nephrite jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-nephrite jmnode.root { -} -jmnodes.theme-nephrite jmexpander { -} -jmnodes.theme-nephrite jmexpander:hover { -} - -/* belizehole theme */ -jmnodes.theme-belizehole jmnode { - background-color: #3498db; - color: #fff; -} -jmnodes.theme-belizehole jmnode:hover { - background-color: #2980b9; -} -jmnodes.theme-belizehole jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-belizehole jmnode.root { -} -jmnodes.theme-belizehole jmexpander { -} -jmnodes.theme-belizehole jmexpander:hover { -} - -/* wisteria theme */ -jmnodes.theme-wisteria jmnode { - background-color: #9b59b6; - color: #fff; -} -jmnodes.theme-wisteria jmnode:hover { - background-color: #8e44ad; -} -jmnodes.theme-wisteria jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-wisteria jmnode.root { -} -jmnodes.theme-wisteria jmexpander { -} -jmnodes.theme-wisteria jmexpander:hover { -} - -/* asphalt theme */ -jmnodes.theme-asphalt jmnode { - background-color: #34495e; - color: #fff; -} -jmnodes.theme-asphalt jmnode:hover { - background-color: #2c3e50; -} -jmnodes.theme-asphalt jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-asphalt jmnode.root { -} -jmnodes.theme-asphalt jmexpander { -} -jmnodes.theme-asphalt jmexpander:hover { -} - -/* orange theme */ -jmnodes.theme-orange jmnode { - background-color: #f1c40f; - color: #fff; -} -jmnodes.theme-orange jmnode:hover { - background-color: #f39c12; -} -jmnodes.theme-orange jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-orange jmnode.root { -} -jmnodes.theme-orange jmexpander { -} -jmnodes.theme-orange jmexpander:hover { -} - -/* pumpkin theme */ -jmnodes.theme-pumpkin jmnode { - background-color: #e67e22; - color: #fff; -} -jmnodes.theme-pumpkin jmnode:hover { - background-color: #d35400; -} -jmnodes.theme-pumpkin jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-pumpkin jmnode.root { -} -jmnodes.theme-pumpkin jmexpander { -} -jmnodes.theme-pumpkin jmexpander:hover { -} - -/* pomegranate theme */ -jmnodes.theme-pomegranate jmnode { - background-color: #e74c3c; - color: #fff; -} -jmnodes.theme-pomegranate jmnode:hover { - background-color: #c0392b; -} -jmnodes.theme-pomegranate jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-pomegranate jmnode.root { -} -jmnodes.theme-pomegranate jmexpander { -} -jmnodes.theme-pomegranate jmexpander:hover { -} - -/* clouds theme */ -jmnodes.theme-clouds jmnode { - background-color: #ecf0f1; - color: #333; -} -jmnodes.theme-clouds jmnode:hover { - background-color: #bdc3c7; -} -jmnodes.theme-clouds jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-clouds jmnode.root { -} -jmnodes.theme-clouds jmexpander { -} -jmnodes.theme-clouds jmexpander:hover { -} - -/* asbestos theme */ -jmnodes.theme-asbestos jmnode { - background-color: #95a5a6; - color: #fff; -} -jmnodes.theme-asbestos jmnode:hover { - background-color: #7f8c8d; -} -jmnodes.theme-asbestos jmnode.selected { - background-color: #11f; - color: #fff; -} -jmnodes.theme-asbestos jmnode.root { -} -jmnodes.theme-asbestos jmexpander { -} -jmnodes.theme-asbestos jmexpander:hover { -} +/* important section */ +.jsmind-inner { + position: relative; + overflow: auto; + width: 100%; + height: 100%; + outline: none; +} /*box-shadow:0 0 2px #000;*/ +.jsmind-inner { + moz-user-select: -moz-none; + -moz-user-select: none; + -o-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.jsmind-inner canvas { + position: absolute; +} + +/* z-index:1 */ +svg.jsmind { + position: absolute; + z-index: 1; +} +canvas.jsmind { + position: absolute; + z-index: 1; +} + +/* z-index:2 */ +jmnodes { + position: absolute; + z-index: 2; + background-color: rgba(0, 0, 0, 0); +} /*background color is necessary*/ +jmnode { + position: absolute; + cursor: default; + max-width: 400px; +} +jmexpander { + position: absolute; + width: 11px; + height: 11px; + display: block; + overflow: hidden; + line-height: 12px; + font-size: 10px; + text-align: center; + border-radius: 6px; + border-width: 1px; + border-style: solid; + cursor: pointer; +} + +.jmnode-overflow-wrap jmnodes { + min-width: 420px; +} + +.jmnode-overflow-hidden jmnode { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* default theme */ +jmnode { + padding: 10px; + background-color: #fff; + color: #333; + border-radius: 5px; + box-shadow: 1px 1px 1px #666; + font: 16px/1.125 Verdana, Arial, Helvetica, sans-serif; +} +jmnode:hover { + box-shadow: 2px 2px 8px #000; + background-color: #ebebeb; + color: #333; +} +jmnode.selected { + background-color: #11f; + color: #fff; + box-shadow: 2px 2px 8px #000; +} +jmnode.root { + font-size: 24px; +} +jmexpander { + border-color: gray; +} +jmexpander:hover { + border-color: #000; +} + +@media screen and (max-device-width: 1024px) { + jmnode { + padding: 5px; + border-radius: 3px; + font-size: 14px; + } + jmnode.root { + font-size: 21px; + } +} +/* primary theme */ +jmnodes.theme-primary jmnode { + background-color: #428bca; + color: #fff; + border-color: #357ebd; +} +jmnodes.theme-primary jmnode:hover { + background-color: #3276b1; + border-color: #285e8e; +} +jmnodes.theme-primary jmnode.selected { + background-color: #f1c40f; + color: #fff; +} +jmnodes.theme-primary jmnode.root { +} +jmnodes.theme-primary jmexpander { +} +jmnodes.theme-primary jmexpander:hover { +} + +/* warning theme */ +jmnodes.theme-warning jmnode { + background-color: #f0ad4e; + border-color: #eea236; + color: #fff; +} +jmnodes.theme-warning jmnode:hover { + background-color: #ed9c28; + border-color: #d58512; +} +jmnodes.theme-warning jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-warning jmnode.root { +} +jmnodes.theme-warning jmexpander { +} +jmnodes.theme-warning jmexpander:hover { +} + +/* danger theme */ +jmnodes.theme-danger jmnode { + background-color: #d9534f; + border-color: #d43f3a; + color: #fff; +} +jmnodes.theme-danger jmnode:hover { + background-color: #d2322d; + border-color: #ac2925; +} +jmnodes.theme-danger jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-danger jmnode.root { +} +jmnodes.theme-danger jmexpander { +} +jmnodes.theme-danger jmexpander:hover { +} + +/* success theme */ +jmnodes.theme-success jmnode { + background-color: #5cb85c; + border-color: #4cae4c; + color: #fff; +} +jmnodes.theme-success jmnode:hover { + background-color: #47a447; + border-color: #398439; +} +jmnodes.theme-success jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-success jmnode.root { +} +jmnodes.theme-success jmexpander { +} +jmnodes.theme-success jmexpander:hover { +} + +/* info theme */ +jmnodes.theme-info jmnode { + background-color: #5dc0de; + border-color: #46b8da; + color: #fff; +} +jmnodes.theme-info jmnode:hover { + background-color: #39b3d7; + border-color: #269abc; +} +jmnodes.theme-info jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-info jmnode.root { +} +jmnodes.theme-info jmexpander { +} +jmnodes.theme-info jmexpander:hover { +} + +/* greensea theme */ +jmnodes.theme-greensea jmnode { + background-color: #1abc9c; + color: #fff; +} +jmnodes.theme-greensea jmnode:hover { + background-color: #16a085; +} +jmnodes.theme-greensea jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-greensea jmnode.root { +} +jmnodes.theme-greensea jmexpander { +} +jmnodes.theme-greensea jmexpander:hover { +} + +/* nephrite theme */ +jmnodes.theme-nephrite jmnode { + background-color: #2ecc71; + color: #fff; +} +jmnodes.theme-nephrite jmnode:hover { + background-color: #27ae60; +} +jmnodes.theme-nephrite jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-nephrite jmnode.root { +} +jmnodes.theme-nephrite jmexpander { +} +jmnodes.theme-nephrite jmexpander:hover { +} + +/* belizehole theme */ +jmnodes.theme-belizehole jmnode { + background-color: #3498db; + color: #fff; +} +jmnodes.theme-belizehole jmnode:hover { + background-color: #2980b9; +} +jmnodes.theme-belizehole jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-belizehole jmnode.root { +} +jmnodes.theme-belizehole jmexpander { +} +jmnodes.theme-belizehole jmexpander:hover { +} + +/* wisteria theme */ +jmnodes.theme-wisteria jmnode { + background-color: #9b59b6; + color: #fff; +} +jmnodes.theme-wisteria jmnode:hover { + background-color: #8e44ad; +} +jmnodes.theme-wisteria jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-wisteria jmnode.root { +} +jmnodes.theme-wisteria jmexpander { +} +jmnodes.theme-wisteria jmexpander:hover { +} + +/* asphalt theme */ +jmnodes.theme-asphalt jmnode { + background-color: #34495e; + color: #fff; +} +jmnodes.theme-asphalt jmnode:hover { + background-color: #2c3e50; +} +jmnodes.theme-asphalt jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-asphalt jmnode.root { +} +jmnodes.theme-asphalt jmexpander { +} +jmnodes.theme-asphalt jmexpander:hover { +} + +/* orange theme */ +jmnodes.theme-orange jmnode { + background-color: #f1c40f; + color: #fff; +} +jmnodes.theme-orange jmnode:hover { + background-color: #f39c12; +} +jmnodes.theme-orange jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-orange jmnode.root { +} +jmnodes.theme-orange jmexpander { +} +jmnodes.theme-orange jmexpander:hover { +} + +/* pumpkin theme */ +jmnodes.theme-pumpkin jmnode { + background-color: #e67e22; + color: #fff; +} +jmnodes.theme-pumpkin jmnode:hover { + background-color: #d35400; +} +jmnodes.theme-pumpkin jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-pumpkin jmnode.root { +} +jmnodes.theme-pumpkin jmexpander { +} +jmnodes.theme-pumpkin jmexpander:hover { +} + +/* pomegranate theme */ +jmnodes.theme-pomegranate jmnode { + background-color: #e74c3c; + color: #fff; +} +jmnodes.theme-pomegranate jmnode:hover { + background-color: #c0392b; +} +jmnodes.theme-pomegranate jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-pomegranate jmnode.root { +} +jmnodes.theme-pomegranate jmexpander { +} +jmnodes.theme-pomegranate jmexpander:hover { +} + +/* clouds theme */ +jmnodes.theme-clouds jmnode { + background-color: #ecf0f1; + color: #333; +} +jmnodes.theme-clouds jmnode:hover { + background-color: #bdc3c7; +} +jmnodes.theme-clouds jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-clouds jmnode.root { +} +jmnodes.theme-clouds jmexpander { +} +jmnodes.theme-clouds jmexpander:hover { +} + +/* asbestos theme */ +jmnodes.theme-asbestos jmnode { + background-color: #95a5a6; + color: #fff; +} +jmnodes.theme-asbestos jmnode:hover { + background-color: #7f8c8d; +} +jmnodes.theme-asbestos jmnode.selected { + background-color: #11f; + color: #fff; +} +jmnodes.theme-asbestos jmnode.root { +} +jmnodes.theme-asbestos jmexpander { +} +jmnodes.theme-asbestos jmexpander:hover { +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.data_provider.js b/app/assets/javascripts/mind_map/jsmind/jsmind.data_provider.js old mode 100644 new mode 100755 index ae9da1b..16e80a3 --- a/app/assets/javascripts/mind_map/jsmind/jsmind.data_provider.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.data_provider.js @@ -1,56 +1,56 @@ -import { logger } from './jsmind.common.js' -import { format } from './jsmind.format.js' - -export class DataProvider { - constructor(jm) { - this.jm = jm - } - - init() { - logger.debug('data.init') - } - reset() { - logger.debug('data.reset') - } - load(mind_data) { - var df = null - var mind = null - if (typeof mind_data === 'object') { - if (!!mind_data.format) { - df = mind_data.format - } else { - df = 'node_tree' - } - } else { - df = 'freemind' - } - - if (df == 'node_array') { - mind = format.node_array.get_mind(mind_data) - } else if (df == 'node_tree') { - mind = format.node_tree.get_mind(mind_data) - } else if (df == 'freemind') { - mind = format.freemind.get_mind(mind_data) - } else if (df == 'text') { - mind = format.text.get_mind(mind_data) - } else { - logger.warn('unsupported format') - } - return mind - } - get_data(data_format) { - var data = null - if (data_format == 'node_array') { - data = format.node_array.get_data(this.jm.mind) - } else if (data_format == 'node_tree') { - data = format.node_tree.get_data(this.jm.mind) - } else if (data_format == 'freemind') { - data = format.freemind.get_data(this.jm.mind) - } else if (data_format == 'text') { - data = format.text.get_data(this.jm.mind) - } else { - logger.error('unsupported ' + data_format + ' format') - } - return data - } -} +import { logger } from './jsmind.common.js' +import { format } from './jsmind.format.js' + +export class DataProvider { + constructor(jm) { + this.jm = jm + } + + init() { + logger.debug('data.init') + } + reset() { + logger.debug('data.reset') + } + load(mind_data) { + var df = null + var mind = null + if (typeof mind_data === 'object') { + if (!!mind_data.format) { + df = mind_data.format + } else { + df = 'node_tree' + } + } else { + df = 'freemind' + } + + if (df == 'node_array') { + mind = format.node_array.get_mind(mind_data) + } else if (df == 'node_tree') { + mind = format.node_tree.get_mind(mind_data) + } else if (df == 'freemind') { + mind = format.freemind.get_mind(mind_data) + } else if (df == 'text') { + mind = format.text.get_mind(mind_data) + } else { + logger.warn('unsupported format') + } + return mind + } + get_data(data_format) { + var data = null + if (data_format == 'node_array') { + data = format.node_array.get_data(this.jm.mind) + } else if (data_format == 'node_tree') { + data = format.node_tree.get_data(this.jm.mind) + } else if (data_format == 'freemind') { + data = format.freemind.get_data(this.jm.mind) + } else if (data_format == 'text') { + data = format.text.get_data(this.jm.mind) + } else { + logger.error('unsupported ' + data_format + ' format') + } + return data + } +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.dom.js b/app/assets/javascripts/mind_map/jsmind/jsmind.dom.js old mode 100644 new mode 100755 index 6a748c1..18c7935 --- a/app/assets/javascripts/mind_map/jsmind/jsmind.dom.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.dom.js @@ -1,49 +1,49 @@ -class Dom { - constructor(w) { - this.w = w - this.d = w.document - this.g = function (id) { - return this.d.getElementById(id) - } - this.c = function (tag) { - return this.d.createElement(tag) - } - this.t = function (n, t) { - if (n.hasChildNodes()) { - n.firstChild.nodeValue = t - } else { - n.appendChild(this.d.createTextNode(t)) - } - } - - this.h = function (n, t) { - if (t instanceof HTMLElement) { - n.innerHTML = '' - n.appendChild(t) - } else { - n.innerHTML = t - } - } - // detect isElement - this.i = function (el) { - return ( - !!el && - typeof el === 'object' && - el.nodeType === 1 && - typeof el.style === 'object' && - typeof el.ownerDocument === 'object' - ) - } - - //target,eventType,handler - this.on = function (t, e, h) { - if (!!t.addEventListener) { - t.addEventListener(e, h, false) - } else { - t.attachEvent('on' + e, h) - } - } - } -} - -export const $ = new Dom(window) +class Dom { + constructor(w) { + this.w = w + this.d = w.document + this.g = function (id) { + return this.d.getElementById(id) + } + this.c = function (tag) { + return this.d.createElement(tag) + } + this.t = function (n, t) { + if (n.hasChildNodes()) { + n.firstChild.nodeValue = t + } else { + n.appendChild(this.d.createTextNode(t)) + } + } + + this.h = function (n, t) { + if (t instanceof HTMLElement) { + n.innerHTML = '' + n.appendChild(t) + } else { + n.innerHTML = t + } + } + // detect isElement + this.i = function (el) { + return ( + !!el && + typeof el === 'object' && + el.nodeType === 1 && + typeof el.style === 'object' && + typeof el.ownerDocument === 'object' + ) + } + + //target,eventType,handler + this.on = function (t, e, h) { + if (!!t.addEventListener) { + t.addEventListener(e, h, false) + } else { + t.attachEvent('on' + e, h) + } + } + } +} + +export const $ = new Dom(window) diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.format.js b/app/assets/javascripts/mind_map/jsmind/jsmind.format.js old mode 100644 new mode 100755 index dbdb750..5b506a5 --- a/app/assets/javascripts/mind_map/jsmind/jsmind.format.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.format.js @@ -1,533 +1,533 @@ -import { __author__, __version__, logger, Direction } from './jsmind.common.js' -import { Mind } from './jsmind.mind.js' -import { Node } from './jsmind.node.js' -import { util } from './jsmind.util.js' - -const DEFAULT_META = { name: 'jsMind', author: __author__, version: __version__ } - -export const format = { - node_tree: { - example: { - meta: DEFAULT_META, - format: 'node_tree', - data: { id: 'root', topic: 'jsMind node_tree example' }, - }, - get_mind: function (source) { - var df = format.node_tree - var mind = new Mind() - mind.name = source.meta.name - mind.author = source.meta.author - mind.version = source.meta.version - df._parse(mind, source.data) - return mind - }, - get_data: function (mind) { - var df = format.node_tree - var json = {} - json.meta = { - name: mind.name, - author: mind.author, - version: mind.version, - } - json.format = 'node_tree' - json.data = df._build_node(mind.root) - return json - }, - - _parse: function (mind, node_root) { - var df = format.node_tree - var data = df._extract_data(node_root) - mind.set_root(node_root.id, node_root.topic, data) - if ('children' in node_root) { - var children = node_root.children - for (var i = 0; i < children.length; i++) { - df._extract_subnode(mind, mind.root, children[i]) - } - } - }, - - _extract_data: function (node_json) { - var data = {} - for (var k in node_json) { - if ( - k == 'id' || - k == 'topic' || - k == 'children' || - k == 'direction' || - k == 'expanded' - ) { - continue - } - data[k] = node_json[k] - } - return data - }, - - _extract_subnode: function (mind, node_parent, node_json) { - var df = format.node_tree - var data = df._extract_data(node_json) - var d = null - if (node_parent.isroot) { - d = node_json.direction == 'left' ? Direction.left : Direction.right - } - var node = mind.add_node( - node_parent, - node_json.id, - node_json.topic, - data, - d, - node_json.expanded - ) - if (!!node_json['children']) { - var children = node_json.children - for (var i = 0; i < children.length; i++) { - df._extract_subnode(mind, node, children[i]) - } - } - }, - - _build_node: function (node) { - var df = format.node_tree - if (!(node instanceof Node)) { - return - } - var o = { - id: node.id, - topic: node.topic, - expanded: node.expanded, - } - if (!!node.parent && node.parent.isroot) { - o.direction = node.direction == Direction.left ? 'left' : 'right' - } - if (node.data != null) { - var node_data = node.data - for (var k in node_data) { - o[k] = node_data[k] - } - } - var children = node.children - if (children.length > 0) { - o.children = [] - for (var i = 0; i < children.length; i++) { - o.children.push(df._build_node(children[i])) - } - } - return o - }, - }, - - node_array: { - example: { - meta: DEFAULT_META, - format: 'node_array', - data: [{ id: 'root', topic: 'jsMind node_array example', isroot: true }], - }, - - get_mind: function (source) { - var df = format.node_array - var mind = new Mind() - mind.name = source.meta.name - mind.author = source.meta.author - mind.version = source.meta.version - df._parse(mind, source.data) - return mind - }, - - get_data: function (mind) { - var df = format.node_array - var json = {} - json.meta = { - name: mind.name, - author: mind.author, - version: mind.version, - } - json.format = 'node_array' - json.data = [] - df._array(mind, json.data) - return json - }, - - _parse: function (mind, node_array) { - var df = format.node_array - var nodes = node_array.slice(0) - // reverse array for improving looping performance - nodes.reverse() - var root_node = df._extract_root(mind, nodes) - if (!!root_node) { - df._extract_subnode(mind, root_node, nodes) - } else { - logger.error('root node can not be found') - } - }, - - _extract_root: function (mind, node_array) { - var df = format.node_array - var i = node_array.length - while (i--) { - if ('isroot' in node_array[i] && node_array[i].isroot) { - var root_json = node_array[i] - var data = df._extract_data(root_json) - var node = mind.set_root(root_json.id, root_json.topic, data) - node_array.splice(i, 1) - return node - } - } - return null - }, - - _extract_subnode: function (mind, parent_node, node_array) { - var df = format.node_array - var i = node_array.length - var node_json = null - var data = null - var extract_count = 0 - while (i--) { - node_json = node_array[i] - if (node_json.parentid == parent_node.id) { - data = df._extract_data(node_json) - var d = null - var node_direction = node_json.direction - if (!!node_direction) { - d = node_direction == 'left' ? Direction.left : Direction.right - } - var node = mind.add_node( - parent_node, - node_json.id, - node_json.topic, - data, - d, - node_json.expanded - ) - node_array.splice(i, 1) - extract_count++ - var sub_extract_count = df._extract_subnode(mind, node, node_array) - if (sub_extract_count > 0) { - // reset loop index after extract subordinate node - i = node_array.length - extract_count += sub_extract_count - } - } - } - return extract_count - }, - - _extract_data: function (node_json) { - var data = {} - for (var k in node_json) { - if ( - k == 'id' || - k == 'topic' || - k == 'parentid' || - k == 'isroot' || - k == 'direction' || - k == 'expanded' - ) { - continue - } - data[k] = node_json[k] - } - return data - }, - - _array: function (mind, node_array) { - var df = format.node_array - df._array_node(mind.root, node_array) - }, - - _array_node: function (node, node_array) { - var df = format.node_array - if (!(node instanceof Node)) { - return - } - var o = { - id: node.id, - topic: node.topic, - expanded: node.expanded, - } - if (!!node.parent) { - o.parentid = node.parent.id - } - if (node.isroot) { - o.isroot = true - } - if (!!node.parent && node.parent.isroot) { - o.direction = node.direction == Direction.left ? 'left' : 'right' - } - if (node.data != null) { - var node_data = node.data - for (var k in node_data) { - o[k] = node_data[k] - } - } - node_array.push(o) - var ci = node.children.length - for (var i = 0; i < ci; i++) { - df._array_node(node.children[i], node_array) - } - }, - }, - - freemind: { - example: { - meta: DEFAULT_META, - format: 'freemind', - data: '', - }, - get_mind: function (source) { - var df = format.freemind - var mind = new Mind() - mind.name = source.meta.name - mind.author = source.meta.author - mind.version = source.meta.version - var xml = source.data - var xml_doc = df._parse_xml(xml) - var xml_root = df._find_root(xml_doc) - df._load_node(mind, null, xml_root) - return mind - }, - - get_data: function (mind) { - var df = format.freemind - var json = {} - json.meta = { - name: mind.name, - author: mind.author, - version: mind.version, - } - json.format = 'freemind' - var xml_lines = [] - xml_lines.push('') - df._build_map(mind.root, xml_lines) - xml_lines.push('') - json.data = xml_lines.join('') - return json - }, - - _parse_xml: function (xml) { - var xml_doc = null - if (window.DOMParser) { - var parser = new DOMParser() - xml_doc = parser.parseFromString(xml, 'text/xml') - } else { - // Internet Explorer - xml_doc = new ActiveXObject('Microsoft.XMLDOM') - xml_doc.async = false - xml_doc.loadXML(xml) - } - return xml_doc - }, - - _find_root: function (xml_doc) { - var nodes = xml_doc.childNodes - var node = null - var root = null - var n = null - for (var i = 0; i < nodes.length; i++) { - n = nodes[i] - if (n.nodeType == 1 && n.tagName == 'map') { - node = n - break - } - } - if (!!node) { - var ns = node.childNodes - node = null - for (var i = 0; i < ns.length; i++) { - n = ns[i] - if (n.nodeType == 1 && n.tagName == 'node') { - node = n - break - } - } - } - return node - }, - - _load_node: function (mind, parent_node, xml_node) { - var df = format.freemind - var node_id = xml_node.getAttribute('ID') - var node_topic = xml_node.getAttribute('TEXT') - var node_folded = xml_node.getAttribute('FOLDED') - // look for richcontent - if (node_topic == null) { - var topic_children = xml_node.childNodes - var topic_child = null - for (var i = 0; i < topic_children.length; i++) { - topic_child = topic_children[i] - if (topic_child.nodeType == 1 && topic_child.tagName === 'richcontent') { - node_topic = topic_child.textContent - break - } - } - } - var node_data = df._load_attributes(xml_node) - var node_expanded = - 'expanded' in node_data ? node_data.expanded == 'true' : node_folded != 'true' - delete node_data.expanded - - var node_position = xml_node.getAttribute('POSITION') - var node_direction = null - if (!!node_position) { - node_direction = node_position == 'left' ? Direction.left : Direction.right - } - var node = null - if (!!parent_node) { - node = mind.add_node( - parent_node, - node_id, - node_topic, - node_data, - node_direction, - node_expanded - ) - } else { - node = mind.set_root(node_id, node_topic, node_data) - } - var children = xml_node.childNodes - var child = null - for (var i = 0; i < children.length; i++) { - child = children[i] - if (child.nodeType == 1 && child.tagName == 'node') { - df._load_node(mind, node, child) - } - } - }, - - _load_attributes: function (xml_node) { - var children = xml_node.childNodes - var attr = null - var attr_data = {} - for (var i = 0; i < children.length; i++) { - attr = children[i] - if (attr.nodeType == 1 && attr.tagName === 'attribute') { - attr_data[attr.getAttribute('NAME')] = attr.getAttribute('VALUE') - } - } - return attr_data - }, - - _build_map: function (node, xml_lines) { - var df = format.freemind - var pos = null - if (!!node.parent && node.parent.isroot) { - pos = node.direction === Direction.left ? 'left' : 'right' - } - xml_lines.push('') - - // for attributes - var node_data = node.data - if (node_data != null) { - for (var k in node_data) { - xml_lines.push('') - } - } - - // for children - var children = node.children - for (var i = 0; i < children.length; i++) { - df._build_map(children[i], xml_lines) - } - - xml_lines.push('') - }, - - _escape: function (text) { - return text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/'/g, ''') - .replace(/"/g, '"') - }, - }, - text: { - example: { - meta: DEFAULT_META, - format: 'text', - data: 'jsMind text example\n node1\n node1-sub\n node1-sub\n node2', - }, - _line_regex: /\s*/, - get_mind: function (source) { - var df = format.text - var mind = new Mind() - mind.name = source.meta.name - mind.author = source.meta.author - mind.version = source.meta.version - var lines = source.data.split(/\n|\r/) - df._fill_nodes(mind, lines, 0, 0) - return mind - }, - - _fill_nodes: function (mind, lines) { - let node_path = [] - let i = 0 - while (i < lines.length) { - let line = lines[i] - let level = line.match(/\s*/)[0].length - let topic = line.substr(level) - - if (level == 0 && node_path.length > 0) { - log.error('more than 1 root node was found: ' + topic) - return - } - if (level > node_path.length) { - log.error('a suspended node was found: ' + topic) - return - } - let diff = node_path.length - level - while (diff--) { - node_path.pop() - } - - if (level == 0 && node_path.length == 0) { - let node = mind.set_root(util.uuid.newid(), topic) - node_path.push(node) - } else { - let node = mind.add_node( - node_path[level - 1], - util.uuid.newid(), - topic, - {}, - null - ) - node_path.push(node) - } - i++ - } - node_path.length = 0 - }, - - get_data: function (mind) { - var df = format.text - var json = {} - json.meta = { - name: mind.name, - author: mind.author, - version: mind.version, - } - json.format = 'text' - let lines = [] - df._build_lines(lines, [mind.root], 0) - json.data = lines.join('\n') - return json - }, - - _build_lines: function (lines, nodes, level) { - let prefix = new Array(level + 1).join(' ') - for (let node of nodes) { - lines.push(prefix + node.topic) - if (!!node.children) { - format.text._build_lines(lines, node.children, level + 1) - } - } - }, - }, -} +import { __author__, __version__, logger, Direction } from './jsmind.common.js' +import { Mind } from './jsmind.mind.js' +import { Node } from './jsmind.node.js' +import { util } from './jsmind.util.js' + +const DEFAULT_META = { name: 'jsMind', author: __author__, version: __version__ } + +export const format = { + node_tree: { + example: { + meta: DEFAULT_META, + format: 'node_tree', + data: { id: 'root', topic: 'jsMind node_tree example' }, + }, + get_mind: function (source) { + var df = format.node_tree + var mind = new Mind() + mind.name = source.meta.name + mind.author = source.meta.author + mind.version = source.meta.version + df._parse(mind, source.data) + return mind + }, + get_data: function (mind) { + var df = format.node_tree + var json = {} + json.meta = { + name: mind.name, + author: mind.author, + version: mind.version, + } + json.format = 'node_tree' + json.data = df._build_node(mind.root) + return json + }, + + _parse: function (mind, node_root) { + var df = format.node_tree + var data = df._extract_data(node_root) + mind.set_root(node_root.id, node_root.topic, data) + if ('children' in node_root) { + var children = node_root.children + for (var i = 0; i < children.length; i++) { + df._extract_subnode(mind, mind.root, children[i]) + } + } + }, + + _extract_data: function (node_json) { + var data = {} + for (var k in node_json) { + if ( + k == 'id' || + k == 'topic' || + k == 'children' || + k == 'direction' || + k == 'expanded' + ) { + continue + } + data[k] = node_json[k] + } + return data + }, + + _extract_subnode: function (mind, node_parent, node_json) { + var df = format.node_tree + var data = df._extract_data(node_json) + var d = null + if (node_parent.isroot) { + d = node_json.direction == 'left' ? Direction.left : Direction.right + } + var node = mind.add_node( + node_parent, + node_json.id, + node_json.topic, + data, + d, + node_json.expanded + ) + if (!!node_json['children']) { + var children = node_json.children + for (var i = 0; i < children.length; i++) { + df._extract_subnode(mind, node, children[i]) + } + } + }, + + _build_node: function (node) { + var df = format.node_tree + if (!(node instanceof Node)) { + return + } + var o = { + id: node.id, + topic: node.topic, + expanded: node.expanded, + } + if (!!node.parent && node.parent.isroot) { + o.direction = node.direction == Direction.left ? 'left' : 'right' + } + if (node.data != null) { + var node_data = node.data + for (var k in node_data) { + o[k] = node_data[k] + } + } + var children = node.children + if (children.length > 0) { + o.children = [] + for (var i = 0; i < children.length; i++) { + o.children.push(df._build_node(children[i])) + } + } + return o + }, + }, + + node_array: { + example: { + meta: DEFAULT_META, + format: 'node_array', + data: [{ id: 'root', topic: 'jsMind node_array example', isroot: true }], + }, + + get_mind: function (source) { + var df = format.node_array + var mind = new Mind() + mind.name = source.meta.name + mind.author = source.meta.author + mind.version = source.meta.version + df._parse(mind, source.data) + return mind + }, + + get_data: function (mind) { + var df = format.node_array + var json = {} + json.meta = { + name: mind.name, + author: mind.author, + version: mind.version, + } + json.format = 'node_array' + json.data = [] + df._array(mind, json.data) + return json + }, + + _parse: function (mind, node_array) { + var df = format.node_array + var nodes = node_array.slice(0) + // reverse array for improving looping performance + nodes.reverse() + var root_node = df._extract_root(mind, nodes) + if (!!root_node) { + df._extract_subnode(mind, root_node, nodes) + } else { + logger.error('root node can not be found') + } + }, + + _extract_root: function (mind, node_array) { + var df = format.node_array + var i = node_array.length + while (i--) { + if ('isroot' in node_array[i] && node_array[i].isroot) { + var root_json = node_array[i] + var data = df._extract_data(root_json) + var node = mind.set_root(root_json.id, root_json.topic, data) + node_array.splice(i, 1) + return node + } + } + return null + }, + + _extract_subnode: function (mind, parent_node, node_array) { + var df = format.node_array + var i = node_array.length + var node_json = null + var data = null + var extract_count = 0 + while (i--) { + node_json = node_array[i] + if (node_json.parentid == parent_node.id) { + data = df._extract_data(node_json) + var d = null + var node_direction = node_json.direction + if (!!node_direction) { + d = node_direction == 'left' ? Direction.left : Direction.right + } + var node = mind.add_node( + parent_node, + node_json.id, + node_json.topic, + data, + d, + node_json.expanded + ) + node_array.splice(i, 1) + extract_count++ + var sub_extract_count = df._extract_subnode(mind, node, node_array) + if (sub_extract_count > 0) { + // reset loop index after extract subordinate node + i = node_array.length + extract_count += sub_extract_count + } + } + } + return extract_count + }, + + _extract_data: function (node_json) { + var data = {} + for (var k in node_json) { + if ( + k == 'id' || + k == 'topic' || + k == 'parentid' || + k == 'isroot' || + k == 'direction' || + k == 'expanded' + ) { + continue + } + data[k] = node_json[k] + } + return data + }, + + _array: function (mind, node_array) { + var df = format.node_array + df._array_node(mind.root, node_array) + }, + + _array_node: function (node, node_array) { + var df = format.node_array + if (!(node instanceof Node)) { + return + } + var o = { + id: node.id, + topic: node.topic, + expanded: node.expanded, + } + if (!!node.parent) { + o.parentid = node.parent.id + } + if (node.isroot) { + o.isroot = true + } + if (!!node.parent && node.parent.isroot) { + o.direction = node.direction == Direction.left ? 'left' : 'right' + } + if (node.data != null) { + var node_data = node.data + for (var k in node_data) { + o[k] = node_data[k] + } + } + node_array.push(o) + var ci = node.children.length + for (var i = 0; i < ci; i++) { + df._array_node(node.children[i], node_array) + } + }, + }, + + freemind: { + example: { + meta: DEFAULT_META, + format: 'freemind', + data: '', + }, + get_mind: function (source) { + var df = format.freemind + var mind = new Mind() + mind.name = source.meta.name + mind.author = source.meta.author + mind.version = source.meta.version + var xml = source.data + var xml_doc = df._parse_xml(xml) + var xml_root = df._find_root(xml_doc) + df._load_node(mind, null, xml_root) + return mind + }, + + get_data: function (mind) { + var df = format.freemind + var json = {} + json.meta = { + name: mind.name, + author: mind.author, + version: mind.version, + } + json.format = 'freemind' + var xml_lines = [] + xml_lines.push('') + df._build_map(mind.root, xml_lines) + xml_lines.push('') + json.data = xml_lines.join('') + return json + }, + + _parse_xml: function (xml) { + var xml_doc = null + if (window.DOMParser) { + var parser = new DOMParser() + xml_doc = parser.parseFromString(xml, 'text/xml') + } else { + // Internet Explorer + xml_doc = new ActiveXObject('Microsoft.XMLDOM') + xml_doc.async = false + xml_doc.loadXML(xml) + } + return xml_doc + }, + + _find_root: function (xml_doc) { + var nodes = xml_doc.childNodes + var node = null + var root = null + var n = null + for (var i = 0; i < nodes.length; i++) { + n = nodes[i] + if (n.nodeType == 1 && n.tagName == 'map') { + node = n + break + } + } + if (!!node) { + var ns = node.childNodes + node = null + for (var i = 0; i < ns.length; i++) { + n = ns[i] + if (n.nodeType == 1 && n.tagName == 'node') { + node = n + break + } + } + } + return node + }, + + _load_node: function (mind, parent_node, xml_node) { + var df = format.freemind + var node_id = xml_node.getAttribute('ID') + var node_topic = xml_node.getAttribute('TEXT') + var node_folded = xml_node.getAttribute('FOLDED') + // look for richcontent + if (node_topic == null) { + var topic_children = xml_node.childNodes + var topic_child = null + for (var i = 0; i < topic_children.length; i++) { + topic_child = topic_children[i] + if (topic_child.nodeType == 1 && topic_child.tagName === 'richcontent') { + node_topic = topic_child.textContent + break + } + } + } + var node_data = df._load_attributes(xml_node) + var node_expanded = + 'expanded' in node_data ? node_data.expanded == 'true' : node_folded != 'true' + delete node_data.expanded + + var node_position = xml_node.getAttribute('POSITION') + var node_direction = null + if (!!node_position) { + node_direction = node_position == 'left' ? Direction.left : Direction.right + } + var node = null + if (!!parent_node) { + node = mind.add_node( + parent_node, + node_id, + node_topic, + node_data, + node_direction, + node_expanded + ) + } else { + node = mind.set_root(node_id, node_topic, node_data) + } + var children = xml_node.childNodes + var child = null + for (var i = 0; i < children.length; i++) { + child = children[i] + if (child.nodeType == 1 && child.tagName == 'node') { + df._load_node(mind, node, child) + } + } + }, + + _load_attributes: function (xml_node) { + var children = xml_node.childNodes + var attr = null + var attr_data = {} + for (var i = 0; i < children.length; i++) { + attr = children[i] + if (attr.nodeType == 1 && attr.tagName === 'attribute') { + attr_data[attr.getAttribute('NAME')] = attr.getAttribute('VALUE') + } + } + return attr_data + }, + + _build_map: function (node, xml_lines) { + var df = format.freemind + var pos = null + if (!!node.parent && node.parent.isroot) { + pos = node.direction === Direction.left ? 'left' : 'right' + } + xml_lines.push('') + + // for attributes + var node_data = node.data + if (node_data != null) { + for (var k in node_data) { + xml_lines.push('') + } + } + + // for children + var children = node.children + for (var i = 0; i < children.length; i++) { + df._build_map(children[i], xml_lines) + } + + xml_lines.push('') + }, + + _escape: function (text) { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/'/g, ''') + .replace(/"/g, '"') + }, + }, + text: { + example: { + meta: DEFAULT_META, + format: 'text', + data: 'jsMind text example\n node1\n node1-sub\n node1-sub\n node2', + }, + _line_regex: /\s*/, + get_mind: function (source) { + var df = format.text + var mind = new Mind() + mind.name = source.meta.name + mind.author = source.meta.author + mind.version = source.meta.version + var lines = source.data.split(/\n|\r/) + df._fill_nodes(mind, lines, 0, 0) + return mind + }, + + _fill_nodes: function (mind, lines) { + let node_path = [] + let i = 0 + while (i < lines.length) { + let line = lines[i] + let level = line.match(/\s*/)[0].length + let topic = line.substr(level) + + if (level == 0 && node_path.length > 0) { + log.error('more than 1 root node was found: ' + topic) + return + } + if (level > node_path.length) { + log.error('a suspended node was found: ' + topic) + return + } + let diff = node_path.length - level + while (diff--) { + node_path.pop() + } + + if (level == 0 && node_path.length == 0) { + let node = mind.set_root(util.uuid.newid(), topic) + node_path.push(node) + } else { + let node = mind.add_node( + node_path[level - 1], + util.uuid.newid(), + topic, + {}, + null + ) + node_path.push(node) + } + i++ + } + node_path.length = 0 + }, + + get_data: function (mind) { + var df = format.text + var json = {} + json.meta = { + name: mind.name, + author: mind.author, + version: mind.version, + } + json.format = 'text' + let lines = [] + df._build_lines(lines, [mind.root], 0) + json.data = lines.join('\n') + return json + }, + + _build_lines: function (lines, nodes, level) { + let prefix = new Array(level + 1).join(' ') + for (let node of nodes) { + lines.push(prefix + node.topic) + if (!!node.children) { + format.text._build_lines(lines, node.children, level + 1) + } + } + }, + }, +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.graph.js b/app/assets/javascripts/mind_map/jsmind/jsmind.graph.js old mode 100644 new mode 100755 index 47313c2..4ce2856 --- a/app/assets/javascripts/mind_map/jsmind/jsmind.graph.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.graph.js @@ -1,179 +1,179 @@ -import { $ } from './jsmind.dom.js' -import { logger } from './jsmind.common.js' - -class SvgGraph { - constructor(view) { - this.view = view - this.opts = view.opts - this.e_svg = SvgGraph.c('svg') - this.e_svg.setAttribute('class', 'jsmind') - this.size = { w: 0, h: 0 } - this.lines = [] - this.line_drawing = { - straight: this._line_to, - curved: this._bezier_to, - } - this.init_line_render() - } - static c(tag) { - return $.d.createElementNS('http://www.w3.org/2000/svg', tag) - } - init_line_render() { - if (typeof this.opts.custom_line_render === 'function') { - this.drawing = (path, x1, y1, x2, y2) => { - try { - this.opts.custom_line_render.call(this, { - ctx: path, - start_point: { x: x1, y: y1 }, - end_point: { x: x2, y: y2 }, - }) - } catch (e) { - logger.error('custom line renderer error: ', e) - } - } - } else { - this.drawing = this.line_drawing[this.opts.line_style] || this.line_drawing.curved - } - } - element() { - return this.e_svg - } - set_size(w, h) { - this.size.w = w - this.size.h = h - this.e_svg.setAttribute('width', w) - this.e_svg.setAttribute('height', h) - } - clear() { - var len = this.lines.length - while (len--) { - this.e_svg.removeChild(this.lines[len]) - } - this.lines.length = 0 - } - draw_line(pout, pin, offset, color) { - var line = SvgGraph.c('path') - line.setAttribute('stroke', color || this.opts.line_color) - line.setAttribute('stroke-width', this.opts.line_width) - line.setAttribute('fill', 'transparent') - this.lines.push(line) - this.e_svg.appendChild(line) - this.drawing(line, pin.x + offset.x, pin.y + offset.y, pout.x + offset.x, pout.y + offset.y) - } - - copy_to(dest_canvas_ctx, callback) { - var img = new Image() - img.onload = function () { - dest_canvas_ctx.drawImage(img, 0, 0) - !!callback && callback() - } - img.src = - 'data:image/svg+xml;base64,' + btoa(new XMLSerializer().serializeToString(this.e_svg)) - } - _bezier_to(path, x1, y1, x2, y2) { - path.setAttribute( - 'd', - 'M ' + - x1 + - ' ' + - y1 + - ' C ' + - (x1 + ((x2 - x1) * 2) / 3) + - ' ' + - y1 + - ', ' + - x1 + - ' ' + - y2 + - ', ' + - x2 + - ' ' + - y2 - ) - } - _line_to(path, x1, y1, x2, y2) { - path.setAttribute('d', 'M ' + x1 + ' ' + y1 + ' L ' + x2 + ' ' + y2) - } -} - -class CanvasGraph { - constructor(view) { - this.opts = view.opts - this.e_canvas = $.c('canvas') - this.e_canvas.className = 'jsmind' - this.canvas_ctx = this.e_canvas.getContext('2d') - this.size = { w: 0, h: 0 } - this.line_drawing = { - straight: this._line_to, - curved: this._bezier_to, - } - this.dpr = view.device_pixel_ratio - this.init_line_render() - } - init_line_render() { - if (typeof this.opts.custom_line_render === 'function') { - this.drawing = (ctx, x1, y1, x2, y2) => { - try { - this.opts.custom_line_render.call(this, { - ctx, - start_point: { x: x1, y: y1 }, - end_point: { x: x2, y: y2 }, - }) - } catch (e) { - logger.error('custom line render error: ', e) - } - } - } else { - this.drawing = this.line_drawing[this.opts.line_style] || this.line_drawing.curved - } - } - element() { - return this.e_canvas - } - set_size(w, h) { - this.size.w = w - this.size.h = h - if (this.e_canvas.width && this.e_canvas.height && this.canvas_ctx.scale) { - this.e_canvas.width = w * this.dpr - this.e_canvas.height = h * this.dpr - - this.e_canvas.style.width = w + 'px' - this.e_canvas.style.height = h + 'px' - this.canvas_ctx.scale(this.dpr, this.dpr) - } else { - this.e_canvas.width = w - this.e_canvas.height = h - } - } - - clear() { - this.canvas_ctx.clearRect(0, 0, this.size.w, this.size.h) - } - draw_line(pout, pin, offset, color) { - var ctx = this.canvas_ctx - ctx.strokeStyle = color || this.opts.line_color - ctx.lineWidth = this.opts.line_width - ctx.lineCap = 'round' - this.drawing(ctx, pin.x + offset.x, pin.y + offset.y, pout.x + offset.x, pout.y + offset.y) - } - copy_to(dest_canvas_ctx, callback) { - dest_canvas_ctx.drawImage(this.e_canvas, 0, 0, this.size.w, this.size.h) - !!callback && callback() - } - _bezier_to(ctx, x1, y1, x2, y2) { - ctx.beginPath() - ctx.moveTo(x1, y1) - ctx.bezierCurveTo(x1 + ((x2 - x1) * 2) / 3, y1, x1, y2, x2, y2) - ctx.stroke() - } - _line_to(ctx, x1, y1, x2, y2) { - ctx.beginPath() - ctx.moveTo(x1, y1) - ctx.lineTo(x2, y2) - ctx.stroke() - } -} - -export function init_graph(view, engine) { - return engine.toLowerCase() === 'svg' ? new SvgGraph(view) : new CanvasGraph(view) -} +import { $ } from './jsmind.dom.js' +import { logger } from './jsmind.common.js' + +class SvgGraph { + constructor(view) { + this.view = view + this.opts = view.opts + this.e_svg = SvgGraph.c('svg') + this.e_svg.setAttribute('class', 'jsmind') + this.size = { w: 0, h: 0 } + this.lines = [] + this.line_drawing = { + straight: this._line_to, + curved: this._bezier_to, + } + this.init_line_render() + } + static c(tag) { + return $.d.createElementNS('http://www.w3.org/2000/svg', tag) + } + init_line_render() { + if (typeof this.opts.custom_line_render === 'function') { + this.drawing = (path, x1, y1, x2, y2) => { + try { + this.opts.custom_line_render.call(this, { + ctx: path, + start_point: { x: x1, y: y1 }, + end_point: { x: x2, y: y2 }, + }) + } catch (e) { + logger.error('custom line renderer error: ', e) + } + } + } else { + this.drawing = this.line_drawing[this.opts.line_style] || this.line_drawing.curved + } + } + element() { + return this.e_svg + } + set_size(w, h) { + this.size.w = w + this.size.h = h + this.e_svg.setAttribute('width', w) + this.e_svg.setAttribute('height', h) + } + clear() { + var len = this.lines.length + while (len--) { + this.e_svg.removeChild(this.lines[len]) + } + this.lines.length = 0 + } + draw_line(pout, pin, offset, color) { + var line = SvgGraph.c('path') + line.setAttribute('stroke', color || this.opts.line_color) + line.setAttribute('stroke-width', this.opts.line_width) + line.setAttribute('fill', 'transparent') + this.lines.push(line) + this.e_svg.appendChild(line) + this.drawing(line, pin.x + offset.x, pin.y + offset.y, pout.x + offset.x, pout.y + offset.y) + } + + copy_to(dest_canvas_ctx, callback) { + var img = new Image() + img.onload = function () { + dest_canvas_ctx.drawImage(img, 0, 0) + !!callback && callback() + } + img.src = + 'data:image/svg+xml;base64,' + btoa(new XMLSerializer().serializeToString(this.e_svg)) + } + _bezier_to(path, x1, y1, x2, y2) { + path.setAttribute( + 'd', + 'M ' + + x1 + + ' ' + + y1 + + ' C ' + + (x1 + ((x2 - x1) * 2) / 3) + + ' ' + + y1 + + ', ' + + x1 + + ' ' + + y2 + + ', ' + + x2 + + ' ' + + y2 + ) + } + _line_to(path, x1, y1, x2, y2) { + path.setAttribute('d', 'M ' + x1 + ' ' + y1 + ' L ' + x2 + ' ' + y2) + } +} + +class CanvasGraph { + constructor(view) { + this.opts = view.opts + this.e_canvas = $.c('canvas') + this.e_canvas.className = 'jsmind' + this.canvas_ctx = this.e_canvas.getContext('2d') + this.size = { w: 0, h: 0 } + this.line_drawing = { + straight: this._line_to, + curved: this._bezier_to, + } + this.dpr = view.device_pixel_ratio + this.init_line_render() + } + init_line_render() { + if (typeof this.opts.custom_line_render === 'function') { + this.drawing = (ctx, x1, y1, x2, y2) => { + try { + this.opts.custom_line_render.call(this, { + ctx, + start_point: { x: x1, y: y1 }, + end_point: { x: x2, y: y2 }, + }) + } catch (e) { + logger.error('custom line render error: ', e) + } + } + } else { + this.drawing = this.line_drawing[this.opts.line_style] || this.line_drawing.curved + } + } + element() { + return this.e_canvas + } + set_size(w, h) { + this.size.w = w + this.size.h = h + if (this.e_canvas.width && this.e_canvas.height && this.canvas_ctx.scale) { + this.e_canvas.width = w * this.dpr + this.e_canvas.height = h * this.dpr + + this.e_canvas.style.width = w + 'px' + this.e_canvas.style.height = h + 'px' + this.canvas_ctx.scale(this.dpr, this.dpr) + } else { + this.e_canvas.width = w + this.e_canvas.height = h + } + } + + clear() { + this.canvas_ctx.clearRect(0, 0, this.size.w, this.size.h) + } + draw_line(pout, pin, offset, color) { + var ctx = this.canvas_ctx + ctx.strokeStyle = color || this.opts.line_color + ctx.lineWidth = this.opts.line_width + ctx.lineCap = 'round' + this.drawing(ctx, pin.x + offset.x, pin.y + offset.y, pout.x + offset.x, pout.y + offset.y) + } + copy_to(dest_canvas_ctx, callback) { + dest_canvas_ctx.drawImage(this.e_canvas, 0, 0, this.size.w, this.size.h) + !!callback && callback() + } + _bezier_to(ctx, x1, y1, x2, y2) { + ctx.beginPath() + ctx.moveTo(x1, y1) + ctx.bezierCurveTo(x1 + ((x2 - x1) * 2) / 3, y1, x1, y2, x2, y2) + ctx.stroke() + } + _line_to(ctx, x1, y1, x2, y2) { + ctx.beginPath() + ctx.moveTo(x1, y1) + ctx.lineTo(x2, y2) + ctx.stroke() + } +} + +export function init_graph(view, engine) { + return engine.toLowerCase() === 'svg' ? new SvgGraph(view) : new CanvasGraph(view) +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.js b/app/assets/javascripts/mind_map/jsmind/jsmind.js old mode 100644 new mode 100755 index a8b46af..f3cd37e --- a/app/assets/javascripts/mind_map/jsmind/jsmind.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.js @@ -1,755 +1,755 @@ -import { - __version__, - logger, - EventType, - Direction, - LogLevel, -} from './jsmind.common.js' -import { merge_option } from './jsmind.option.js' -import { Mind } from './jsmind.mind.js' -import { Node } from './jsmind.node.js' -import { DataProvider } from './jsmind.data_provider.js' -import { LayoutProvider } from './jsmind.layout_provider.js' -import { ViewProvider } from './jsmind.view_provider.js' -import { ShortcutProvider } from './jsmind.shortcut_provider.js' -import { - Plugin, - register as _register_plugin, - apply as apply_plugins, -} from './jsmind.plugin.js' -import { format } from './jsmind.format.js' -import { $ } from './jsmind.dom.js' -import { util as _util } from './jsmind.util.js' - -export default class jsMind { - static mind = Mind - static node = Node - static direction = Direction - static event_type = EventType - static $ = $ - static plugin = Plugin - static register_plugin = _register_plugin - static util = _util - - constructor(options) { - jsMind.current = this - this.options = merge_option(options) - logger.level(LogLevel[this.options.log_level]) - this.version = __version__ - this.initialized = false - this.mind = null - this.event_handles = [] - this.init() - } - - init() { - if (!!this.initialized) { - return - } - this.initialized = true - var opts_layout = { - mode: this.options.mode, - hspace: this.options.layout.hspace, - vspace: this.options.layout.vspace, - pspace: this.options.layout.pspace, - cousin_space: this.options.layout.cousin_space, - } - var opts_view = { - container: this.options.container, - support_html: this.options.support_html, - engine: this.options.view.engine, - enable_device_pixel_ratio: - this.options.view.enable_device_pixel_ratio, - hmargin: this.options.view.hmargin, - vmargin: this.options.view.vmargin, - line_width: this.options.view.line_width, - line_color: this.options.view.line_color, - line_style: this.options.view.line_style, - custom_line_render: this.options.view.custom_line_render, - draggable: this.options.view.draggable, - hide_scrollbars_when_draggable: - this.options.view.hide_scrollbars_when_draggable, - node_overflow: this.options.view.node_overflow, - zoom: this.options.view.zoom, - custom_node_render: this.options.view.custom_node_render, - expander_style: this.options.view.expander_style, - } - // create instance of function provider - this.data = new DataProvider(this) - this.layout = new LayoutProvider(this, opts_layout) - this.view = new ViewProvider(this, opts_view) - this.shortcut = new ShortcutProvider(this, this.options.shortcut) - - this.data.init() - this.layout.init() - this.view.init() - this.shortcut.init() - - this._event_bind() - - apply_plugins(this, this.options.plugin) - } - get_editable() { - return this.options.editable - } - enable_edit() { - this.options.editable = true - } - disable_edit() { - this.options.editable = false - } - get_view_draggable() { - return this.options.view.draggable - } - enable_view_draggable() { - this.options.view.draggable = true - this.view.setup_canvas_draggable(true) - } - disable_view_draggable() { - this.options.view.draggable = false - this.view.setup_canvas_draggable(false) - } - // options are 'mousedown', 'click', 'dblclick', 'mousewheel' - enable_event_handle(event_handle) { - this.options.default_event_handle[ - 'enable_' + event_handle + '_handle' - ] = true - } - // options are 'mousedown', 'click', 'dblclick', 'mousewheel' - disable_event_handle(event_handle) { - this.options.default_event_handle[ - 'enable_' + event_handle + '_handle' - ] = false - } - set_theme(theme) { - var theme_old = this.options.theme - this.options.theme = !!theme ? theme : null - if (theme_old != this.options.theme) { - this.view.reset_theme() - this.view.reset_custom_style() - } - } - _event_bind() { - this.view.add_event(this, 'mousedown', this.mousedown_handle) - this.view.add_event(this, 'click', this.click_handle) - this.view.add_event(this, 'dblclick', this.dblclick_handle) - this.view.add_event(this, 'mousewheel', this.mousewheel_handle, true) - } - mousedown_handle(e) { - if (!this.options.default_event_handle['enable_mousedown_handle']) { - return - } - var element = e.target || event.srcElement - var node_id = this.view.get_binded_nodeid(element) - if (!!node_id) { - if (this.view.is_node(element)) { - this.select_node(node_id) - } - } else { - this.select_clear() - } - } - click_handle(e) { - if (!this.options.default_event_handle['enable_click_handle']) { - return - } - var element = e.target || event.srcElement - var is_expander = this.view.is_expander(element) - if (is_expander) { - var node_id = this.view.get_binded_nodeid(element) - if (!!node_id) { - this.toggle_node(node_id) - } - } - } - dblclick_handle(e) { - if (!this.options.default_event_handle['enable_dblclick_handle']) { - return - } - if (this.get_editable()) { - var element = e.target || event.srcElement - var is_node = this.view.is_node(element) - if (is_node) { - var node_id = this.view.get_binded_nodeid(element) - if (!!node_id) { - this.begin_edit(node_id) - } - } - } - } - // Use [Ctrl] + Mousewheel, to zoom in/out. - mousewheel_handle(e) { - // Test if mousewheel option is enabled and Ctrl key is pressed. - if ( - !this.options.default_event_handle['enable_mousewheel_handle'] || - !e.ctrlKey - ) { - return - } - var evt = e || event - // Avoid default page scrolling behavior. - evt.preventDefault() - - if (evt.deltaY < 0) { - this.view.zoom_in(evt) // wheel down - } else { - this.view.zoom_out(evt) - } - } - begin_edit(node) { - if (!Node.is_node(node)) { - var the_node = this.get_node(node) - if (!the_node) { - logger.error('the node[id=' + node + '] can not be found.') - return false - } else { - return this.begin_edit(the_node) - } - } - if (this.get_editable()) { - this.view.edit_node_begin(node) - } else { - logger.error('fail, this mind map is not editable.') - return - } - } - end_edit() { - this.view.edit_node_end() - } - toggle_node(node) { - if (!Node.is_node(node)) { - var the_node = this.get_node(node) - if (!the_node) { - logger.error('the node[id=' + node + '] can not be found.') - return - } else { - return this.toggle_node(the_node) - } - } - if (node.isroot) { - return - } - this.view.save_location(node) - this.layout.toggle_node(node) - this.view.relayout() - this.view.restore_location(node) - } - expand_node(node) { - if (!Node.is_node(node)) { - var the_node = this.get_node(node) - if (!the_node) { - logger.error('the node[id=' + node + '] can not be found.') - return - } else { - return this.expand_node(the_node) - } - } - if (node.isroot) { - return - } - this.view.save_location(node) - this.layout.expand_node(node) - this.view.relayout() - this.view.restore_location(node) - } - collapse_node(node) { - if (!Node.is_node(node)) { - var the_node = this.get_node(node) - if (!the_node) { - logger.error('the node[id=' + node + '] can not be found.') - return - } else { - return this.collapse_node(the_node) - } - } - if (node.isroot) { - return - } - this.view.save_location(node) - this.layout.collapse_node(node) - this.view.relayout() - this.view.restore_location(node) - } - expand_all() { - this.layout.expand_all() - this.view.relayout() - } - collapse_all() { - this.layout.collapse_all() - this.view.relayout() - } - expand_to_depth(depth) { - this.layout.expand_to_depth(depth) - this.view.relayout() - } - _reset() { - this.view.reset() - this.layout.reset() - this.data.reset() - } - _show(mind, skip_centering) { - var m = mind || format.node_array.example - this.mind = this.data.load(m) - if (!this.mind) { - logger.error('data.load error') - return - } else { - logger.debug('data.load ok') - } - - this.view.load() - logger.debug('view.load ok') - - this.layout.layout() - logger.debug('layout.layout ok') - - this.view.show(!skip_centering) - logger.debug('view.show ok') - - this.invoke_event_handle(EventType.show, { data: [mind] }) - } - show(mind, skip_centering) { - this._reset() - this._show(mind, skip_centering) - } - get_meta() { - return { - name: this.mind.name, - author: this.mind.author, - version: this.mind.version, - } - } - get_data(data_format) { - var df = data_format || 'node_tree' - return this.data.get_data(df) - } - get_root() { - return this.mind.root - } - get_node(node) { - if (Node.is_node(node)) { - return node - } - return this.mind.get_node(node) - } - add_node(parent_node, node_id, topic, data, direction) { - if (this.get_editable()) { - var the_parent_node = this.get_node(parent_node) - var dir = Direction.of(direction) - if (dir === undefined) { - dir = - this.layout.calculate_next_child_direction(the_parent_node) - } - var node = this.mind.add_node( - the_parent_node, - node_id, - topic, - data, - dir - ) - if (!!node) { - this.view.add_node(node) - this.layout.layout() - this.view.show(false) - this.view.reset_node_custom_style(node) - this.expand_node(the_parent_node) - this.invoke_event_handle(EventType.edit, { - evt: 'add_node', - data: [the_parent_node.id, node_id, topic, data, dir], - node: node_id, - }) - } - return node - } else { - logger.error('fail, this mind map is not editable') - return null - } - } - insert_node_before(node_before, node_id, topic, data, direction) { - if (this.get_editable()) { - var the_node_before = this.get_node(node_before) - var dir = Direction.of(direction) - if (dir === undefined) { - dir = this.layout.calculate_next_child_direction( - the_node_before.parent - ) - } - var node = this.mind.insert_node_before( - the_node_before, - node_id, - topic, - data, - dir - ) - if (!!node) { - this.view.add_node(node) - this.layout.layout() - this.view.show(false) - this.invoke_event_handle(EventType.edit, { - evt: 'insert_node_before', - data: [the_node_before.id, node_id, topic, data, dir], - node: node_id, - }) - } - return node - } else { - logger.error('fail, this mind map is not editable') - return null - } - } - insert_node_after(node_after, node_id, topic, data, direction) { - if (this.get_editable()) { - var the_node_after = this.get_node(node_after) - var dir = Direction.of(direction) - if (dir === undefined) { - dir = this.layout.calculate_next_child_direction( - the_node_after.parent - ) - } - var node = this.mind.insert_node_after( - the_node_after, - node_id, - topic, - data, - dir - ) - if (!!node) { - this.view.add_node(node) - this.layout.layout() - this.view.show(false) - this.invoke_event_handle(EventType.edit, { - evt: 'insert_node_after', - data: [the_node_after.id, node_id, topic, data, dir], - node: node_id, - }) - } - return node - } else { - logger.error('fail, this mind map is not editable') - return null - } - } - remove_node(node) { - if (!Node.is_node(node)) { - var the_node = this.get_node(node) - if (!the_node) { - logger.error('the node[id=' + node + '] can not be found.') - return false - } else { - return this.remove_node(the_node) - } - } - if (this.get_editable()) { - if (node.isroot) { - logger.error('fail, can not remove root node') - return false - } - var node_id = node.id - var parent_id = node.parent.id - var parent_node = this.get_node(parent_id) - this.view.save_location(parent_node) - this.view.remove_node(node) - this.mind.remove_node(node) - this.layout.layout() - this.view.show(false) - this.view.restore_location(parent_node) - this.invoke_event_handle(EventType.edit, { - evt: 'remove_node', - data: [node_id], - node: parent_id, - }) - return true - } else { - logger.error('fail, this mind map is not editable') - return false - } - } - update_node(node_id, topic) { - if (this.get_editable()) { - if (_util.text.is_empty(topic)) { - logger.warn('fail, topic can not be empty') - return - } - var node = this.get_node(node_id) - if (!!node) { - if (node.topic === topic) { - logger.info('nothing changed') - this.view.update_node(node) - return - } - node.topic = topic - this.view.update_node(node) - this.layout.layout() - this.view.show(false) - this.invoke_event_handle(EventType.edit, { - evt: 'update_node', - data: [node_id, topic], - node: node_id, - }) - } - } else { - logger.error('fail, this mind map is not editable') - return - } - } - move_node(node_id, before_id, parent_id, direction) { - if (this.get_editable()) { - var node = this.get_node(node_id) - var updated_node = this.mind.move_node( - node, - before_id, - parent_id, - direction - ) - if (!!updated_node) { - this.view.update_node(updated_node) - this.layout.layout() - this.view.show(false) - this.invoke_event_handle(EventType.edit, { - evt: 'move_node', - data: [node_id, before_id, parent_id, direction], - node: node_id, - }) - } - } else { - logger.error('fail, this mind map is not editable') - return - } - } - select_node(node) { - if (!Node.is_node(node)) { - var the_node = this.get_node(node) - if (!the_node) { - logger.error('the node[id=' + node + '] can not be found.') - return - } else { - return this.select_node(the_node) - } - } - if (!this.layout.is_visible(node)) { - return - } - this.mind.selected = node - this.view.select_node(node) - this.invoke_event_handle(EventType.select, { - evt: 'select_node', - data: [], - node: node.id, - }) - } - get_selected_node() { - if (!!this.mind) { - return this.mind.selected - } else { - return null - } - } - select_clear() { - if (!!this.mind) { - this.mind.selected = null - this.view.select_clear() - } - } - is_node_visible(node) { - return this.layout.is_visible(node) - } - scroll_node_to_center(node) { - if (!Node.is_node(node)) { - var the_node = this.get_node(node) - if (!the_node) { - logger.error('the node[id=' + node + '] can not be found.') - } else { - this.scroll_node_to_center(the_node) - } - return - } - this.view.center_node(node) - } - find_node_before(node) { - if (!Node.is_node(node)) { - var the_node = this.get_node(node) - if (!the_node) { - logger.error('the node[id=' + node + '] can not be found.') - return - } else { - return this.find_node_before(the_node) - } - } - if (node.isroot) { - return null - } - var n = null - if (node.parent.isroot) { - var c = node.parent.children - var prev = null - var ni = null - for (var i = 0; i < c.length; i++) { - ni = c[i] - if (node.direction === ni.direction) { - if (node.id === ni.id) { - n = prev - } - prev = ni - } - } - } else { - n = this.mind.get_node_before(node) - } - return n - } - find_node_after(node) { - if (!Node.is_node(node)) { - var the_node = this.get_node(node) - if (!the_node) { - logger.error('the node[id=' + node + '] can not be found.') - return - } else { - return this.find_node_after(the_node) - } - } - if (node.isroot) { - return null - } - var n = null - if (node.parent.isroot) { - var c = node.parent.children - var found = false - var ni = null - for (var i = 0; i < c.length; i++) { - ni = c[i] - if (node.direction === ni.direction) { - if (found) { - n = ni - break - } - if (node.id === ni.id) { - found = true - } - } - } - } else { - n = this.mind.get_node_after(node) - } - return n - } - set_node_color(node_id, bg_color, fg_color) { - if (this.get_editable()) { - var node = this.mind.get_node(node_id) - if (!!node) { - if (!!bg_color) { - node.data['background-color'] = bg_color - } - if (!!fg_color) { - node.data['foreground-color'] = fg_color - } - this.view.reset_node_custom_style(node) - } - } else { - logger.error('fail, this mind map is not editable') - return null - } - } - set_node_font_style(node_id, size, weight, style) { - if (this.get_editable()) { - var node = this.mind.get_node(node_id) - if (!!node) { - if (!!size) { - node.data['font-size'] = size - } - if (!!weight) { - node.data['font-weight'] = weight - } - if (!!style) { - node.data['font-style'] = style - } - this.view.reset_node_custom_style(node) - this.view.update_node(node) - this.layout.layout() - this.view.show(false) - } - } else { - logger.error('fail, this mind map is not editable') - return null - } - } - set_node_background_image(node_id, image, width, height, rotation) { - if (this.get_editable()) { - var node = this.mind.get_node(node_id) - if (!!node) { - if (!!image) { - node.data['background-image'] = image - } - if (!!width) { - node.data['width'] = width - } - if (!!height) { - node.data['height'] = height - } - if (!!rotation) { - node.data['background-rotation'] = rotation - } - this.view.reset_node_custom_style(node) - this.view.update_node(node) - this.layout.layout() - this.view.show(false) - } - } else { - logger.error('fail, this mind map is not editable') - return null - } - } - set_node_background_rotation(node_id, rotation) { - if (this.get_editable()) { - var node = this.mind.get_node(node_id) - if (!!node) { - if (!node.data['background-image']) { - logger.error( - 'fail, only can change rotation angle of node with background image' - ) - return null - } - node.data['background-rotation'] = rotation - this.view.reset_node_custom_style(node) - this.view.update_node(node) - this.layout.layout() - this.view.show(false) - } - } else { - logger.error('fail, this mind map is not editable') - return null - } - } - resize() { - this.view.resize() - } - // callback(type ,data) - add_event_listener(callback) { - if (typeof callback === 'function') { - this.event_handles.push(callback) - } - } - clear_event_listener() { - this.event_handles = [] - } - invoke_event_handle(type, data) { - var j = this - $.w.setTimeout(function () { - j._invoke_event_handle(type, data) - }, 0) - } - _invoke_event_handle(type, data) { - var l = this.event_handles.length - for (var i = 0; i < l; i++) { - this.event_handles[i](type, data) - } - } - - static show(options, mind) { - logger.warn( - '`jsMind.show(options, mind)` is deprecated, please use `jm = new jsMind(options); jm.show(mind);` instead' - ) - var _jm = new jsMind(options) - _jm.show(mind) - return _jm - } -} +import { + __version__, + logger, + EventType, + Direction, + LogLevel, +} from './jsmind.common.js' +import { merge_option } from './jsmind.option.js' +import { Mind } from './jsmind.mind.js' +import { Node } from './jsmind.node.js' +import { DataProvider } from './jsmind.data_provider.js' +import { LayoutProvider } from './jsmind.layout_provider.js' +import { ViewProvider } from './jsmind.view_provider.js' +import { ShortcutProvider } from './jsmind.shortcut_provider.js' +import { + Plugin, + register as _register_plugin, + apply as apply_plugins, +} from './jsmind.plugin.js' +import { format } from './jsmind.format.js' +import { $ } from './jsmind.dom.js' +import { util as _util } from './jsmind.util.js' + +export default class jsMind { + static mind = Mind + static node = Node + static direction = Direction + static event_type = EventType + static $ = $ + static plugin = Plugin + static register_plugin = _register_plugin + static util = _util + + constructor(options) { + jsMind.current = this + this.options = merge_option(options) + logger.level(LogLevel[this.options.log_level]) + this.version = __version__ + this.initialized = false + this.mind = null + this.event_handles = [] + this.init() + } + + init() { + if (!!this.initialized) { + return + } + this.initialized = true + var opts_layout = { + mode: this.options.mode, + hspace: this.options.layout.hspace, + vspace: this.options.layout.vspace, + pspace: this.options.layout.pspace, + cousin_space: this.options.layout.cousin_space, + } + var opts_view = { + container: this.options.container, + support_html: this.options.support_html, + engine: this.options.view.engine, + enable_device_pixel_ratio: + this.options.view.enable_device_pixel_ratio, + hmargin: this.options.view.hmargin, + vmargin: this.options.view.vmargin, + line_width: this.options.view.line_width, + line_color: this.options.view.line_color, + line_style: this.options.view.line_style, + custom_line_render: this.options.view.custom_line_render, + draggable: this.options.view.draggable, + hide_scrollbars_when_draggable: + this.options.view.hide_scrollbars_when_draggable, + node_overflow: this.options.view.node_overflow, + zoom: this.options.view.zoom, + custom_node_render: this.options.view.custom_node_render, + expander_style: this.options.view.expander_style, + } + // create instance of function provider + this.data = new DataProvider(this) + this.layout = new LayoutProvider(this, opts_layout) + this.view = new ViewProvider(this, opts_view) + this.shortcut = new ShortcutProvider(this, this.options.shortcut) + + this.data.init() + this.layout.init() + this.view.init() + this.shortcut.init() + + this._event_bind() + + apply_plugins(this, this.options.plugin) + } + get_editable() { + return this.options.editable + } + enable_edit() { + this.options.editable = true + } + disable_edit() { + this.options.editable = false + } + get_view_draggable() { + return this.options.view.draggable + } + enable_view_draggable() { + this.options.view.draggable = true + this.view.setup_canvas_draggable(true) + } + disable_view_draggable() { + this.options.view.draggable = false + this.view.setup_canvas_draggable(false) + } + // options are 'mousedown', 'click', 'dblclick', 'mousewheel' + enable_event_handle(event_handle) { + this.options.default_event_handle[ + 'enable_' + event_handle + '_handle' + ] = true + } + // options are 'mousedown', 'click', 'dblclick', 'mousewheel' + disable_event_handle(event_handle) { + this.options.default_event_handle[ + 'enable_' + event_handle + '_handle' + ] = false + } + set_theme(theme) { + var theme_old = this.options.theme + this.options.theme = !!theme ? theme : null + if (theme_old != this.options.theme) { + this.view.reset_theme() + this.view.reset_custom_style() + } + } + _event_bind() { + this.view.add_event(this, 'mousedown', this.mousedown_handle) + this.view.add_event(this, 'click', this.click_handle) + this.view.add_event(this, 'dblclick', this.dblclick_handle) + this.view.add_event(this, 'mousewheel', this.mousewheel_handle, true) + } + mousedown_handle(e) { + if (!this.options.default_event_handle['enable_mousedown_handle']) { + return + } + var element = e.target || event.srcElement + var node_id = this.view.get_binded_nodeid(element) + if (!!node_id) { + if (this.view.is_node(element)) { + this.select_node(node_id) + } + } else { + this.select_clear() + } + } + click_handle(e) { + if (!this.options.default_event_handle['enable_click_handle']) { + return + } + var element = e.target || event.srcElement + var is_expander = this.view.is_expander(element) + if (is_expander) { + var node_id = this.view.get_binded_nodeid(element) + if (!!node_id) { + this.toggle_node(node_id) + } + } + } + dblclick_handle(e) { + if (!this.options.default_event_handle['enable_dblclick_handle']) { + return + } + if (this.get_editable()) { + var element = e.target || event.srcElement + var is_node = this.view.is_node(element) + if (is_node) { + var node_id = this.view.get_binded_nodeid(element) + if (!!node_id) { + this.begin_edit(node_id) + } + } + } + } + // Use [Ctrl] + Mousewheel, to zoom in/out. + mousewheel_handle(e) { + // Test if mousewheel option is enabled and Ctrl key is pressed. + if ( + !this.options.default_event_handle['enable_mousewheel_handle'] || + !e.ctrlKey + ) { + return + } + var evt = e || event + // Avoid default page scrolling behavior. + evt.preventDefault() + + if (evt.deltaY < 0) { + this.view.zoom_in(evt) // wheel down + } else { + this.view.zoom_out(evt) + } + } + begin_edit(node) { + if (!Node.is_node(node)) { + var the_node = this.get_node(node) + if (!the_node) { + logger.error('the node[id=' + node + '] can not be found.') + return false + } else { + return this.begin_edit(the_node) + } + } + if (this.get_editable()) { + this.view.edit_node_begin(node) + } else { + logger.error('fail, this mind map is not editable.') + return + } + } + end_edit() { + this.view.edit_node_end() + } + toggle_node(node) { + if (!Node.is_node(node)) { + var the_node = this.get_node(node) + if (!the_node) { + logger.error('the node[id=' + node + '] can not be found.') + return + } else { + return this.toggle_node(the_node) + } + } + if (node.isroot) { + return + } + this.view.save_location(node) + this.layout.toggle_node(node) + this.view.relayout() + this.view.restore_location(node) + } + expand_node(node) { + if (!Node.is_node(node)) { + var the_node = this.get_node(node) + if (!the_node) { + logger.error('the node[id=' + node + '] can not be found.') + return + } else { + return this.expand_node(the_node) + } + } + if (node.isroot) { + return + } + this.view.save_location(node) + this.layout.expand_node(node) + this.view.relayout() + this.view.restore_location(node) + } + collapse_node(node) { + if (!Node.is_node(node)) { + var the_node = this.get_node(node) + if (!the_node) { + logger.error('the node[id=' + node + '] can not be found.') + return + } else { + return this.collapse_node(the_node) + } + } + if (node.isroot) { + return + } + this.view.save_location(node) + this.layout.collapse_node(node) + this.view.relayout() + this.view.restore_location(node) + } + expand_all() { + this.layout.expand_all() + this.view.relayout() + } + collapse_all() { + this.layout.collapse_all() + this.view.relayout() + } + expand_to_depth(depth) { + this.layout.expand_to_depth(depth) + this.view.relayout() + } + _reset() { + this.view.reset() + this.layout.reset() + this.data.reset() + } + _show(mind, skip_centering) { + var m = mind || format.node_array.example + this.mind = this.data.load(m) + if (!this.mind) { + logger.error('data.load error') + return + } else { + logger.debug('data.load ok') + } + + this.view.load() + logger.debug('view.load ok') + + this.layout.layout() + logger.debug('layout.layout ok') + + this.view.show(!skip_centering) + logger.debug('view.show ok') + + this.invoke_event_handle(EventType.show, { data: [mind] }) + } + show(mind, skip_centering) { + this._reset() + this._show(mind, skip_centering) + } + get_meta() { + return { + name: this.mind.name, + author: this.mind.author, + version: this.mind.version, + } + } + get_data(data_format) { + var df = data_format || 'node_tree' + return this.data.get_data(df) + } + get_root() { + return this.mind.root + } + get_node(node) { + if (Node.is_node(node)) { + return node + } + return this.mind.get_node(node) + } + add_node(parent_node, node_id, topic, data, direction) { + if (this.get_editable()) { + var the_parent_node = this.get_node(parent_node) + var dir = Direction.of(direction) + if (dir === undefined) { + dir = + this.layout.calculate_next_child_direction(the_parent_node) + } + var node = this.mind.add_node( + the_parent_node, + node_id, + topic, + data, + dir + ) + if (!!node) { + this.view.add_node(node) + this.layout.layout() + this.view.show(false) + this.view.reset_node_custom_style(node) + this.expand_node(the_parent_node) + this.invoke_event_handle(EventType.edit, { + evt: 'add_node', + data: [the_parent_node.id, node_id, topic, data, dir], + node: node_id, + }) + } + return node + } else { + logger.error('fail, this mind map is not editable') + return null + } + } + insert_node_before(node_before, node_id, topic, data, direction) { + if (this.get_editable()) { + var the_node_before = this.get_node(node_before) + var dir = Direction.of(direction) + if (dir === undefined) { + dir = this.layout.calculate_next_child_direction( + the_node_before.parent + ) + } + var node = this.mind.insert_node_before( + the_node_before, + node_id, + topic, + data, + dir + ) + if (!!node) { + this.view.add_node(node) + this.layout.layout() + this.view.show(false) + this.invoke_event_handle(EventType.edit, { + evt: 'insert_node_before', + data: [the_node_before.id, node_id, topic, data, dir], + node: node_id, + }) + } + return node + } else { + logger.error('fail, this mind map is not editable') + return null + } + } + insert_node_after(node_after, node_id, topic, data, direction) { + if (this.get_editable()) { + var the_node_after = this.get_node(node_after) + var dir = Direction.of(direction) + if (dir === undefined) { + dir = this.layout.calculate_next_child_direction( + the_node_after.parent + ) + } + var node = this.mind.insert_node_after( + the_node_after, + node_id, + topic, + data, + dir + ) + if (!!node) { + this.view.add_node(node) + this.layout.layout() + this.view.show(false) + this.invoke_event_handle(EventType.edit, { + evt: 'insert_node_after', + data: [the_node_after.id, node_id, topic, data, dir], + node: node_id, + }) + } + return node + } else { + logger.error('fail, this mind map is not editable') + return null + } + } + remove_node(node) { + if (!Node.is_node(node)) { + var the_node = this.get_node(node) + if (!the_node) { + logger.error('the node[id=' + node + '] can not be found.') + return false + } else { + return this.remove_node(the_node) + } + } + if (this.get_editable()) { + if (node.isroot) { + logger.error('fail, can not remove root node') + return false + } + var node_id = node.id + var parent_id = node.parent.id + var parent_node = this.get_node(parent_id) + this.view.save_location(parent_node) + this.view.remove_node(node) + this.mind.remove_node(node) + this.layout.layout() + this.view.show(false) + this.view.restore_location(parent_node) + this.invoke_event_handle(EventType.edit, { + evt: 'remove_node', + data: [node_id], + node: parent_id, + }) + return true + } else { + logger.error('fail, this mind map is not editable') + return false + } + } + update_node(node_id, topic) { + if (this.get_editable()) { + if (_util.text.is_empty(topic)) { + logger.warn('fail, topic can not be empty') + return + } + var node = this.get_node(node_id) + if (!!node) { + if (node.topic === topic) { + logger.info('nothing changed') + this.view.update_node(node) + return + } + node.topic = topic + this.view.update_node(node) + this.layout.layout() + this.view.show(false) + this.invoke_event_handle(EventType.edit, { + evt: 'update_node', + data: [node_id, topic], + node: node_id, + }) + } + } else { + logger.error('fail, this mind map is not editable') + return + } + } + move_node(node_id, before_id, parent_id, direction) { + if (this.get_editable()) { + var node = this.get_node(node_id) + var updated_node = this.mind.move_node( + node, + before_id, + parent_id, + direction + ) + if (!!updated_node) { + this.view.update_node(updated_node) + this.layout.layout() + this.view.show(false) + this.invoke_event_handle(EventType.edit, { + evt: 'move_node', + data: [node_id, before_id, parent_id, direction], + node: node_id, + }) + } + } else { + logger.error('fail, this mind map is not editable') + return + } + } + select_node(node) { + if (!Node.is_node(node)) { + var the_node = this.get_node(node) + if (!the_node) { + logger.error('the node[id=' + node + '] can not be found.') + return + } else { + return this.select_node(the_node) + } + } + if (!this.layout.is_visible(node)) { + return + } + this.mind.selected = node + this.view.select_node(node) + this.invoke_event_handle(EventType.select, { + evt: 'select_node', + data: [], + node: node.id, + }) + } + get_selected_node() { + if (!!this.mind) { + return this.mind.selected + } else { + return null + } + } + select_clear() { + if (!!this.mind) { + this.mind.selected = null + this.view.select_clear() + } + } + is_node_visible(node) { + return this.layout.is_visible(node) + } + scroll_node_to_center(node) { + if (!Node.is_node(node)) { + var the_node = this.get_node(node) + if (!the_node) { + logger.error('the node[id=' + node + '] can not be found.') + } else { + this.scroll_node_to_center(the_node) + } + return + } + this.view.center_node(node) + } + find_node_before(node) { + if (!Node.is_node(node)) { + var the_node = this.get_node(node) + if (!the_node) { + logger.error('the node[id=' + node + '] can not be found.') + return + } else { + return this.find_node_before(the_node) + } + } + if (node.isroot) { + return null + } + var n = null + if (node.parent.isroot) { + var c = node.parent.children + var prev = null + var ni = null + for (var i = 0; i < c.length; i++) { + ni = c[i] + if (node.direction === ni.direction) { + if (node.id === ni.id) { + n = prev + } + prev = ni + } + } + } else { + n = this.mind.get_node_before(node) + } + return n + } + find_node_after(node) { + if (!Node.is_node(node)) { + var the_node = this.get_node(node) + if (!the_node) { + logger.error('the node[id=' + node + '] can not be found.') + return + } else { + return this.find_node_after(the_node) + } + } + if (node.isroot) { + return null + } + var n = null + if (node.parent.isroot) { + var c = node.parent.children + var found = false + var ni = null + for (var i = 0; i < c.length; i++) { + ni = c[i] + if (node.direction === ni.direction) { + if (found) { + n = ni + break + } + if (node.id === ni.id) { + found = true + } + } + } + } else { + n = this.mind.get_node_after(node) + } + return n + } + set_node_color(node_id, bg_color, fg_color) { + if (this.get_editable()) { + var node = this.mind.get_node(node_id) + if (!!node) { + if (!!bg_color) { + node.data['background-color'] = bg_color + } + if (!!fg_color) { + node.data['foreground-color'] = fg_color + } + this.view.reset_node_custom_style(node) + } + } else { + logger.error('fail, this mind map is not editable') + return null + } + } + set_node_font_style(node_id, size, weight, style) { + if (this.get_editable()) { + var node = this.mind.get_node(node_id) + if (!!node) { + if (!!size) { + node.data['font-size'] = size + } + if (!!weight) { + node.data['font-weight'] = weight + } + if (!!style) { + node.data['font-style'] = style + } + this.view.reset_node_custom_style(node) + this.view.update_node(node) + this.layout.layout() + this.view.show(false) + } + } else { + logger.error('fail, this mind map is not editable') + return null + } + } + set_node_background_image(node_id, image, width, height, rotation) { + if (this.get_editable()) { + var node = this.mind.get_node(node_id) + if (!!node) { + if (!!image) { + node.data['background-image'] = image + } + if (!!width) { + node.data['width'] = width + } + if (!!height) { + node.data['height'] = height + } + if (!!rotation) { + node.data['background-rotation'] = rotation + } + this.view.reset_node_custom_style(node) + this.view.update_node(node) + this.layout.layout() + this.view.show(false) + } + } else { + logger.error('fail, this mind map is not editable') + return null + } + } + set_node_background_rotation(node_id, rotation) { + if (this.get_editable()) { + var node = this.mind.get_node(node_id) + if (!!node) { + if (!node.data['background-image']) { + logger.error( + 'fail, only can change rotation angle of node with background image' + ) + return null + } + node.data['background-rotation'] = rotation + this.view.reset_node_custom_style(node) + this.view.update_node(node) + this.layout.layout() + this.view.show(false) + } + } else { + logger.error('fail, this mind map is not editable') + return null + } + } + resize() { + this.view.resize() + } + // callback(type ,data) + add_event_listener(callback) { + if (typeof callback === 'function') { + this.event_handles.push(callback) + } + } + clear_event_listener() { + this.event_handles = [] + } + invoke_event_handle(type, data) { + var j = this + $.w.setTimeout(function () { + j._invoke_event_handle(type, data) + }, 0) + } + _invoke_event_handle(type, data) { + var l = this.event_handles.length + for (var i = 0; i < l; i++) { + this.event_handles[i](type, data) + } + } + + static show(options, mind) { + logger.warn( + '`jsMind.show(options, mind)` is deprecated, please use `jm = new jsMind(options); jm.show(mind);` instead' + ) + var _jm = new jsMind(options) + _jm.show(mind) + return _jm + } +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.layout_provider.js b/app/assets/javascripts/mind_map/jsmind/jsmind.layout_provider.js old mode 100644 new mode 100755 index bc93a06..dfc8b72 --- a/app/assets/javascripts/mind_map/jsmind/jsmind.layout_provider.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.layout_provider.js @@ -1,445 +1,445 @@ -import { logger, Direction, EventType } from './jsmind.common.js' - -export class LayoutProvider { - constructor(jm, options) { - this.opts = options - this.jm = jm - this.isside = this.opts.mode == 'side' - this.bounds = null - - this.cache_valid = false - } - init() { - logger.debug('layout.init') - } - reset() { - logger.debug('layout.reset') - this.bounds = { n: 0, s: 0, w: 0, e: 0 } - } - calculate_next_child_direction(node) { - if (this.isside) { - return Direction.right - } - var children = node.children || [] - var children_len = children.length - var r = 0 - for (var i = 0; i < children_len; i++) { - if (children[i].direction === Direction.left) { - r-- - } else { - r++ - } - } - return children_len > 1 && r > 0 ? Direction.left : Direction.right - } - layout() { - logger.debug('layout.layout') - this.layout_direction() - this.layout_offset() - } - layout_direction() { - this._layout_direction_root() - } - _layout_direction_root() { - var node = this.jm.mind.root - var layout_data = null - if ('layout' in node._data) { - layout_data = node._data.layout - } else { - layout_data = {} - node._data.layout = layout_data - } - var children = node.children - var children_count = children.length - layout_data.direction = Direction.center - layout_data.side_index = 0 - if (this.isside) { - var i = children_count - while (i--) { - this._layout_direction_side(children[i], Direction.right, i) - } - } else { - var i = children_count - var subnode = null - while (i--) { - subnode = children[i] - if (subnode.direction == Direction.left) { - this._layout_direction_side(subnode, Direction.left, i) - } else { - this._layout_direction_side(subnode, Direction.right, i) - } - } - } - } - _layout_direction_side(node, direction, side_index) { - var layout_data = null - if ('layout' in node._data) { - layout_data = node._data.layout - } else { - layout_data = {} - node._data.layout = layout_data - } - var children = node.children - var children_count = children.length - - layout_data.direction = direction - layout_data.side_index = side_index - var i = children_count - while (i--) { - this._layout_direction_side(children[i], direction, i) - } - } - layout_offset() { - var node = this.jm.mind.root - var layout_data = node._data.layout - layout_data.offset_x = 0 - layout_data.offset_y = 0 - layout_data.outer_height = 0 - var children = node.children - var i = children.length - var left_nodes = [] - var right_nodes = [] - var subnode = null - while (i--) { - subnode = children[i] - if (subnode._data.layout.direction == Direction.right) { - right_nodes.unshift(subnode) - } else { - left_nodes.unshift(subnode) - } - } - layout_data.left_nodes = left_nodes - layout_data.right_nodes = right_nodes - layout_data.outer_height_left = this._layout_offset_subnodes(left_nodes) - layout_data.outer_height_right = this._layout_offset_subnodes(right_nodes) - this.bounds.e = node._data.view.width / 2 - this.bounds.w = 0 - this.bounds.e - this.bounds.n = 0 - this.bounds.s = Math.max(layout_data.outer_height_left, layout_data.outer_height_right) - } - // layout both the x and y axis - _layout_offset_subnodes(nodes) { - var total_height = 0 - var nodes_count = nodes.length - var i = nodes_count - var node = null - var node_outer_height = 0 - var layout_data = null - var base_y = 0 - var pd = null // parent._data - while (i--) { - node = nodes[i] - layout_data = node._data.layout - if (pd == null) { - pd = node.parent._data - } - - node_outer_height = this._layout_offset_subnodes(node.children) - if (!node.expanded) { - node_outer_height = 0 - this.set_visible(node.children, false) - } - node_outer_height = Math.max(node._data.view.height, node_outer_height) - if (node.children.length > 1) { - node_outer_height += this.opts.cousin_space - } - - layout_data.outer_height = node_outer_height - layout_data.offset_y = base_y - node_outer_height / 2 - layout_data.offset_x = - this.opts.hspace * layout_data.direction + - (pd.view.width * (pd.layout.direction + layout_data.direction)) / 2 - if (!node.parent.isroot) { - layout_data.offset_x += this.opts.pspace * layout_data.direction - } - - base_y = base_y - node_outer_height - this.opts.vspace - total_height += node_outer_height - } - if (nodes_count > 1) { - total_height += this.opts.vspace * (nodes_count - 1) - } - i = nodes_count - var middle_height = total_height / 2 - while (i--) { - node = nodes[i] - node._data.layout.offset_y += middle_height - } - return total_height - } - // layout the y axis only, for collapse/expand a node - _layout_offset_subnodes_height(nodes) { - var total_height = 0 - var nodes_count = nodes.length - var i = nodes_count - var node = null - var node_outer_height = 0 - var layout_data = null - var base_y = 0 - var pd = null // parent._data - while (i--) { - node = nodes[i] - layout_data = node._data.layout - if (pd == null) { - pd = node.parent._data - } - - node_outer_height = this._layout_offset_subnodes_height(node.children) - if (!node.expanded) { - node_outer_height = 0 - } - node_outer_height = Math.max(node._data.view.height, node_outer_height) - if (node.children.length > 1) { - node_outer_height += this.opts.cousin_space - } - - layout_data.outer_height = node_outer_height - layout_data.offset_y = base_y - node_outer_height / 2 - base_y = base_y - node_outer_height - this.opts.vspace - total_height += node_outer_height - } - if (nodes_count > 1) { - total_height += this.opts.vspace * (nodes_count - 1) - } - i = nodes_count - var middle_height = total_height / 2 - while (i--) { - node = nodes[i] - node._data.layout.offset_y += middle_height - } - return total_height - } - get_node_offset(node) { - var layout_data = node._data.layout - var offset_cache = null - if ('_offset_' in layout_data && this.cache_valid) { - offset_cache = layout_data._offset_ - } else { - offset_cache = { x: -1, y: -1 } - layout_data._offset_ = offset_cache - } - if (offset_cache.x == -1 || offset_cache.y == -1) { - var x = layout_data.offset_x - var y = layout_data.offset_y - if (!node.isroot) { - var offset_p = this.get_node_offset(node.parent) - x += offset_p.x - y += offset_p.y - } - offset_cache.x = x - offset_cache.y = y - } - return offset_cache - } - get_node_point(node) { - var view_data = node._data.view - var offset_p = this.get_node_offset(node) - var p = {} - p.x = offset_p.x + (view_data.width * (node._data.layout.direction - 1)) / 2 - p.y = offset_p.y - view_data.height / 2 - return p - } - get_node_point_in(node) { - var p = this.get_node_offset(node) - return p - } - get_node_point_out(node) { - var layout_data = node._data.layout - var pout_cache = null - if ('_pout_' in layout_data && this.cache_valid) { - pout_cache = layout_data._pout_ - } else { - pout_cache = { x: -1, y: -1 } - layout_data._pout_ = pout_cache - } - if (pout_cache.x == -1 || pout_cache.y == -1) { - if (node.isroot) { - pout_cache.x = 0 - pout_cache.y = 0 - } else { - var view_data = node._data.view - var offset_p = this.get_node_offset(node) - pout_cache.x = - offset_p.x + (view_data.width + this.opts.pspace) * node._data.layout.direction - pout_cache.y = offset_p.y - } - } - return pout_cache - } - get_expander_point(node) { - var p = this.get_node_point_out(node) - var ex_p = {} - if (node._data.layout.direction == Direction.right) { - ex_p.x = p.x - this.opts.pspace - } else { - ex_p.x = p.x - } - ex_p.y = p.y - Math.ceil(this.opts.pspace / 2) - return ex_p - } - get_min_size() { - var nodes = this.jm.mind.nodes - var node = null - var pout = null - for (var node_id in nodes) { - node = nodes[node_id] - pout = this.get_node_point_out(node) - if (pout.x > this.bounds.e) { - this.bounds.e = pout.x - } - if (pout.x < this.bounds.w) { - this.bounds.w = pout.x - } - } - return { - w: this.bounds.e - this.bounds.w, - h: this.bounds.s - this.bounds.n, - } - } - toggle_node(node) { - if (node.isroot) { - return - } - if (node.expanded) { - this.collapse_node(node) - } else { - this.expand_node(node) - } - } - expand_node(node) { - node.expanded = true - this.part_layout(node) - this.set_visible(node.children, true) - this.jm.invoke_event_handle(EventType.show, { - evt: 'expand_node', - data: [], - node: node.id, - }) - } - collapse_node(node) { - node.expanded = false - this.part_layout(node) - this.set_visible(node.children, false) - this.jm.invoke_event_handle(EventType.show, { - evt: 'collapse_node', - data: [], - node: node.id, - }) - } - expand_all() { - var nodes = this.jm.mind.nodes - var c = 0 - var node - for (var node_id in nodes) { - node = nodes[node_id] - if (!node.expanded) { - node.expanded = true - c++ - } - } - if (c > 0) { - var root = this.jm.mind.root - this.part_layout(root) - this.set_visible(root.children, true) - } - } - collapse_all() { - var nodes = this.jm.mind.nodes - var c = 0 - var node - for (var node_id in nodes) { - node = nodes[node_id] - if (node.expanded && !node.isroot) { - node.expanded = false - c++ - } - } - if (c > 0) { - var root = this.jm.mind.root - this.part_layout(root) - this.set_visible(root.children, true) - } - } - expand_to_depth(target_depth, curr_nodes, curr_depth) { - if (target_depth < 1) { - return - } - var nodes = curr_nodes || this.jm.mind.root.children - var depth = curr_depth || 1 - var i = nodes.length - var node = null - while (i--) { - node = nodes[i] - if (depth < target_depth) { - if (!node.expanded) { - this.expand_node(node) - } - this.expand_to_depth(target_depth, node.children, depth + 1) - } - if (depth == target_depth) { - if (node.expanded) { - this.collapse_node(node) - } - } - } - } - part_layout(node) { - var root = this.jm.mind.root - if (!!root) { - var root_layout_data = root._data.layout - if (node.isroot) { - root_layout_data.outer_height_right = this._layout_offset_subnodes_height( - root_layout_data.right_nodes - ) - root_layout_data.outer_height_left = this._layout_offset_subnodes_height( - root_layout_data.left_nodes - ) - } else { - if (node._data.layout.direction == Direction.right) { - root_layout_data.outer_height_right = this._layout_offset_subnodes_height( - root_layout_data.right_nodes - ) - } else { - root_layout_data.outer_height_left = this._layout_offset_subnodes_height( - root_layout_data.left_nodes - ) - } - } - this.bounds.s = Math.max( - root_layout_data.outer_height_left, - root_layout_data.outer_height_right - ) - this.cache_valid = false - } else { - logger.warn('can not found root node') - } - } - set_visible(nodes, visible) { - var i = nodes.length - var node = null - var layout_data = null - while (i--) { - node = nodes[i] - layout_data = node._data.layout - if (node.expanded) { - this.set_visible(node.children, visible) - } else { - this.set_visible(node.children, false) - } - if (!node.isroot) { - node._data.layout.visible = visible - } - } - } - is_expand(node) { - return node.expanded - } - is_visible(node) { - var layout_data = node._data.layout - if ('visible' in layout_data && !layout_data.visible) { - return false - } else { - return true - } - } -} +import { logger, Direction, EventType } from './jsmind.common.js' + +export class LayoutProvider { + constructor(jm, options) { + this.opts = options + this.jm = jm + this.isside = this.opts.mode == 'side' + this.bounds = null + + this.cache_valid = false + } + init() { + logger.debug('layout.init') + } + reset() { + logger.debug('layout.reset') + this.bounds = { n: 0, s: 0, w: 0, e: 0 } + } + calculate_next_child_direction(node) { + if (this.isside) { + return Direction.right + } + var children = node.children || [] + var children_len = children.length + var r = 0 + for (var i = 0; i < children_len; i++) { + if (children[i].direction === Direction.left) { + r-- + } else { + r++ + } + } + return children_len > 1 && r > 0 ? Direction.left : Direction.right + } + layout() { + logger.debug('layout.layout') + this.layout_direction() + this.layout_offset() + } + layout_direction() { + this._layout_direction_root() + } + _layout_direction_root() { + var node = this.jm.mind.root + var layout_data = null + if ('layout' in node._data) { + layout_data = node._data.layout + } else { + layout_data = {} + node._data.layout = layout_data + } + var children = node.children + var children_count = children.length + layout_data.direction = Direction.center + layout_data.side_index = 0 + if (this.isside) { + var i = children_count + while (i--) { + this._layout_direction_side(children[i], Direction.right, i) + } + } else { + var i = children_count + var subnode = null + while (i--) { + subnode = children[i] + if (subnode.direction == Direction.left) { + this._layout_direction_side(subnode, Direction.left, i) + } else { + this._layout_direction_side(subnode, Direction.right, i) + } + } + } + } + _layout_direction_side(node, direction, side_index) { + var layout_data = null + if ('layout' in node._data) { + layout_data = node._data.layout + } else { + layout_data = {} + node._data.layout = layout_data + } + var children = node.children + var children_count = children.length + + layout_data.direction = direction + layout_data.side_index = side_index + var i = children_count + while (i--) { + this._layout_direction_side(children[i], direction, i) + } + } + layout_offset() { + var node = this.jm.mind.root + var layout_data = node._data.layout + layout_data.offset_x = 0 + layout_data.offset_y = 0 + layout_data.outer_height = 0 + var children = node.children + var i = children.length + var left_nodes = [] + var right_nodes = [] + var subnode = null + while (i--) { + subnode = children[i] + if (subnode._data.layout.direction == Direction.right) { + right_nodes.unshift(subnode) + } else { + left_nodes.unshift(subnode) + } + } + layout_data.left_nodes = left_nodes + layout_data.right_nodes = right_nodes + layout_data.outer_height_left = this._layout_offset_subnodes(left_nodes) + layout_data.outer_height_right = this._layout_offset_subnodes(right_nodes) + this.bounds.e = node._data.view.width / 2 + this.bounds.w = 0 - this.bounds.e + this.bounds.n = 0 + this.bounds.s = Math.max(layout_data.outer_height_left, layout_data.outer_height_right) + } + // layout both the x and y axis + _layout_offset_subnodes(nodes) { + var total_height = 0 + var nodes_count = nodes.length + var i = nodes_count + var node = null + var node_outer_height = 0 + var layout_data = null + var base_y = 0 + var pd = null // parent._data + while (i--) { + node = nodes[i] + layout_data = node._data.layout + if (pd == null) { + pd = node.parent._data + } + + node_outer_height = this._layout_offset_subnodes(node.children) + if (!node.expanded) { + node_outer_height = 0 + this.set_visible(node.children, false) + } + node_outer_height = Math.max(node._data.view.height, node_outer_height) + if (node.children.length > 1) { + node_outer_height += this.opts.cousin_space + } + + layout_data.outer_height = node_outer_height + layout_data.offset_y = base_y - node_outer_height / 2 + layout_data.offset_x = + this.opts.hspace * layout_data.direction + + (pd.view.width * (pd.layout.direction + layout_data.direction)) / 2 + if (!node.parent.isroot) { + layout_data.offset_x += this.opts.pspace * layout_data.direction + } + + base_y = base_y - node_outer_height - this.opts.vspace + total_height += node_outer_height + } + if (nodes_count > 1) { + total_height += this.opts.vspace * (nodes_count - 1) + } + i = nodes_count + var middle_height = total_height / 2 + while (i--) { + node = nodes[i] + node._data.layout.offset_y += middle_height + } + return total_height + } + // layout the y axis only, for collapse/expand a node + _layout_offset_subnodes_height(nodes) { + var total_height = 0 + var nodes_count = nodes.length + var i = nodes_count + var node = null + var node_outer_height = 0 + var layout_data = null + var base_y = 0 + var pd = null // parent._data + while (i--) { + node = nodes[i] + layout_data = node._data.layout + if (pd == null) { + pd = node.parent._data + } + + node_outer_height = this._layout_offset_subnodes_height(node.children) + if (!node.expanded) { + node_outer_height = 0 + } + node_outer_height = Math.max(node._data.view.height, node_outer_height) + if (node.children.length > 1) { + node_outer_height += this.opts.cousin_space + } + + layout_data.outer_height = node_outer_height + layout_data.offset_y = base_y - node_outer_height / 2 + base_y = base_y - node_outer_height - this.opts.vspace + total_height += node_outer_height + } + if (nodes_count > 1) { + total_height += this.opts.vspace * (nodes_count - 1) + } + i = nodes_count + var middle_height = total_height / 2 + while (i--) { + node = nodes[i] + node._data.layout.offset_y += middle_height + } + return total_height + } + get_node_offset(node) { + var layout_data = node._data.layout + var offset_cache = null + if ('_offset_' in layout_data && this.cache_valid) { + offset_cache = layout_data._offset_ + } else { + offset_cache = { x: -1, y: -1 } + layout_data._offset_ = offset_cache + } + if (offset_cache.x == -1 || offset_cache.y == -1) { + var x = layout_data.offset_x + var y = layout_data.offset_y + if (!node.isroot) { + var offset_p = this.get_node_offset(node.parent) + x += offset_p.x + y += offset_p.y + } + offset_cache.x = x + offset_cache.y = y + } + return offset_cache + } + get_node_point(node) { + var view_data = node._data.view + var offset_p = this.get_node_offset(node) + var p = {} + p.x = offset_p.x + (view_data.width * (node._data.layout.direction - 1)) / 2 + p.y = offset_p.y - view_data.height / 2 + return p + } + get_node_point_in(node) { + var p = this.get_node_offset(node) + return p + } + get_node_point_out(node) { + var layout_data = node._data.layout + var pout_cache = null + if ('_pout_' in layout_data && this.cache_valid) { + pout_cache = layout_data._pout_ + } else { + pout_cache = { x: -1, y: -1 } + layout_data._pout_ = pout_cache + } + if (pout_cache.x == -1 || pout_cache.y == -1) { + if (node.isroot) { + pout_cache.x = 0 + pout_cache.y = 0 + } else { + var view_data = node._data.view + var offset_p = this.get_node_offset(node) + pout_cache.x = + offset_p.x + (view_data.width + this.opts.pspace) * node._data.layout.direction + pout_cache.y = offset_p.y + } + } + return pout_cache + } + get_expander_point(node) { + var p = this.get_node_point_out(node) + var ex_p = {} + if (node._data.layout.direction == Direction.right) { + ex_p.x = p.x - this.opts.pspace + } else { + ex_p.x = p.x + } + ex_p.y = p.y - Math.ceil(this.opts.pspace / 2) + return ex_p + } + get_min_size() { + var nodes = this.jm.mind.nodes + var node = null + var pout = null + for (var node_id in nodes) { + node = nodes[node_id] + pout = this.get_node_point_out(node) + if (pout.x > this.bounds.e) { + this.bounds.e = pout.x + } + if (pout.x < this.bounds.w) { + this.bounds.w = pout.x + } + } + return { + w: this.bounds.e - this.bounds.w, + h: this.bounds.s - this.bounds.n, + } + } + toggle_node(node) { + if (node.isroot) { + return + } + if (node.expanded) { + this.collapse_node(node) + } else { + this.expand_node(node) + } + } + expand_node(node) { + node.expanded = true + this.part_layout(node) + this.set_visible(node.children, true) + this.jm.invoke_event_handle(EventType.show, { + evt: 'expand_node', + data: [], + node: node.id, + }) + } + collapse_node(node) { + node.expanded = false + this.part_layout(node) + this.set_visible(node.children, false) + this.jm.invoke_event_handle(EventType.show, { + evt: 'collapse_node', + data: [], + node: node.id, + }) + } + expand_all() { + var nodes = this.jm.mind.nodes + var c = 0 + var node + for (var node_id in nodes) { + node = nodes[node_id] + if (!node.expanded) { + node.expanded = true + c++ + } + } + if (c > 0) { + var root = this.jm.mind.root + this.part_layout(root) + this.set_visible(root.children, true) + } + } + collapse_all() { + var nodes = this.jm.mind.nodes + var c = 0 + var node + for (var node_id in nodes) { + node = nodes[node_id] + if (node.expanded && !node.isroot) { + node.expanded = false + c++ + } + } + if (c > 0) { + var root = this.jm.mind.root + this.part_layout(root) + this.set_visible(root.children, true) + } + } + expand_to_depth(target_depth, curr_nodes, curr_depth) { + if (target_depth < 1) { + return + } + var nodes = curr_nodes || this.jm.mind.root.children + var depth = curr_depth || 1 + var i = nodes.length + var node = null + while (i--) { + node = nodes[i] + if (depth < target_depth) { + if (!node.expanded) { + this.expand_node(node) + } + this.expand_to_depth(target_depth, node.children, depth + 1) + } + if (depth == target_depth) { + if (node.expanded) { + this.collapse_node(node) + } + } + } + } + part_layout(node) { + var root = this.jm.mind.root + if (!!root) { + var root_layout_data = root._data.layout + if (node.isroot) { + root_layout_data.outer_height_right = this._layout_offset_subnodes_height( + root_layout_data.right_nodes + ) + root_layout_data.outer_height_left = this._layout_offset_subnodes_height( + root_layout_data.left_nodes + ) + } else { + if (node._data.layout.direction == Direction.right) { + root_layout_data.outer_height_right = this._layout_offset_subnodes_height( + root_layout_data.right_nodes + ) + } else { + root_layout_data.outer_height_left = this._layout_offset_subnodes_height( + root_layout_data.left_nodes + ) + } + } + this.bounds.s = Math.max( + root_layout_data.outer_height_left, + root_layout_data.outer_height_right + ) + this.cache_valid = false + } else { + logger.warn('can not found root node') + } + } + set_visible(nodes, visible) { + var i = nodes.length + var node = null + var layout_data = null + while (i--) { + node = nodes[i] + layout_data = node._data.layout + if (node.expanded) { + this.set_visible(node.children, visible) + } else { + this.set_visible(node.children, false) + } + if (!node.isroot) { + node._data.layout.visible = visible + } + } + } + is_expand(node) { + return node.expanded + } + is_visible(node) { + var layout_data = node._data.layout + if ('visible' in layout_data && !layout_data.visible) { + return false + } else { + return true + } + } +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.mind.js b/app/assets/javascripts/mind_map/jsmind/jsmind.mind.js old mode 100644 new mode 100755 index 3129cea..1472a00 --- a/app/assets/javascripts/mind_map/jsmind/jsmind.mind.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.mind.js @@ -1,254 +1,254 @@ -import { Node } from './jsmind.node.js' -import { logger, Direction } from './jsmind.common.js' - -export class Mind { - constructor() { - this.name = null - this.author = null - this.version = null - this.root = null - this.selected = null - this.nodes = {} - } - get_node(node_id) { - if (node_id in this.nodes) { - return this.nodes[node_id] - } else { - logger.warn('the node[id=' + node_id + '] can not be found') - return null - } - } - set_root(node_id, topic, data) { - if (this.root == null) { - this.root = new Node(node_id, 0, topic, data, true) - this._put_node(this.root) - return this.root - } else { - logger.error('root node is already exist') - return null - } - } - add_node(parent_node, node_id, topic, data, direction, expanded, idx) { - if (!Node.is_node(parent_node)) { - logger.error('the parent_node ' + parent_node + ' is not a node.') - return null - } - var node_index = idx || -1 - var node = new Node( - node_id, - node_index, - topic, - data, - false, - parent_node, - parent_node.direction, - expanded - ) - if (parent_node.isroot) { - node.direction = direction || Direction.right - } - if (this._put_node(node)) { - parent_node.children.push(node) - this._update_index(parent_node) - } else { - logger.error("fail, the node id '" + node.id + "' has been already exist.") - node = null - } - return node - } - insert_node_before(node_before, node_id, topic, data, direction) { - if (!Node.is_node(node_before)) { - logger.error('the node_before ' + node_before + ' is not a node.') - return null - } - var node_index = node_before.index - 0.5 - return this.add_node(node_before.parent, node_id, topic, data, direction, true, node_index) - } - get_node_before(node) { - if (!Node.is_node(node)) { - var the_node = this.get_node(node) - if (!the_node) { - logger.error('the node[id=' + node + '] can not be found.') - return null - } else { - return this.get_node_before(the_node) - } - } - if (node.isroot) { - return null - } - var idx = node.index - 2 - if (idx >= 0) { - return node.parent.children[idx] - } else { - return null - } - } - insert_node_after(node_after, node_id, topic, data, direction) { - if (!Node.is_node(node_after)) { - logger.error('the node_after ' + node_after + ' is not a node.') - return null - } - var node_index = node_after.index + 0.5 - return this.add_node(node_after.parent, node_id, topic, data, direction, true, node_index) - } - get_node_after(node) { - if (!Node.is_node(node)) { - var the_node = this.get_node(node) - if (!the_node) { - logger.error('the node[id=' + node + '] can not be found.') - return null - } else { - return this.get_node_after(the_node) - } - } - if (node.isroot) { - return null - } - var idx = node.index - var brothers = node.parent.children - if (brothers.length > idx) { - return node.parent.children[idx] - } else { - return null - } - } - move_node(node, before_id, parent_id, direction) { - if (!Node.is_node(node)) { - logger.error('the parameter node ' + node + ' is not a node.') - return null - } - if (!parent_id) { - parent_id = node.parent.id - } - return this._move_node(node, before_id, parent_id, direction) - } - _flow_node_direction(node, direction) { - if (typeof direction === 'undefined') { - direction = node.direction - } else { - node.direction = direction - } - var len = node.children.length - while (len--) { - this._flow_node_direction(node.children[len], direction) - } - } - _move_node_internal(node, before_id) { - if (!!node && !!before_id) { - if (before_id == '_last_') { - node.index = -1 - this._update_index(node.parent) - } else if (before_id == '_first_') { - node.index = 0 - this._update_index(node.parent) - } else { - var node_before = !!before_id ? this.get_node(before_id) : null - if ( - node_before != null && - node_before.parent != null && - node_before.parent.id == node.parent.id - ) { - node.index = node_before.index - 0.5 - this._update_index(node.parent) - } - } - } - return node - } - _move_node(node, before_id, parent_id, direction) { - if (!!node && !!parent_id) { - var parent_node = this.get_node(parent_id) - if (Node.inherited(node, parent_node)) { - logger.error('can not move a node to its children') - return null - } - if (node.parent.id != parent_id) { - // remove from parent's children - var sibling = node.parent.children - var si = sibling.length - while (si--) { - if (sibling[si].id == node.id) { - sibling.splice(si, 1) - break - } - } - let origin_parent = node.parent - node.parent = parent_node - parent_node.children.push(node) - this._update_index(origin_parent) - } - - if (node.parent.isroot) { - if (direction == Direction.left) { - node.direction = direction - } else { - node.direction = Direction.right - } - } else { - node.direction = node.parent.direction - } - this._move_node_internal(node, before_id) - this._flow_node_direction(node) - } - return node - } - remove_node(node) { - if (!Node.is_node(node)) { - logger.error('the parameter node ' + node + ' is not a node.') - return false - } - if (node.isroot) { - logger.error('fail, can not remove root node') - return false - } - if (this.selected != null && this.selected.id == node.id) { - this.selected = null - } - // clean all subordinate nodes - var children = node.children - var ci = children.length - while (ci--) { - this.remove_node(children[ci]) - } - // clean all children - children.length = 0 - var node_parent = node.parent - // remove from parent's children - var sibling = node_parent.children - var si = sibling.length - while (si--) { - if (sibling[si].id == node.id) { - sibling.splice(si, 1) - break - } - } - // remove from global nodes - delete this.nodes[node.id] - // clean all properties - for (var k in node) { - delete node[k] - } - // remove it's self - node = null - this._update_index(node_parent) - return true - } - _put_node(node) { - if (node.id in this.nodes) { - logger.warn("the node_id '" + node.id + "' has been already exist.") - return false - } else { - this.nodes[node.id] = node - return true - } - } - _update_index(node) { - if (node instanceof Node) { - node.children.sort(Node.compare) - for (var i = 0; i < node.children.length; i++) { - node.children[i].index = i + 1 - } - } - } -} +import { Node } from './jsmind.node.js' +import { logger, Direction } from './jsmind.common.js' + +export class Mind { + constructor() { + this.name = null + this.author = null + this.version = null + this.root = null + this.selected = null + this.nodes = {} + } + get_node(node_id) { + if (node_id in this.nodes) { + return this.nodes[node_id] + } else { + logger.warn('the node[id=' + node_id + '] can not be found') + return null + } + } + set_root(node_id, topic, data) { + if (this.root == null) { + this.root = new Node(node_id, 0, topic, data, true) + this._put_node(this.root) + return this.root + } else { + logger.error('root node is already exist') + return null + } + } + add_node(parent_node, node_id, topic, data, direction, expanded, idx) { + if (!Node.is_node(parent_node)) { + logger.error('the parent_node ' + parent_node + ' is not a node.') + return null + } + var node_index = idx || -1 + var node = new Node( + node_id, + node_index, + topic, + data, + false, + parent_node, + parent_node.direction, + expanded + ) + if (parent_node.isroot) { + node.direction = direction || Direction.right + } + if (this._put_node(node)) { + parent_node.children.push(node) + this._update_index(parent_node) + } else { + logger.error("fail, the node id '" + node.id + "' has been already exist.") + node = null + } + return node + } + insert_node_before(node_before, node_id, topic, data, direction) { + if (!Node.is_node(node_before)) { + logger.error('the node_before ' + node_before + ' is not a node.') + return null + } + var node_index = node_before.index - 0.5 + return this.add_node(node_before.parent, node_id, topic, data, direction, true, node_index) + } + get_node_before(node) { + if (!Node.is_node(node)) { + var the_node = this.get_node(node) + if (!the_node) { + logger.error('the node[id=' + node + '] can not be found.') + return null + } else { + return this.get_node_before(the_node) + } + } + if (node.isroot) { + return null + } + var idx = node.index - 2 + if (idx >= 0) { + return node.parent.children[idx] + } else { + return null + } + } + insert_node_after(node_after, node_id, topic, data, direction) { + if (!Node.is_node(node_after)) { + logger.error('the node_after ' + node_after + ' is not a node.') + return null + } + var node_index = node_after.index + 0.5 + return this.add_node(node_after.parent, node_id, topic, data, direction, true, node_index) + } + get_node_after(node) { + if (!Node.is_node(node)) { + var the_node = this.get_node(node) + if (!the_node) { + logger.error('the node[id=' + node + '] can not be found.') + return null + } else { + return this.get_node_after(the_node) + } + } + if (node.isroot) { + return null + } + var idx = node.index + var brothers = node.parent.children + if (brothers.length > idx) { + return node.parent.children[idx] + } else { + return null + } + } + move_node(node, before_id, parent_id, direction) { + if (!Node.is_node(node)) { + logger.error('the parameter node ' + node + ' is not a node.') + return null + } + if (!parent_id) { + parent_id = node.parent.id + } + return this._move_node(node, before_id, parent_id, direction) + } + _flow_node_direction(node, direction) { + if (typeof direction === 'undefined') { + direction = node.direction + } else { + node.direction = direction + } + var len = node.children.length + while (len--) { + this._flow_node_direction(node.children[len], direction) + } + } + _move_node_internal(node, before_id) { + if (!!node && !!before_id) { + if (before_id == '_last_') { + node.index = -1 + this._update_index(node.parent) + } else if (before_id == '_first_') { + node.index = 0 + this._update_index(node.parent) + } else { + var node_before = !!before_id ? this.get_node(before_id) : null + if ( + node_before != null && + node_before.parent != null && + node_before.parent.id == node.parent.id + ) { + node.index = node_before.index - 0.5 + this._update_index(node.parent) + } + } + } + return node + } + _move_node(node, before_id, parent_id, direction) { + if (!!node && !!parent_id) { + var parent_node = this.get_node(parent_id) + if (Node.inherited(node, parent_node)) { + logger.error('can not move a node to its children') + return null + } + if (node.parent.id != parent_id) { + // remove from parent's children + var sibling = node.parent.children + var si = sibling.length + while (si--) { + if (sibling[si].id == node.id) { + sibling.splice(si, 1) + break + } + } + let origin_parent = node.parent + node.parent = parent_node + parent_node.children.push(node) + this._update_index(origin_parent) + } + + if (node.parent.isroot) { + if (direction == Direction.left) { + node.direction = direction + } else { + node.direction = Direction.right + } + } else { + node.direction = node.parent.direction + } + this._move_node_internal(node, before_id) + this._flow_node_direction(node) + } + return node + } + remove_node(node) { + if (!Node.is_node(node)) { + logger.error('the parameter node ' + node + ' is not a node.') + return false + } + if (node.isroot) { + logger.error('fail, can not remove root node') + return false + } + if (this.selected != null && this.selected.id == node.id) { + this.selected = null + } + // clean all subordinate nodes + var children = node.children + var ci = children.length + while (ci--) { + this.remove_node(children[ci]) + } + // clean all children + children.length = 0 + var node_parent = node.parent + // remove from parent's children + var sibling = node_parent.children + var si = sibling.length + while (si--) { + if (sibling[si].id == node.id) { + sibling.splice(si, 1) + break + } + } + // remove from global nodes + delete this.nodes[node.id] + // clean all properties + for (var k in node) { + delete node[k] + } + // remove it's self + node = null + this._update_index(node_parent) + return true + } + _put_node(node) { + if (node.id in this.nodes) { + logger.warn("the node_id '" + node.id + "' has been already exist.") + return false + } else { + this.nodes[node.id] = node + return true + } + } + _update_index(node) { + if (node instanceof Node) { + node.children.sort(Node.compare) + for (var i = 0; i < node.children.length; i++) { + node.children[i].index = i + 1 + } + } + } +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.node.js b/app/assets/javascripts/mind_map/jsmind/jsmind.node.js old mode 100644 new mode 100755 index 550836d..360fcaa --- a/app/assets/javascripts/mind_map/jsmind/jsmind.node.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.node.js @@ -1,82 +1,82 @@ -import { logger } from './jsmind.common.js' -export class Node { - constructor(sId, iIndex, sTopic, oData, bIsRoot, oParent, eDirection, bExpanded) { - if (!sId) { - logger.error('invalid node id') - return - } - if (typeof iIndex != 'number') { - logger.error('invalid node index') - return - } - if (typeof bExpanded === 'undefined') { - bExpanded = true - } - this.id = sId - this.index = iIndex - this.topic = sTopic - this.data = oData || {} - this.isroot = bIsRoot - this.parent = oParent - this.direction = eDirection - this.expanded = !!bExpanded - this.children = [] - this._data = {} - } - - get_location() { - var vd = this._data.view - return { - x: vd.abs_x, - y: vd.abs_y, - } - } - get_size() { - var vd = this._data.view - return { - w: vd.width, - h: vd.height, - } - } - - static compare(node1, node2) { - // '-1' is always the latest - var r = 0 - var i1 = node1.index - var i2 = node2.index - if (i1 >= 0 && i2 >= 0) { - r = i1 - i2 - } else if (i1 == -1 && i2 == -1) { - r = 0 - } else if (i1 == -1) { - r = 1 - } else if (i2 == -1) { - r = -1 - } else { - r = 0 - } - return r - } - static inherited(parent_node, node) { - if (!!parent_node && !!node) { - if (parent_node.id === node.id) { - return true - } - if (parent_node.isroot) { - return true - } - var pid = parent_node.id - var p = node - while (!p.isroot) { - p = p.parent - if (p.id === pid) { - return true - } - } - } - return false - } - static is_node(n) { - return !!n && n instanceof Node - } -} +import { logger } from './jsmind.common.js' +export class Node { + constructor(sId, iIndex, sTopic, oData, bIsRoot, oParent, eDirection, bExpanded) { + if (!sId) { + logger.error('invalid node id') + return + } + if (typeof iIndex != 'number') { + logger.error('invalid node index') + return + } + if (typeof bExpanded === 'undefined') { + bExpanded = true + } + this.id = sId + this.index = iIndex + this.topic = sTopic + this.data = oData || {} + this.isroot = bIsRoot + this.parent = oParent + this.direction = eDirection + this.expanded = !!bExpanded + this.children = [] + this._data = {} + } + + get_location() { + var vd = this._data.view + return { + x: vd.abs_x, + y: vd.abs_y, + } + } + get_size() { + var vd = this._data.view + return { + w: vd.width, + h: vd.height, + } + } + + static compare(node1, node2) { + // '-1' is always the latest + var r = 0 + var i1 = node1.index + var i2 = node2.index + if (i1 >= 0 && i2 >= 0) { + r = i1 - i2 + } else if (i1 == -1 && i2 == -1) { + r = 0 + } else if (i1 == -1) { + r = 1 + } else if (i2 == -1) { + r = -1 + } else { + r = 0 + } + return r + } + static inherited(parent_node, node) { + if (!!parent_node && !!node) { + if (parent_node.id === node.id) { + return true + } + if (parent_node.isroot) { + return true + } + var pid = parent_node.id + var p = node + while (!p.isroot) { + p = p.parent + if (p.id === pid) { + return true + } + } + } + return false + } + static is_node(n) { + return !!n && n instanceof Node + } +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.option.js b/app/assets/javascripts/mind_map/jsmind/jsmind.option.js old mode 100644 new mode 100755 index 86b37e8..4dbf47e --- a/app/assets/javascripts/mind_map/jsmind/jsmind.option.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.option.js @@ -1,69 +1,69 @@ -import { util } from './jsmind.util.js' - -const default_options = { - container: '', // id of the container - editable: false, // you can change it in your options - theme: null, - mode: 'full', // full or side - support_html: true, - log_level: 'info', - - view: { - engine: 'canvas', - enable_device_pixel_ratio: false, - hmargin: 100, - vmargin: 50, - line_width: 2, - line_color: '#555', - line_style: 'curved', // [straight | curved] - draggable: false, // drag the mind map with your mouse, when it's larger that the container - hide_scrollbars_when_draggable: false, // hide container scrollbars, when mind map is larger than container and draggable option is true. - node_overflow: 'hidden', // [hidden | wrap] - zoom: { - min: 0.5, - max: 2.1, - step: 0.1, - }, - custom_node_render: null, - expander_style: 'char', // [char | number] - }, - layout: { - hspace: 30, - vspace: 20, - pspace: 13, - cousin_space: 0, - }, - default_event_handle: { - enable_mousedown_handle: true, - enable_click_handle: true, - enable_dblclick_handle: true, - enable_mousewheel_handle: true, - }, - shortcut: { - enable: true, - handles: {}, - mapping: { - addchild: [45, 4096 + 13], // Insert, Ctrl+Enter - addbrother: 13, // Enter - editnode: 113, // F2 - delnode: 46, // Delete - toggle: 32, // Space - left: 37, // Left - up: 38, // Up - right: 39, // Right - down: 40, // Down - }, - }, - plugin: {}, -} - -export function merge_option(options) { - var opts = {} - util.json.merge(opts, default_options) - util.json.merge(opts, options) - - if (!opts.container) { - throw new Error('the options.container should not be null or empty.') - } - return opts -} +import { util } from './jsmind.util.js' + +const default_options = { + container: '', // id of the container + editable: false, // you can change it in your options + theme: null, + mode: 'full', // full or side + support_html: true, + log_level: 'info', + + view: { + engine: 'canvas', + enable_device_pixel_ratio: false, + hmargin: 100, + vmargin: 50, + line_width: 2, + line_color: '#555', + line_style: 'curved', // [straight | curved] + draggable: false, // drag the mind map with your mouse, when it's larger that the container + hide_scrollbars_when_draggable: false, // hide container scrollbars, when mind map is larger than container and draggable option is true. + node_overflow: 'hidden', // [hidden | wrap] + zoom: { + min: 0.5, + max: 2.1, + step: 0.1, + }, + custom_node_render: null, + expander_style: 'char', // [char | number] + }, + layout: { + hspace: 30, + vspace: 20, + pspace: 13, + cousin_space: 0, + }, + default_event_handle: { + enable_mousedown_handle: true, + enable_click_handle: true, + enable_dblclick_handle: true, + enable_mousewheel_handle: true, + }, + shortcut: { + enable: true, + handles: {}, + mapping: { + addchild: [45, 4096 + 13], // Insert, Ctrl+Enter + addbrother: 13, // Enter + editnode: 113, // F2 + delnode: 46, // Delete + toggle: 32, // Space + left: 37, // Left + up: 38, // Up + right: 39, // Right + down: 40, // Down + }, + }, + plugin: {}, +} + +export function merge_option(options) { + var opts = {} + util.json.merge(opts, default_options) + util.json.merge(opts, options) + + if (!opts.container) { + throw new Error('the options.container should not be null or empty.') + } + return opts +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.plugin.js b/app/assets/javascripts/mind_map/jsmind/jsmind.plugin.js old mode 100644 new mode 100755 index 59e9b14..13fa558 --- a/app/assets/javascripts/mind_map/jsmind/jsmind.plugin.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.plugin.js @@ -1,39 +1,39 @@ -import { $ } from './jsmind.dom.js' - -const plugin_data = { - plugins: [], -} - -export function register(plugin) { - if (!(plugin instanceof Plugin)) { - throw new Error('can not register plugin, it is not an instance of Plugin') - } - if (plugin_data.plugins.map((p) => p.name).includes(plugin.name)) { - throw new Error('can not register plugin ' + plugin.name + ': plugin name already exist') - } - plugin_data.plugins.push(plugin) -} - -export function apply(jm, options) { - $.w.setTimeout(function () { - _apply(jm, options) - }, 0) -} - -function _apply(jm, options) { - plugin_data.plugins.forEach((p) => p.fn_init(jm, options[p.name])) -} - -export class Plugin { - // function fn_init(jm, options){ } - constructor(name, fn_init) { - if (!name) { - throw new Error('plugin must has a name') - } - if (!fn_init || typeof fn_init !== 'function') { - throw new Error('plugin must has an init function') - } - this.name = name - this.fn_init = fn_init - } -} +import { $ } from './jsmind.dom.js' + +const plugin_data = { + plugins: [], +} + +export function register(plugin) { + if (!(plugin instanceof Plugin)) { + throw new Error('can not register plugin, it is not an instance of Plugin') + } + if (plugin_data.plugins.map((p) => p.name).includes(plugin.name)) { + throw new Error('can not register plugin ' + plugin.name + ': plugin name already exist') + } + plugin_data.plugins.push(plugin) +} + +export function apply(jm, options) { + $.w.setTimeout(function () { + _apply(jm, options) + }, 0) +} + +function _apply(jm, options) { + plugin_data.plugins.forEach((p) => p.fn_init(jm, options[p.name])) +} + +export class Plugin { + // function fn_init(jm, options){ } + constructor(name, fn_init) { + if (!name) { + throw new Error('plugin must has a name') + } + if (!fn_init || typeof fn_init !== 'function') { + throw new Error('plugin must has an init function') + } + this.name = name + this.fn_init = fn_init + } +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.shortcut_provider.js b/app/assets/javascripts/mind_map/jsmind/jsmind.shortcut_provider.js old mode 100644 new mode 100755 index 89a8f05..085c932 --- a/app/assets/javascripts/mind_map/jsmind/jsmind.shortcut_provider.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.shortcut_provider.js @@ -1,188 +1,188 @@ -import { $ } from './jsmind.dom.js' -import { util } from './jsmind.util.js' -import { Direction } from './jsmind.common.js' - -export class ShortcutProvider { - constructor(jm, options) { - this.jm = jm - this.opts = options - this.mapping = options.mapping - this.handles = options.handles - this._newid = null - this._mapping = {} - } - init() { - $.on(this.jm.view.e_panel, 'keydown', this.handler.bind(this)) - - this.handles['addchild'] = this.handle_addchild - this.handles['addbrother'] = this.handle_addbrother - this.handles['editnode'] = this.handle_editnode - this.handles['delnode'] = this.handle_delnode - this.handles['toggle'] = this.handle_toggle - this.handles['up'] = this.handle_up - this.handles['down'] = this.handle_down - this.handles['left'] = this.handle_left - this.handles['right'] = this.handle_right - - for (var handle in this.mapping) { - if (!!this.mapping[handle] && handle in this.handles) { - let keys = this.mapping[handle] - if (!Array.isArray(keys)) { - keys = [keys] - } - for (let key of keys) { - this._mapping[key] = this.handles[handle] - } - } - } - - if (typeof this.opts.id_generator === 'function') { - this._newid = this.opts.id_generator - } else { - this._newid = util.uuid.newid - } - } - enable_shortcut() { - this.opts.enable = true - } - disable_shortcut() { - this.opts.enable = false - } - handler(e) { - if (e.which == 9) { - e.preventDefault() - } //prevent tab to change focus in browser - if (this.jm.view.is_editing()) { - return - } - var evt = e || event - if (!this.opts.enable) { - return true - } - var kc = - evt.keyCode + - (evt.metaKey << 13) + - (evt.ctrlKey << 12) + - (evt.altKey << 11) + - (evt.shiftKey << 10) - if (kc in this._mapping) { - this._mapping[kc].call(this, this.jm, e) - } - } - handle_addchild(_jm, e) { - var selected_node = _jm.get_selected_node() - if (!!selected_node) { - var node_id = this._newid() - var node = _jm.add_node(selected_node, node_id, 'New Node') - if (!!node) { - _jm.select_node(node_id) - _jm.begin_edit(node_id) - } - } - } - handle_addbrother(_jm, e) { - var selected_node = _jm.get_selected_node() - if (!!selected_node && !selected_node.isroot) { - var node_id = this._newid() - var node = _jm.insert_node_after(selected_node, node_id, 'New Node') - if (!!node) { - _jm.select_node(node_id) - _jm.begin_edit(node_id) - } - } - } - handle_editnode(_jm, e) { - var selected_node = _jm.get_selected_node() - if (!!selected_node) { - _jm.begin_edit(selected_node) - } - } - handle_delnode(_jm, e) { - var selected_node = _jm.get_selected_node() - if (!!selected_node && !selected_node.isroot) { - _jm.select_node(selected_node.parent) - _jm.remove_node(selected_node) - } - } - handle_toggle(_jm, e) { - var evt = e || event - var selected_node = _jm.get_selected_node() - if (!!selected_node) { - _jm.toggle_node(selected_node.id) - evt.stopPropagation() - evt.preventDefault() - } - } - handle_up(_jm, e) { - var evt = e || event - var selected_node = _jm.get_selected_node() - if (!!selected_node) { - var up_node = _jm.find_node_before(selected_node) - if (!up_node) { - var np = _jm.find_node_before(selected_node.parent) - if (!!np && np.children.length > 0) { - up_node = np.children[np.children.length - 1] - } - } - if (!!up_node) { - _jm.select_node(up_node) - } - evt.stopPropagation() - evt.preventDefault() - } - } - handle_down(_jm, e) { - var evt = e || event - var selected_node = _jm.get_selected_node() - if (!!selected_node) { - var down_node = _jm.find_node_after(selected_node) - if (!down_node) { - var np = _jm.find_node_after(selected_node.parent) - if (!!np && np.children.length > 0) { - down_node = np.children[0] - } - } - if (!!down_node) { - _jm.select_node(down_node) - } - evt.stopPropagation() - evt.preventDefault() - } - } - handle_left(_jm, e) { - this._handle_direction(_jm, e, Direction.left) - } - handle_right(_jm, e) { - this._handle_direction(_jm, e, Direction.right) - } - _handle_direction(_jm, e, d) { - var evt = e || event - var selected_node = _jm.get_selected_node() - var node = null - if (!!selected_node) { - if (selected_node.isroot) { - var c = selected_node.children - var children = [] - for (var i = 0; i < c.length; i++) { - if (c[i].direction === d) { - children.push(i) - } - } - node = c[children[Math.floor((children.length - 1) / 2)]] - } else if (selected_node.direction === d) { - var children = selected_node.children - var children_count = children.length - if (children_count > 0) { - node = children[Math.floor((children_count - 1) / 2)] - } - } else { - node = selected_node.parent - } - if (!!node) { - _jm.select_node(node) - } - evt.stopPropagation() - evt.preventDefault() - } - } -} +import { $ } from './jsmind.dom.js' +import { util } from './jsmind.util.js' +import { Direction } from './jsmind.common.js' + +export class ShortcutProvider { + constructor(jm, options) { + this.jm = jm + this.opts = options + this.mapping = options.mapping + this.handles = options.handles + this._newid = null + this._mapping = {} + } + init() { + $.on(this.jm.view.e_panel, 'keydown', this.handler.bind(this)) + + this.handles['addchild'] = this.handle_addchild + this.handles['addbrother'] = this.handle_addbrother + this.handles['editnode'] = this.handle_editnode + this.handles['delnode'] = this.handle_delnode + this.handles['toggle'] = this.handle_toggle + this.handles['up'] = this.handle_up + this.handles['down'] = this.handle_down + this.handles['left'] = this.handle_left + this.handles['right'] = this.handle_right + + for (var handle in this.mapping) { + if (!!this.mapping[handle] && handle in this.handles) { + let keys = this.mapping[handle] + if (!Array.isArray(keys)) { + keys = [keys] + } + for (let key of keys) { + this._mapping[key] = this.handles[handle] + } + } + } + + if (typeof this.opts.id_generator === 'function') { + this._newid = this.opts.id_generator + } else { + this._newid = util.uuid.newid + } + } + enable_shortcut() { + this.opts.enable = true + } + disable_shortcut() { + this.opts.enable = false + } + handler(e) { + if (e.which == 9) { + e.preventDefault() + } //prevent tab to change focus in browser + if (this.jm.view.is_editing()) { + return + } + var evt = e || event + if (!this.opts.enable) { + return true + } + var kc = + evt.keyCode + + (evt.metaKey << 13) + + (evt.ctrlKey << 12) + + (evt.altKey << 11) + + (evt.shiftKey << 10) + if (kc in this._mapping) { + this._mapping[kc].call(this, this.jm, e) + } + } + handle_addchild(_jm, e) { + var selected_node = _jm.get_selected_node() + if (!!selected_node) { + var node_id = this._newid() + var node = _jm.add_node(selected_node, node_id, 'New Node') + if (!!node) { + _jm.select_node(node_id) + _jm.begin_edit(node_id) + } + } + } + handle_addbrother(_jm, e) { + var selected_node = _jm.get_selected_node() + if (!!selected_node && !selected_node.isroot) { + var node_id = this._newid() + var node = _jm.insert_node_after(selected_node, node_id, 'New Node') + if (!!node) { + _jm.select_node(node_id) + _jm.begin_edit(node_id) + } + } + } + handle_editnode(_jm, e) { + var selected_node = _jm.get_selected_node() + if (!!selected_node) { + _jm.begin_edit(selected_node) + } + } + handle_delnode(_jm, e) { + var selected_node = _jm.get_selected_node() + if (!!selected_node && !selected_node.isroot) { + _jm.select_node(selected_node.parent) + _jm.remove_node(selected_node) + } + } + handle_toggle(_jm, e) { + var evt = e || event + var selected_node = _jm.get_selected_node() + if (!!selected_node) { + _jm.toggle_node(selected_node.id) + evt.stopPropagation() + evt.preventDefault() + } + } + handle_up(_jm, e) { + var evt = e || event + var selected_node = _jm.get_selected_node() + if (!!selected_node) { + var up_node = _jm.find_node_before(selected_node) + if (!up_node) { + var np = _jm.find_node_before(selected_node.parent) + if (!!np && np.children.length > 0) { + up_node = np.children[np.children.length - 1] + } + } + if (!!up_node) { + _jm.select_node(up_node) + } + evt.stopPropagation() + evt.preventDefault() + } + } + handle_down(_jm, e) { + var evt = e || event + var selected_node = _jm.get_selected_node() + if (!!selected_node) { + var down_node = _jm.find_node_after(selected_node) + if (!down_node) { + var np = _jm.find_node_after(selected_node.parent) + if (!!np && np.children.length > 0) { + down_node = np.children[0] + } + } + if (!!down_node) { + _jm.select_node(down_node) + } + evt.stopPropagation() + evt.preventDefault() + } + } + handle_left(_jm, e) { + this._handle_direction(_jm, e, Direction.left) + } + handle_right(_jm, e) { + this._handle_direction(_jm, e, Direction.right) + } + _handle_direction(_jm, e, d) { + var evt = e || event + var selected_node = _jm.get_selected_node() + var node = null + if (!!selected_node) { + if (selected_node.isroot) { + var c = selected_node.children + var children = [] + for (var i = 0; i < c.length; i++) { + if (c[i].direction === d) { + children.push(i) + } + } + node = c[children[Math.floor((children.length - 1) / 2)]] + } else if (selected_node.direction === d) { + var children = selected_node.children + var children_count = children.length + if (children_count > 0) { + node = children[Math.floor((children_count - 1) / 2)] + } + } else { + node = selected_node.parent + } + if (!!node) { + _jm.select_node(node) + } + evt.stopPropagation() + evt.preventDefault() + } + } +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.util.js b/app/assets/javascripts/mind_map/jsmind/jsmind.util.js old mode 100644 new mode 100755 index 6c98ae3..2434554 --- a/app/assets/javascripts/mind_map/jsmind/jsmind.util.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.util.js @@ -1,94 +1,94 @@ -import { $ } from './jsmind.dom.js' - -export const util = { - file: { - read: function (file_data, fn_callback) { - var reader = new FileReader() - reader.onload = function () { - if (typeof fn_callback === 'function') { - fn_callback(this.result, file_data.name) - } - } - reader.readAsText(file_data) - }, - - save: function (file_data, type, name) { - var blob - if (typeof $.w.Blob === 'function') { - blob = new Blob([file_data], { type: type }) - } else { - var BlobBuilder = - $.w.BlobBuilder || - $.w.MozBlobBuilder || - $.w.WebKitBlobBuilder || - $.w.MSBlobBuilder - var bb = new BlobBuilder() - bb.append(file_data) - blob = bb.getBlob(type) - } - if (navigator.msSaveBlob) { - navigator.msSaveBlob(blob, name) - } else { - var URL = $.w.URL || $.w.webkitURL - var blob_url = URL.createObjectURL(blob) - var anchor = $.c('a') - if ('download' in anchor) { - anchor.style.visibility = 'hidden' - anchor.href = blob_url - anchor.download = name - $.d.body.appendChild(anchor) - var evt = $.d.createEvent('MouseEvents') - evt.initEvent('click', true, true) - anchor.dispatchEvent(evt) - $.d.body.removeChild(anchor) - } else { - location.href = blob_url - } - } - }, - }, - - json: { - json2string: function (json) { - return JSON.stringify(json) - }, - string2json: function (json_str) { - return JSON.parse(json_str) - }, - merge: function (b, a) { - for (var o in a) { - if (o in b) { - if ( - typeof b[o] === 'object' && - Object.prototype.toString.call(b[o]).toLowerCase() == '[object object]' && - !b[o].length - ) { - util.json.merge(b[o], a[o]) - } else { - b[o] = a[o] - } - } else { - b[o] = a[o] - } - } - return b - }, - }, - - uuid: { - newid: function () { - return ( - new Date().getTime().toString(16) + Math.random().toString(16).substring(2) - ).substring(2, 18) - }, - }, - - text: { - is_empty: function (s) { - if (!s) { - return true - } - return s.replace(/\s*/, '').length == 0 - }, - }, -} +import { $ } from './jsmind.dom.js' + +export const util = { + file: { + read: function (file_data, fn_callback) { + var reader = new FileReader() + reader.onload = function () { + if (typeof fn_callback === 'function') { + fn_callback(this.result, file_data.name) + } + } + reader.readAsText(file_data) + }, + + save: function (file_data, type, name) { + var blob + if (typeof $.w.Blob === 'function') { + blob = new Blob([file_data], { type: type }) + } else { + var BlobBuilder = + $.w.BlobBuilder || + $.w.MozBlobBuilder || + $.w.WebKitBlobBuilder || + $.w.MSBlobBuilder + var bb = new BlobBuilder() + bb.append(file_data) + blob = bb.getBlob(type) + } + if (navigator.msSaveBlob) { + navigator.msSaveBlob(blob, name) + } else { + var URL = $.w.URL || $.w.webkitURL + var blob_url = URL.createObjectURL(blob) + var anchor = $.c('a') + if ('download' in anchor) { + anchor.style.visibility = 'hidden' + anchor.href = blob_url + anchor.download = name + $.d.body.appendChild(anchor) + var evt = $.d.createEvent('MouseEvents') + evt.initEvent('click', true, true) + anchor.dispatchEvent(evt) + $.d.body.removeChild(anchor) + } else { + location.href = blob_url + } + } + }, + }, + + json: { + json2string: function (json) { + return JSON.stringify(json) + }, + string2json: function (json_str) { + return JSON.parse(json_str) + }, + merge: function (b, a) { + for (var o in a) { + if (o in b) { + if ( + typeof b[o] === 'object' && + Object.prototype.toString.call(b[o]).toLowerCase() == '[object object]' && + !b[o].length + ) { + util.json.merge(b[o], a[o]) + } else { + b[o] = a[o] + } + } else { + b[o] = a[o] + } + } + return b + }, + }, + + uuid: { + newid: function () { + return ( + new Date().getTime().toString(16) + Math.random().toString(16).substring(2) + ).substring(2, 18) + }, + }, + + text: { + is_empty: function (s) { + if (!s) { + return true + } + return s.replace(/\s*/, '').length == 0 + }, + }, +} diff --git a/app/assets/javascripts/mind_map/jsmind/jsmind.view_provider.js b/app/assets/javascripts/mind_map/jsmind/jsmind.view_provider.js old mode 100644 new mode 100755 index 1cd824d..cebea29 --- a/app/assets/javascripts/mind_map/jsmind/jsmind.view_provider.js +++ b/app/assets/javascripts/mind_map/jsmind/jsmind.view_provider.js @@ -1,662 +1,662 @@ -import { logger, EventType } from './jsmind.common.js' -import { $ } from './jsmind.dom.js' -import { init_graph } from './jsmind.graph.js' -import { util } from './jsmind.util.js' - -export class ViewProvider { - constructor(jm, options) { - this.opts = options - this.jm = jm - this.layout = jm.layout - - this.container = null - this.e_panel = null - this.e_nodes = null - - this.size = { w: 0, h: 0 } - - this.selected_node = null - this.editing_node = null - - this.graph = null - this.render_node = !!options.custom_node_render - ? this._custom_node_render - : this._default_node_render - this.zoom_current = 1 - this.device_pixel_ratio = this.opts.enable_device_pixel_ratio - ? $.w.devicePixelRatio || 1 - : 1 - this._initialized = false - } - init() { - logger.debug(this.opts) - logger.debug('view.init') - - this.container = $.i(this.opts.container) ? this.opts.container : $.g(this.opts.container) - if (!this.container) { - logger.error('the options.view.container was not be found in dom') - return - } - this.graph = init_graph(this, this.opts.engine) - - this.e_panel = $.c('div') - this.e_nodes = $.c('jmnodes') - this.e_editor = $.c('input') - this.e_panel.className = 'jsmind-inner jmnode-overflow-' + this.opts.node_overflow - this.e_panel.tabIndex = 1 - this.e_panel.appendChild(this.graph.element()) - this.e_panel.appendChild(this.e_nodes) - - this.e_editor.className = 'jsmind-editor' - this.e_editor.type = 'text' - - var v = this - $.on(this.e_editor, 'keydown', function (e) { - var evt = e || event - if (evt.keyCode == 13) { - v.edit_node_end() - evt.stopPropagation() - } - }) - $.on(this.e_editor, 'blur', function (e) { - v.edit_node_end() - }) - - this.container.appendChild(this.e_panel) - - if (!this.container.offsetParent) { - new IntersectionObserver((entities, observer) => { - if (entities[0].isIntersecting) { - observer.unobserve(this.e_panel) - this.resize() - } - }).observe(this.e_panel) - } - } - - add_event(obj, event_name, event_handle, capture_by_panel) { - let target = !!capture_by_panel ? this.e_panel : this.e_nodes - $.on(target, event_name, function (e) { - var evt = e || event - event_handle.call(obj, evt) - }) - } - get_binded_nodeid(element) { - if (element == null) { - return null - } - var tagName = element.tagName.toLowerCase() - if (tagName == 'jmnode' || tagName == 'jmexpander') { - return element.getAttribute('nodeid') - } else if (tagName == 'jmnodes' || tagName == 'body' || tagName == 'html') { - return null - } else { - return this.get_binded_nodeid(element.parentElement) - } - } - is_node(element) { - if (element == null) { - return false - } - var tagName = element.tagName.toLowerCase() - if (tagName == 'jmnode') { - return true - } else if (tagName == 'jmnodes' || tagName == 'body' || tagName == 'html') { - return false - } else { - return this.is_node(element.parentElement) - } - } - is_expander(element) { - return element.tagName.toLowerCase() == 'jmexpander' - } - reset() { - logger.debug('view.reset') - this.selected_node = null - this.clear_lines() - this.clear_nodes() - this.reset_theme() - } - reset_theme() { - var theme_name = this.jm.options.theme - if (!!theme_name) { - this.e_nodes.className = 'theme-' + theme_name - } else { - this.e_nodes.className = '' - } - } - reset_custom_style() { - var nodes = this.jm.mind.nodes - for (var nodeid in nodes) { - this.reset_node_custom_style(nodes[nodeid]) - } - } - load() { - logger.debug('view.load') - this.setup_canvas_draggable(this.opts.draggable) - this.init_nodes() - this._initialized = true - } - expand_size() { - var min_size = this.layout.get_min_size() - var min_width = min_size.w + this.opts.hmargin * 2 - var min_height = min_size.h + this.opts.vmargin * 2 - var client_w = this.e_panel.clientWidth - var client_h = this.e_panel.clientHeight - if (client_w < min_width) { - client_w = min_width - } - if (client_h < min_height) { - client_h = min_height - } - this.size.w = client_w - this.size.h = client_h - } - init_nodes_size(node) { - var view_data = node._data.view - view_data.width = view_data.element.clientWidth - view_data.height = view_data.element.clientHeight - } - init_nodes() { - var nodes = this.jm.mind.nodes - var doc_frag = $.d.createDocumentFragment() - for (var nodeid in nodes) { - this.create_node_element(nodes[nodeid], doc_frag) - } - this.e_nodes.appendChild(doc_frag) - - this.run_in_c11y_mode_if_needed(() => { - for (var nodeid in nodes) { - this.init_nodes_size(nodes[nodeid]) - } - }) - } - add_node(node) { - this.create_node_element(node, this.e_nodes) - this.run_in_c11y_mode_if_needed(() => { - this.init_nodes_size(node) - }) - } - run_in_c11y_mode_if_needed(func) { - if (!!this.container.offsetParent) { - func() - return - } - logger.warn( - 'init nodes in compatibility mode. because the container or its parent has style {display:none}. ' - ) - this.e_panel.style.position = 'absolute' - this.e_panel.style.top = '-100000' - $.d.body.appendChild(this.e_panel) - func() - this.container.appendChild(this.e_panel) - this.e_panel.style.position = null - this.e_panel.style.top = null - } - create_node_element(node, parent_node) { - var view_data = null - if ('view' in node._data) { - view_data = node._data.view - } else { - view_data = {} - node._data.view = view_data - } - - var d = $.c('jmnode') - if (node.isroot) { - d.className = 'root' - } else { - var d_e = $.c('jmexpander') - $.t(d_e, '-') - d_e.setAttribute('nodeid', node.id) - d_e.style.visibility = 'hidden' - parent_node.appendChild(d_e) - view_data.expander = d_e - } - if (!!node.topic) { - this.render_node(d, node) - } - d.setAttribute('nodeid', node.id) - d.style.visibility = 'hidden' - this._reset_node_custom_style(d, node.data) - - parent_node.appendChild(d) - view_data.element = d - } - remove_node(node) { - if (this.selected_node != null && this.selected_node.id == node.id) { - this.selected_node = null - } - if (this.editing_node != null && this.editing_node.id == node.id) { - node._data.view.element.removeChild(this.e_editor) - this.editing_node = null - } - var children = node.children - var i = children.length - while (i--) { - this.remove_node(children[i]) - } - if (node._data.view) { - var element = node._data.view.element - var expander = node._data.view.expander - this.e_nodes.removeChild(element) - this.e_nodes.removeChild(expander) - node._data.view.element = null - node._data.view.expander = null - } - } - update_node(node) { - var view_data = node._data.view - var element = view_data.element - if (!!node.topic) { - this.render_node(element, node) - } - if (this.layout.is_visible(node)) { - view_data.width = element.clientWidth - view_data.height = element.clientHeight - } else { - let origin_style = element.getAttribute('style') - element.style = 'visibility: visible; left:0; top:0;' - view_data.width = element.clientWidth - view_data.height = element.clientHeight - element.style = origin_style - } - } - select_node(node) { - if (!!this.selected_node) { - var element = this.selected_node._data.view.element - element.className = element.className.replace(/\s*selected\b/i, '') - this.restore_selected_node_custom_style(this.selected_node) - } - if (!!node) { - this.selected_node = node - node._data.view.element.className += ' selected' - this.clear_selected_node_custom_style(node) - } - } - select_clear() { - this.select_node(null) - } - get_editing_node() { - return this.editing_node - } - is_editing() { - return !!this.editing_node - } - edit_node_begin(node) { - if (!node.topic) { - logger.warn("don't edit image nodes") - return - } - if (this.editing_node != null) { - this.edit_node_end() - } - this.editing_node = node - var view_data = node._data.view - var element = view_data.element - var topic = node.topic - var ncs = getComputedStyle(element) - this.e_editor.value = topic - this.e_editor.style.width = - element.clientWidth - - parseInt(ncs.getPropertyValue('padding-left')) - - parseInt(ncs.getPropertyValue('padding-right')) + - 'px' - element.innerHTML = '' - element.appendChild(this.e_editor) - element.style.zIndex = 5 - this.e_editor.focus() - this.e_editor.select() - } - edit_node_end() { - if (this.editing_node != null) { - var node = this.editing_node - this.editing_node = null - var view_data = node._data.view - var element = view_data.element - var topic = this.e_editor.value - element.style.zIndex = 'auto' - element.removeChild(this.e_editor) - if (util.text.is_empty(topic) || node.topic === topic) { - this.render_node(element, node) - } else { - this.jm.update_node(node.id, topic) - } - } - this.e_panel.focus() - } - get_view_offset() { - var bounds = this.layout.bounds - var _x = (this.size.w - bounds.e - bounds.w) / 2 - var _y = this.size.h / 2 - return { x: _x, y: _y } - } - resize() { - this.graph.set_size(1, 1) - this.e_nodes.style.width = '1px' - this.e_nodes.style.height = '1px' - - this.expand_size() - this._show() - } - _show() { - this.graph.set_size(this.size.w, this.size.h) - this.e_nodes.style.width = this.size.w + 'px' - this.e_nodes.style.height = this.size.h + 'px' - this.show_nodes() - this.show_lines() - //this.layout.cache_valid = true; - this.jm.invoke_event_handle(EventType.resize, { data: [] }) - } - zoom_in(e) { - return this.set_zoom(this.zoom_current + this.opts.zoom.step, e) - } - zoom_out(e) { - return this.set_zoom(this.zoom_current - this.opts.zoom.step, e) - } - set_zoom(zoom, e) { - if (zoom < this.opts.zoom.min || zoom > this.opts.zoom.max) { - return false - } - let e_panel_rect = this.e_panel.getBoundingClientRect() - if ( - zoom < 1 && - zoom < this.zoom_current && - this.size.w * zoom < e_panel_rect.width && - this.size.h * zoom < e_panel_rect.height - ) { - return false - } - let zoom_center = !!e - ? { x: e.x - e_panel_rect.x, y: e.y - e_panel_rect.y } - : { x: e_panel_rect.width / 2, y: e_panel_rect.height / 2 } - let panel_scroll_x = - ((this.e_panel.scrollLeft + zoom_center.x) * zoom) / this.zoom_current - zoom_center.x - let panel_scroll_y = - ((this.e_panel.scrollTop + zoom_center.y) * zoom) / this.zoom_current - zoom_center.y - - this.zoom_current = zoom - for (var i = 0; i < this.e_panel.children.length; i++) { - this.e_panel.children[i].style.zoom = zoom - } - this._show() - this.e_panel.scrollLeft = panel_scroll_x - this.e_panel.scrollTop = panel_scroll_y - return true - } - show(keep_center) { - logger.debug(`view.show: {keep_center: ${keep_center}}`) - this.expand_size() - this._show() - if (!!keep_center) { - this.center_node(this.jm.mind.root) - } - } - relayout() { - this.expand_size() - this._show() - } - save_location(node) { - var vd = node._data.view - vd._saved_location = { - x: parseInt(vd.element.style.left) - this.e_panel.scrollLeft, - y: parseInt(vd.element.style.top) - this.e_panel.scrollTop, - } - } - restore_location(node) { - var vd = node._data.view - this.e_panel.scrollLeft = parseInt(vd.element.style.left) - vd._saved_location.x - this.e_panel.scrollTop = parseInt(vd.element.style.top) - vd._saved_location.y - } - clear_nodes() { - var mind = this.jm.mind - if (mind == null) { - return - } - var nodes = mind.nodes - var node = null - for (var nodeid in nodes) { - node = nodes[nodeid] - node._data.view.element = null - node._data.view.expander = null - } - this.e_nodes.innerHTML = '' - } - show_nodes() { - var nodes = this.jm.mind.nodes - var node = null - var node_element = null - var p = null - var view_data = null - var view_offset = this.get_view_offset() - for (var nodeid in nodes) { - node = nodes[nodeid] - view_data = node._data.view - node_element = view_data.element - if (!this.layout.is_visible(node)) { - node_element.style.display = 'none' - view_data.expander.style.display = 'none' - continue - } - this.reset_node_custom_style(node) - p = this.layout.get_node_point(node) - view_data.abs_x = view_offset.x + p.x - view_data.abs_y = view_offset.y + p.y - node_element.style.left = view_offset.x + p.x + 'px' - node_element.style.top = view_offset.y + p.y + 'px' - node_element.style.display = '' - node_element.style.visibility = 'visible' - this._show_expander(node, view_offset) - } - } - _show_expander(node, view_offset) { - if (node.isroot) { - return - } - - var expander = node._data.view.expander - if (node.children.length == 0) { - expander.style.display = 'none' - expander.style.visibility = 'hidden' - return - } - - let expander_text = this._get_expander_text(node) - $.t(expander, expander_text) - - let p_expander = this.layout.get_expander_point(node) - expander.style.left = view_offset.x + p_expander.x + 'px' - expander.style.top = view_offset.y + p_expander.y + 'px' - expander.style.display = '' - expander.style.visibility = 'visible' - } - - _get_expander_text(node) { - let style = !!this.opts.expander_style ? this.opts.expander_style.toLowerCase() : 'char' - if (style === 'number') { - return node.children.length > 99 ? '...' : node.children.length - } - if (style === 'char') { - return node.expanded ? '-' : '+' - } - } - - _default_node_render(ele, node) { - if (this.opts.support_html) { - $.h(ele, node.topic) - } else { - $.t(ele, node.topic) - } - } - _custom_node_render(ele, node) { - let rendered = this.opts.custom_node_render(this.jm, ele, node) - if (!rendered) { - this._default_node_render(ele, node) - } - } - reset_node_custom_style(node) { - this._reset_node_custom_style(node._data.view.element, node.data) - } - _reset_node_custom_style(node_element, node_data) { - if ('background-color' in node_data) { - node_element.style.backgroundColor = node_data['background-color'] - } - if ('foreground-color' in node_data) { - node_element.style.color = node_data['foreground-color'] - } - if ('width' in node_data) { - node_element.style.width = node_data['width'] + 'px' - } - if ('height' in node_data) { - node_element.style.height = node_data['height'] + 'px' - } - if ('font-size' in node_data) { - node_element.style.fontSize = node_data['font-size'] + 'px' - } - if ('font-weight' in node_data) { - node_element.style.fontWeight = node_data['font-weight'] - } - if ('font-style' in node_data) { - node_element.style.fontStyle = node_data['font-style'] - } - if ('background-image' in node_data) { - var backgroundImage = node_data['background-image'] - if (backgroundImage.startsWith('data') && node_data['width'] && node_data['height']) { - var img = new Image() - - img.onload = function () { - var c = $.c('canvas') - c.width = node_element.clientWidth - c.height = node_element.clientHeight - var img = this - if (c.getContext) { - var ctx = c.getContext('2d') - ctx.drawImage( - img, - 2, - 2, - node_element.clientWidth, - node_element.clientHeight - ) - var scaledImageData = c.toDataURL() - node_element.style.backgroundImage = 'url(' + scaledImageData + ')' - } - } - img.src = backgroundImage - } else { - node_element.style.backgroundImage = 'url(' + backgroundImage + ')' - } - node_element.style.backgroundSize = '99%' - - if ('background-rotation' in node_data) { - node_element.style.transform = 'rotate(' + node_data['background-rotation'] + 'deg)' - } - } - } - restore_selected_node_custom_style(node) { - var node_element = node._data.view.element - var node_data = node.data - if ('background-color' in node_data) { - node_element.style.backgroundColor = node_data['background-color'] - } - if ('foreground-color' in node_data) { - node_element.style.color = node_data['foreground-color'] - } - } - clear_selected_node_custom_style(node) { - var node_element = node._data.view.element - node_element.style.backgroundColor = '' - node_element.style.color = '' - } - clear_lines() { - this.graph.clear() - } - show_lines() { - this.clear_lines() - var nodes = this.jm.mind.nodes - var node = null - var pin = null - var pout = null - var color = null - var _offset = this.get_view_offset() - for (var nodeid in nodes) { - node = nodes[nodeid] - if (!!node.isroot) { - continue - } - if (!this.layout.is_visible(node)) { - continue - } - pin = this.layout.get_node_point_in(node) - pout = this.layout.get_node_point_out(node.parent) - color = node.data['leading-line-color'] - this.graph.draw_line(pout, pin, _offset, color) - } - } - // Drag the whole mind map with your mouse, when it's larger that the container - setup_canvas_draggable(enabled) { - this.opts.draggable = enabled - if (!this._initialized) { - let dragging = false - let x, y - if (this.opts.hide_scrollbars_when_draggable) { - // Avoid scrollbars when mind map is larger than the container (e_panel = id jsmind-inner) - this.e_panel.style = 'overflow: hidden' - } - // Move the whole mind map with mouse moves, while button is down. - $.on(this.container, 'mousedown', (eventDown) => { - if (this.opts.draggable) { - dragging = true - // Record current mouse position. - x = eventDown.clientX - y = eventDown.clientY - } - }) - // Stop moving mind map once mouse button is released. - $.on(this.container, 'mouseup', () => { - dragging = false - }) - // Follow current mouse position and move mind map accordingly. - $.on(this.container, 'mousemove', (eventMove) => { - if (this.opts.draggable) { - if (dragging) { - this.e_panel.scrollBy(x - eventMove.clientX, y - eventMove.clientY) - // Record new current position. - x = eventMove.clientX - y = eventMove.clientY - } - } - }) - } - } - center_node(node) { - if (!this.layout.is_visible(node)) { - logger.warn('can not scroll to the node, because it is invisible') - return false - } - let view_data = node._data.view - let e_panel_rect = this.e_panel.getBoundingClientRect() - let node_center_point = { - x: view_data.abs_x + view_data.width / 2, - y: view_data.abs_y + view_data.height / 2, - } - this.e_panel.scrollTo( - node_center_point.x * this.zoom_current - e_panel_rect.width / 2, - node_center_point.y * this.zoom_current - e_panel_rect.height / 2 - ) - return true - } - - zoomIn(e) { - logger.warn('please use zoom_in instead') - return this.zoom_in(e) - } - zoomOut(e) { - logger.warn('please use zoom_out instead') - return this.zoom_out(e) - } - setZoom(zoom, e) { - logger.warn('please use set_zoom instead') - return this.set_zoom(zoom, e) - } -} +import { logger, EventType } from './jsmind.common.js' +import { $ } from './jsmind.dom.js' +import { init_graph } from './jsmind.graph.js' +import { util } from './jsmind.util.js' + +export class ViewProvider { + constructor(jm, options) { + this.opts = options + this.jm = jm + this.layout = jm.layout + + this.container = null + this.e_panel = null + this.e_nodes = null + + this.size = { w: 0, h: 0 } + + this.selected_node = null + this.editing_node = null + + this.graph = null + this.render_node = !!options.custom_node_render + ? this._custom_node_render + : this._default_node_render + this.zoom_current = 1 + this.device_pixel_ratio = this.opts.enable_device_pixel_ratio + ? $.w.devicePixelRatio || 1 + : 1 + this._initialized = false + } + init() { + logger.debug(this.opts) + logger.debug('view.init') + + this.container = $.i(this.opts.container) ? this.opts.container : $.g(this.opts.container) + if (!this.container) { + logger.error('the options.view.container was not be found in dom') + return + } + this.graph = init_graph(this, this.opts.engine) + + this.e_panel = $.c('div') + this.e_nodes = $.c('jmnodes') + this.e_editor = $.c('input') + this.e_panel.className = 'jsmind-inner jmnode-overflow-' + this.opts.node_overflow + this.e_panel.tabIndex = 1 + this.e_panel.appendChild(this.graph.element()) + this.e_panel.appendChild(this.e_nodes) + + this.e_editor.className = 'jsmind-editor' + this.e_editor.type = 'text' + + var v = this + $.on(this.e_editor, 'keydown', function (e) { + var evt = e || event + if (evt.keyCode == 13) { + v.edit_node_end() + evt.stopPropagation() + } + }) + $.on(this.e_editor, 'blur', function (e) { + v.edit_node_end() + }) + + this.container.appendChild(this.e_panel) + + if (!this.container.offsetParent) { + new IntersectionObserver((entities, observer) => { + if (entities[0].isIntersecting) { + observer.unobserve(this.e_panel) + this.resize() + } + }).observe(this.e_panel) + } + } + + add_event(obj, event_name, event_handle, capture_by_panel) { + let target = !!capture_by_panel ? this.e_panel : this.e_nodes + $.on(target, event_name, function (e) { + var evt = e || event + event_handle.call(obj, evt) + }) + } + get_binded_nodeid(element) { + if (element == null) { + return null + } + var tagName = element.tagName.toLowerCase() + if (tagName == 'jmnode' || tagName == 'jmexpander') { + return element.getAttribute('nodeid') + } else if (tagName == 'jmnodes' || tagName == 'body' || tagName == 'html') { + return null + } else { + return this.get_binded_nodeid(element.parentElement) + } + } + is_node(element) { + if (element == null) { + return false + } + var tagName = element.tagName.toLowerCase() + if (tagName == 'jmnode') { + return true + } else if (tagName == 'jmnodes' || tagName == 'body' || tagName == 'html') { + return false + } else { + return this.is_node(element.parentElement) + } + } + is_expander(element) { + return element.tagName.toLowerCase() == 'jmexpander' + } + reset() { + logger.debug('view.reset') + this.selected_node = null + this.clear_lines() + this.clear_nodes() + this.reset_theme() + } + reset_theme() { + var theme_name = this.jm.options.theme + if (!!theme_name) { + this.e_nodes.className = 'theme-' + theme_name + } else { + this.e_nodes.className = '' + } + } + reset_custom_style() { + var nodes = this.jm.mind.nodes + for (var nodeid in nodes) { + this.reset_node_custom_style(nodes[nodeid]) + } + } + load() { + logger.debug('view.load') + this.setup_canvas_draggable(this.opts.draggable) + this.init_nodes() + this._initialized = true + } + expand_size() { + var min_size = this.layout.get_min_size() + var min_width = min_size.w + this.opts.hmargin * 2 + var min_height = min_size.h + this.opts.vmargin * 2 + var client_w = this.e_panel.clientWidth + var client_h = this.e_panel.clientHeight + if (client_w < min_width) { + client_w = min_width + } + if (client_h < min_height) { + client_h = min_height + } + this.size.w = client_w + this.size.h = client_h + } + init_nodes_size(node) { + var view_data = node._data.view + view_data.width = view_data.element.clientWidth + view_data.height = view_data.element.clientHeight + } + init_nodes() { + var nodes = this.jm.mind.nodes + var doc_frag = $.d.createDocumentFragment() + for (var nodeid in nodes) { + this.create_node_element(nodes[nodeid], doc_frag) + } + this.e_nodes.appendChild(doc_frag) + + this.run_in_c11y_mode_if_needed(() => { + for (var nodeid in nodes) { + this.init_nodes_size(nodes[nodeid]) + } + }) + } + add_node(node) { + this.create_node_element(node, this.e_nodes) + this.run_in_c11y_mode_if_needed(() => { + this.init_nodes_size(node) + }) + } + run_in_c11y_mode_if_needed(func) { + if (!!this.container.offsetParent) { + func() + return + } + logger.warn( + 'init nodes in compatibility mode. because the container or its parent has style {display:none}. ' + ) + this.e_panel.style.position = 'absolute' + this.e_panel.style.top = '-100000' + $.d.body.appendChild(this.e_panel) + func() + this.container.appendChild(this.e_panel) + this.e_panel.style.position = null + this.e_panel.style.top = null + } + create_node_element(node, parent_node) { + var view_data = null + if ('view' in node._data) { + view_data = node._data.view + } else { + view_data = {} + node._data.view = view_data + } + + var d = $.c('jmnode') + if (node.isroot) { + d.className = 'root' + } else { + var d_e = $.c('jmexpander') + $.t(d_e, '-') + d_e.setAttribute('nodeid', node.id) + d_e.style.visibility = 'hidden' + parent_node.appendChild(d_e) + view_data.expander = d_e + } + if (!!node.topic) { + this.render_node(d, node) + } + d.setAttribute('nodeid', node.id) + d.style.visibility = 'hidden' + this._reset_node_custom_style(d, node.data) + + parent_node.appendChild(d) + view_data.element = d + } + remove_node(node) { + if (this.selected_node != null && this.selected_node.id == node.id) { + this.selected_node = null + } + if (this.editing_node != null && this.editing_node.id == node.id) { + node._data.view.element.removeChild(this.e_editor) + this.editing_node = null + } + var children = node.children + var i = children.length + while (i--) { + this.remove_node(children[i]) + } + if (node._data.view) { + var element = node._data.view.element + var expander = node._data.view.expander + this.e_nodes.removeChild(element) + this.e_nodes.removeChild(expander) + node._data.view.element = null + node._data.view.expander = null + } + } + update_node(node) { + var view_data = node._data.view + var element = view_data.element + if (!!node.topic) { + this.render_node(element, node) + } + if (this.layout.is_visible(node)) { + view_data.width = element.clientWidth + view_data.height = element.clientHeight + } else { + let origin_style = element.getAttribute('style') + element.style = 'visibility: visible; left:0; top:0;' + view_data.width = element.clientWidth + view_data.height = element.clientHeight + element.style = origin_style + } + } + select_node(node) { + if (!!this.selected_node) { + var element = this.selected_node._data.view.element + element.className = element.className.replace(/\s*selected\b/i, '') + this.restore_selected_node_custom_style(this.selected_node) + } + if (!!node) { + this.selected_node = node + node._data.view.element.className += ' selected' + this.clear_selected_node_custom_style(node) + } + } + select_clear() { + this.select_node(null) + } + get_editing_node() { + return this.editing_node + } + is_editing() { + return !!this.editing_node + } + edit_node_begin(node) { + if (!node.topic) { + logger.warn("don't edit image nodes") + return + } + if (this.editing_node != null) { + this.edit_node_end() + } + this.editing_node = node + var view_data = node._data.view + var element = view_data.element + var topic = node.topic + var ncs = getComputedStyle(element) + this.e_editor.value = topic + this.e_editor.style.width = + element.clientWidth - + parseInt(ncs.getPropertyValue('padding-left')) - + parseInt(ncs.getPropertyValue('padding-right')) + + 'px' + element.innerHTML = '' + element.appendChild(this.e_editor) + element.style.zIndex = 5 + this.e_editor.focus() + this.e_editor.select() + } + edit_node_end() { + if (this.editing_node != null) { + var node = this.editing_node + this.editing_node = null + var view_data = node._data.view + var element = view_data.element + var topic = this.e_editor.value + element.style.zIndex = 'auto' + element.removeChild(this.e_editor) + if (util.text.is_empty(topic) || node.topic === topic) { + this.render_node(element, node) + } else { + this.jm.update_node(node.id, topic) + } + } + this.e_panel.focus() + } + get_view_offset() { + var bounds = this.layout.bounds + var _x = (this.size.w - bounds.e - bounds.w) / 2 + var _y = this.size.h / 2 + return { x: _x, y: _y } + } + resize() { + this.graph.set_size(1, 1) + this.e_nodes.style.width = '1px' + this.e_nodes.style.height = '1px' + + this.expand_size() + this._show() + } + _show() { + this.graph.set_size(this.size.w, this.size.h) + this.e_nodes.style.width = this.size.w + 'px' + this.e_nodes.style.height = this.size.h + 'px' + this.show_nodes() + this.show_lines() + //this.layout.cache_valid = true; + this.jm.invoke_event_handle(EventType.resize, { data: [] }) + } + zoom_in(e) { + return this.set_zoom(this.zoom_current + this.opts.zoom.step, e) + } + zoom_out(e) { + return this.set_zoom(this.zoom_current - this.opts.zoom.step, e) + } + set_zoom(zoom, e) { + if (zoom < this.opts.zoom.min || zoom > this.opts.zoom.max) { + return false + } + let e_panel_rect = this.e_panel.getBoundingClientRect() + if ( + zoom < 1 && + zoom < this.zoom_current && + this.size.w * zoom < e_panel_rect.width && + this.size.h * zoom < e_panel_rect.height + ) { + return false + } + let zoom_center = !!e + ? { x: e.x - e_panel_rect.x, y: e.y - e_panel_rect.y } + : { x: e_panel_rect.width / 2, y: e_panel_rect.height / 2 } + let panel_scroll_x = + ((this.e_panel.scrollLeft + zoom_center.x) * zoom) / this.zoom_current - zoom_center.x + let panel_scroll_y = + ((this.e_panel.scrollTop + zoom_center.y) * zoom) / this.zoom_current - zoom_center.y + + this.zoom_current = zoom + for (var i = 0; i < this.e_panel.children.length; i++) { + this.e_panel.children[i].style.zoom = zoom + } + this._show() + this.e_panel.scrollLeft = panel_scroll_x + this.e_panel.scrollTop = panel_scroll_y + return true + } + show(keep_center) { + logger.debug(`view.show: {keep_center: ${keep_center}}`) + this.expand_size() + this._show() + if (!!keep_center) { + this.center_node(this.jm.mind.root) + } + } + relayout() { + this.expand_size() + this._show() + } + save_location(node) { + var vd = node._data.view + vd._saved_location = { + x: parseInt(vd.element.style.left) - this.e_panel.scrollLeft, + y: parseInt(vd.element.style.top) - this.e_panel.scrollTop, + } + } + restore_location(node) { + var vd = node._data.view + this.e_panel.scrollLeft = parseInt(vd.element.style.left) - vd._saved_location.x + this.e_panel.scrollTop = parseInt(vd.element.style.top) - vd._saved_location.y + } + clear_nodes() { + var mind = this.jm.mind + if (mind == null) { + return + } + var nodes = mind.nodes + var node = null + for (var nodeid in nodes) { + node = nodes[nodeid] + node._data.view.element = null + node._data.view.expander = null + } + this.e_nodes.innerHTML = '' + } + show_nodes() { + var nodes = this.jm.mind.nodes + var node = null + var node_element = null + var p = null + var view_data = null + var view_offset = this.get_view_offset() + for (var nodeid in nodes) { + node = nodes[nodeid] + view_data = node._data.view + node_element = view_data.element + if (!this.layout.is_visible(node)) { + node_element.style.display = 'none' + view_data.expander.style.display = 'none' + continue + } + this.reset_node_custom_style(node) + p = this.layout.get_node_point(node) + view_data.abs_x = view_offset.x + p.x + view_data.abs_y = view_offset.y + p.y + node_element.style.left = view_offset.x + p.x + 'px' + node_element.style.top = view_offset.y + p.y + 'px' + node_element.style.display = '' + node_element.style.visibility = 'visible' + this._show_expander(node, view_offset) + } + } + _show_expander(node, view_offset) { + if (node.isroot) { + return + } + + var expander = node._data.view.expander + if (node.children.length == 0) { + expander.style.display = 'none' + expander.style.visibility = 'hidden' + return + } + + let expander_text = this._get_expander_text(node) + $.t(expander, expander_text) + + let p_expander = this.layout.get_expander_point(node) + expander.style.left = view_offset.x + p_expander.x + 'px' + expander.style.top = view_offset.y + p_expander.y + 'px' + expander.style.display = '' + expander.style.visibility = 'visible' + } + + _get_expander_text(node) { + let style = !!this.opts.expander_style ? this.opts.expander_style.toLowerCase() : 'char' + if (style === 'number') { + return node.children.length > 99 ? '...' : node.children.length + } + if (style === 'char') { + return node.expanded ? '-' : '+' + } + } + + _default_node_render(ele, node) { + if (this.opts.support_html) { + $.h(ele, node.topic) + } else { + $.t(ele, node.topic) + } + } + _custom_node_render(ele, node) { + let rendered = this.opts.custom_node_render(this.jm, ele, node) + if (!rendered) { + this._default_node_render(ele, node) + } + } + reset_node_custom_style(node) { + this._reset_node_custom_style(node._data.view.element, node.data) + } + _reset_node_custom_style(node_element, node_data) { + if ('background-color' in node_data) { + node_element.style.backgroundColor = node_data['background-color'] + } + if ('foreground-color' in node_data) { + node_element.style.color = node_data['foreground-color'] + } + if ('width' in node_data) { + node_element.style.width = node_data['width'] + 'px' + } + if ('height' in node_data) { + node_element.style.height = node_data['height'] + 'px' + } + if ('font-size' in node_data) { + node_element.style.fontSize = node_data['font-size'] + 'px' + } + if ('font-weight' in node_data) { + node_element.style.fontWeight = node_data['font-weight'] + } + if ('font-style' in node_data) { + node_element.style.fontStyle = node_data['font-style'] + } + if ('background-image' in node_data) { + var backgroundImage = node_data['background-image'] + if (backgroundImage.startsWith('data') && node_data['width'] && node_data['height']) { + var img = new Image() + + img.onload = function () { + var c = $.c('canvas') + c.width = node_element.clientWidth + c.height = node_element.clientHeight + var img = this + if (c.getContext) { + var ctx = c.getContext('2d') + ctx.drawImage( + img, + 2, + 2, + node_element.clientWidth, + node_element.clientHeight + ) + var scaledImageData = c.toDataURL() + node_element.style.backgroundImage = 'url(' + scaledImageData + ')' + } + } + img.src = backgroundImage + } else { + node_element.style.backgroundImage = 'url(' + backgroundImage + ')' + } + node_element.style.backgroundSize = '99%' + + if ('background-rotation' in node_data) { + node_element.style.transform = 'rotate(' + node_data['background-rotation'] + 'deg)' + } + } + } + restore_selected_node_custom_style(node) { + var node_element = node._data.view.element + var node_data = node.data + if ('background-color' in node_data) { + node_element.style.backgroundColor = node_data['background-color'] + } + if ('foreground-color' in node_data) { + node_element.style.color = node_data['foreground-color'] + } + } + clear_selected_node_custom_style(node) { + var node_element = node._data.view.element + node_element.style.backgroundColor = '' + node_element.style.color = '' + } + clear_lines() { + this.graph.clear() + } + show_lines() { + this.clear_lines() + var nodes = this.jm.mind.nodes + var node = null + var pin = null + var pout = null + var color = null + var _offset = this.get_view_offset() + for (var nodeid in nodes) { + node = nodes[nodeid] + if (!!node.isroot) { + continue + } + if (!this.layout.is_visible(node)) { + continue + } + pin = this.layout.get_node_point_in(node) + pout = this.layout.get_node_point_out(node.parent) + color = node.data['leading-line-color'] + this.graph.draw_line(pout, pin, _offset, color) + } + } + // Drag the whole mind map with your mouse, when it's larger that the container + setup_canvas_draggable(enabled) { + this.opts.draggable = enabled + if (!this._initialized) { + let dragging = false + let x, y + if (this.opts.hide_scrollbars_when_draggable) { + // Avoid scrollbars when mind map is larger than the container (e_panel = id jsmind-inner) + this.e_panel.style = 'overflow: hidden' + } + // Move the whole mind map with mouse moves, while button is down. + $.on(this.container, 'mousedown', (eventDown) => { + if (this.opts.draggable) { + dragging = true + // Record current mouse position. + x = eventDown.clientX + y = eventDown.clientY + } + }) + // Stop moving mind map once mouse button is released. + $.on(this.container, 'mouseup', () => { + dragging = false + }) + // Follow current mouse position and move mind map accordingly. + $.on(this.container, 'mousemove', (eventMove) => { + if (this.opts.draggable) { + if (dragging) { + this.e_panel.scrollBy(x - eventMove.clientX, y - eventMove.clientY) + // Record new current position. + x = eventMove.clientX + y = eventMove.clientY + } + } + }) + } + } + center_node(node) { + if (!this.layout.is_visible(node)) { + logger.warn('can not scroll to the node, because it is invisible') + return false + } + let view_data = node._data.view + let e_panel_rect = this.e_panel.getBoundingClientRect() + let node_center_point = { + x: view_data.abs_x + view_data.width / 2, + y: view_data.abs_y + view_data.height / 2, + } + this.e_panel.scrollTo( + node_center_point.x * this.zoom_current - e_panel_rect.width / 2, + node_center_point.y * this.zoom_current - e_panel_rect.height / 2 + ) + return true + } + + zoomIn(e) { + logger.warn('please use zoom_in instead') + return this.zoom_in(e) + } + zoomOut(e) { + logger.warn('please use zoom_out instead') + return this.zoom_out(e) + } + setZoom(zoom, e) { + logger.warn('please use set_zoom instead') + return this.set_zoom(zoom, e) + } +} diff --git a/app/assets/javascripts/mind_map/jsmind/plugins/jsmind.draggable-node.js b/app/assets/javascripts/mind_map/jsmind/plugins/jsmind.draggable-node.js old mode 100644 new mode 100755 index 46076ba..a632266 --- a/app/assets/javascripts/mind_map/jsmind/plugins/jsmind.draggable-node.js +++ b/app/assets/javascripts/mind_map/jsmind/plugins/jsmind.draggable-node.js @@ -1,466 +1,466 @@ -import jsMind from '../jsmind.js' - -if (!jsMind) { - throw new Error('jsMind is not defined') -} - -const $ = jsMind.$ - -const clear_selection = - 'getSelection' in $.w - ? function () { - $.w.getSelection().removeAllRanges() - } - : function () { - $.d.selection.empty() - } - -const DEFAULT_OPTIONS = { - line_width: 5, - line_color: 'rgba(0,0,0,0.3)', - line_color_invalid: 'rgba(255,51,51,0.6)', - lookup_delay: 200, - lookup_interval: 100, - scrolling_trigger_width: 20, - scrolling_step_length: 10, - shadow_node_class_name: 'jsmind-draggable-shadow-node', -} - -class DraggableNode { - constructor(jm, options) { - var opts = {} - jsMind.util.json.merge(opts, DEFAULT_OPTIONS) - jsMind.util.json.merge(opts, options) - - this.version = '0.4.0' - this.jm = jm - this.options = opts - this.e_canvas = null - this.canvas_ctx = null - this.shadow = null - this.shadow_p_x = 0 - this.shadow_p_y = 0 - this.shadow_w = 0 - this.shadow_h = 0 - this.active_node = null - this.target_node = null - this.target_direct = null - this.client_w = 0 - this.client_h = 0 - this.offset_x = 0 - this.offset_y = 0 - this.hlookup_delay = 0 - this.hlookup_timer = 0 - this.capture = false - this.moved = false - this.canvas_draggable = jm.get_view_draggable() - this.view_panel = jm.view.e_panel - this.view_panel_rect = null - } - init() { - this.create_canvas() - this.create_shadow() - this.event_bind() - } - resize() { - this.jm.view.e_nodes.appendChild(this.shadow) - this.e_canvas.width = this.jm.view.size.w - this.e_canvas.height = this.jm.view.size.h - } - create_canvas() { - var c = $.c('canvas') - this.jm.view.e_panel.appendChild(c) - var ctx = c.getContext('2d') - this.e_canvas = c - this.canvas_ctx = ctx - } - create_shadow() { - var s = $.c('jmnode') - s.style.visibility = 'hidden' - s.style.zIndex = '3' - s.style.cursor = 'move' - s.style.opacity = '0.7' - s.className = this.options.shadow_node_class_name - this.shadow = s - } - reset_shadow(el) { - var s = this.shadow.style - this.shadow.innerHTML = el.innerHTML - s.left = el.style.left - s.top = el.style.top - s.width = el.style.width - s.height = el.style.height - s.backgroundImage = el.style.backgroundImage - s.backgroundSize = el.style.backgroundSize - s.transform = el.style.transform - this.shadow_w = this.shadow.clientWidth - this.shadow_h = this.shadow.clientHeight - } - show_shadow() { - if (!this.moved) { - this.shadow.style.visibility = 'visible' - } - } - hide_shadow() { - this.shadow.style.visibility = 'hidden' - } - magnet_shadow(shadow_p, node_p, invalid) { - this.canvas_ctx.lineWidth = this.options.line_width - this.canvas_ctx.strokeStyle = invalid - ? this.options.line_color_invalid - : this.options.line_color - this.canvas_ctx.lineCap = 'round' - this.clear_lines() - this.canvas_lineto(shadow_p.x, shadow_p.y, node_p.x, node_p.y) - } - clear_lines() { - this.canvas_ctx.clearRect(0, 0, this.jm.view.size.w, this.jm.view.size.h) - } - canvas_lineto(x1, y1, x2, y2) { - this.canvas_ctx.beginPath() - this.canvas_ctx.moveTo(x1, y1) - this.canvas_ctx.lineTo(x2, y2) - this.canvas_ctx.stroke() - } - event_bind() { - var jd = this - var container = this.jm.view.container - $.on(container, 'mousedown', function (e) { - if (e.button === 0) { - jd.dragstart.call(jd, e) - } - }) - $.on(container, 'mousemove', function (e) { - if (e.movementX !== 0 || e.movementY !== 0) { - jd.drag.call(jd, e) - } - }) - $.on(container, 'mouseup', function (e) { - jd.dragend.call(jd, e) - }) - $.on(container, 'touchstart', function (e) { - jd.dragstart.call(jd, e) - }) - $.on(container, 'touchmove', function (e) { - jd.drag.call(jd, e) - }) - $.on(container, 'touchend', function (e) { - jd.dragend.call(jd, e) - }) - } - dragstart(e) { - if (!this.jm.get_editable()) { - return - } - if (this.capture) { - return - } - var jview = this.jm.view - if (jview.is_editing()) { - return - } - this.active_node = null - this.view_draggable = this.jm.get_view_draggable() - - var el = this.find_node_element(e.target) - if (!el) { - return - } - if (this.view_draggable) { - this.jm.disable_view_draggable() - } - var nodeid = jview.get_binded_nodeid(el) - if (!!nodeid) { - var node = this.jm.get_node(nodeid) - if (!node.isroot) { - this.reset_shadow(el) - this.view_panel_rect = this.view_panel.getBoundingClientRect() - this.active_node = node - this.offset_x = - (e.clientX || e.touches[0].clientX) / jview.zoom_current - el.offsetLeft - this.offset_y = - (e.clientY || e.touches[0].clientY) / jview.zoom_current - el.offsetTop - this.client_hw = Math.floor(el.clientWidth / 2) - this.client_hh = Math.floor(el.clientHeight / 2) - if (this.hlookup_delay != 0) { - $.w.clearTimeout(this.hlookup_delay) - } - if (this.hlookup_timer != 0) { - $.w.clearInterval(this.hlookup_timer) - } - var jd = this - this.hlookup_delay = $.w.setTimeout(function () { - jd.hlookup_delay = 0 - jd.hlookup_timer = $.w.setInterval(function () { - jd.lookup_target_node.call(jd) - }, jd.options.lookup_interval) - }, this.options.lookup_delay) - jd.capture = true - } - } - } - drag(e) { - if (!this.jm.get_editable()) { - return - } - if (this.capture) { - e.preventDefault() - this.show_shadow() - this.moved = true - clear_selection() - var jview = this.jm.view - var px = (e.clientX || e.touches[0].clientX) / jview.zoom_current - this.offset_x - var py = (e.clientY || e.touches[0].clientY) / jview.zoom_current - this.offset_y - // scrolling container axisY if drag nodes exceeding container - if ( - e.clientY - this.view_panel_rect.top < this.options.scrolling_trigger_width && - this.view_panel.scrollTop > this.options.scrolling_step_length - ) { - this.view_panel.scrollBy(0, -this.options.scrolling_step_length) - this.offset_y += this.options.scrolling_step_length / jview.zoom_current - } else if ( - this.view_panel_rect.bottom - e.clientY < this.options.scrolling_trigger_width && - this.view_panel.scrollTop < - this.view_panel.scrollHeight - - this.view_panel_rect.height - - this.options.scrolling_step_length - ) { - this.view_panel.scrollBy(0, this.options.scrolling_step_length) - this.offset_y -= this.options.scrolling_step_length / jview.zoom_current - } - // scrolling container axisX if drag nodes exceeding container - if ( - e.clientX - this.view_panel_rect.left < this.options.scrolling_trigger_width && - this.view_panel.scrollLeft > this.options.scrolling_step_length - ) { - this.view_panel.scrollBy(-this.options.scrolling_step_length, 0) - this.offset_x += this.options.scrolling_step_length / jview.zoom_current - } else if ( - this.view_panel_rect.right - e.clientX < this.options.scrolling_trigger_width && - this.view_panel.scrollLeft < - this.view_panel.scrollWidth - - this.view_panel_rect.width - - this.options.scrolling_step_length - ) { - this.view_panel.scrollBy(this.options.scrolling_step_length, 0) - this.offset_x -= this.options.scrolling_step_length / jview.zoom_current - } - this.shadow.style.left = px + 'px' - this.shadow.style.top = py + 'px' - clear_selection() - } - } - dragend(e) { - if (!this.jm.get_editable()) { - return - } - if (this.view_draggable) { - this.jm.enable_view_draggable() - } - if (this.capture) { - if (this.hlookup_delay != 0) { - $.w.clearTimeout(this.hlookup_delay) - this.hlookup_delay = 0 - this.clear_lines() - } - if (this.hlookup_timer != 0) { - $.w.clearInterval(this.hlookup_timer) - this.hlookup_timer = 0 - this.clear_lines() - } - if (this.moved) { - var src_node = this.active_node - var target_node = this.target_node - var target_direct = this.target_direct - this.move_node(src_node, target_node, target_direct) - } - this.hide_shadow() - } - this.view_panel_rect = null - this.moved = false - this.capture = false - } - find_node_element(el) { - if ( - !el || - el === this.jm.view.e_nodes || - el === this.jm.view.e_panel || - el === this.jm.view.container - ) { - return null - } - if (el.tagName.toLowerCase() === 'jmnode') { - return el - } - return this.find_node_element(el.parentNode) - } - lookup_target_node() { - let sx = this.shadow.offsetLeft - let sy = this.shadow.offsetTop - if (sx === this.shadow_p_x && sy === this.shadow_p_y) { - return - } - this.shadow_p_x = sx - this.shadow_p_y = sy - - let target_direction = - this.shadow_p_x + this.shadow_w / 2 >= this.get_root_x() - ? jsMind.direction.right - : jsMind.direction.left - let overlapping_node = this.lookup_overlapping_node_parent(target_direction) - let target_node = overlapping_node || this.lookup_close_node(target_direction) - if (!!target_node) { - let points = this.calc_point_of_node(target_node, target_direction) - let invalid = jsMind.node.inherited(this.active_node, target_node) - this.magnet_shadow(points.sp, points.np, invalid) - this.target_node = target_node - this.target_direct = target_direction - } - } - get_root_x() { - let root = this.jm.get_root() - let root_location = root.get_location() - let root_size = root.get_size() - return root_location.x + root_size.w / 2 - } - - lookup_overlapping_node_parent(direction) { - let shadowRect = this.shadow.getBoundingClientRect() - let x = shadowRect.x + (shadowRect.width * (1 - direction)) / 2 - let deltaX = (this.jm.options.layout.hspace + this.jm.options.layout.pspace) * direction - let deltaY = shadowRect.height - let points = [ - [x, shadowRect.y], - [x, shadowRect.y + deltaY / 2], - [x, shadowRect.y + deltaY], - [x + deltaX / 2, shadowRect.y], - [x + deltaX / 2, shadowRect.y + deltaY / 2], - [x + deltaX / 2, shadowRect.y + deltaY], - [x + deltaX, shadowRect.y], - [x + deltaX, shadowRect.y + deltaY / 2], - [x + deltaX, shadowRect.y + deltaY], - ] - for (const p of points) { - let n = this.lookup_node_parent_by_location(p[0], p[1]) - if (!!n) { - return n - } - } - } - - lookup_node_parent_by_location(x, y) { - return $.d - .elementsFromPoint(x, y) - .filter( - (x) => x.tagName === 'JMNODE' && x.className !== this.options.shadow_node_class_name - ) - .map((el) => this.jm.view.get_binded_nodeid(el)) - .map((id) => id && this.jm.mind.nodes[id]) - .map((n) => n && n.parent) - .find((n) => n) - } - - lookup_close_node(direction) { - return Object.values(this.jm.mind.nodes) - .filter((n) => n.direction == direction || n.isroot) - .filter((n) => this.jm.layout.is_visible(n)) - .filter((n) => this.shadow_on_target_side(n, direction)) - .map((n) => ({ node: n, distance: this.shadow_to_node(n, direction) })) - .reduce( - (prev, curr) => { - return prev.distance < curr.distance ? prev : curr - }, - { node: this.jm.get_root(), distance: Number.MAX_VALUE } - ).node - } - - shadow_on_target_side(node, dir) { - return ( - (dir == jsMind.direction.right && this.shadow_to_right_of_node(node) > 0) || - (dir == jsMind.direction.left && this.shadow_to_left_of_node(node) > 0) - ) - } - - shadow_to_right_of_node(node) { - return this.shadow_p_x - node.get_location().x - node.get_size().w - } - - shadow_to_left_of_node(node) { - return node.get_location().x - this.shadow_p_x - this.shadow_w - } - - shadow_to_base_line_of_node(node) { - return this.shadow_p_y + this.shadow_h / 2 - node.get_location().y - node.get_size().h / 2 - } - - shadow_to_node(node, dir) { - let distance_x = - dir === jsMind.direction.right - ? Math.abs(this.shadow_to_right_of_node(node)) - : Math.abs(this.shadow_to_left_of_node(node)) - let distance_y = Math.abs(this.shadow_to_base_line_of_node(node)) - return distance_x + distance_y - } - - calc_point_of_node(node, dir) { - let ns = node.get_size() - let nl = node.get_location() - let node_x = node.isroot - ? nl.x + ns.w / 2 - : nl.x + (ns.w * (1 + dir)) / 2 + this.options.line_width * dir - let node_y = nl.y + ns.h / 2 - let shadow_x = - this.shadow_p_x + (this.shadow_w * (1 - dir)) / 2 - this.options.line_width * dir - let shadow_y = this.shadow_p_y + this.shadow_h / 2 - return { - sp: { x: shadow_x, y: shadow_y }, - np: { x: node_x, y: node_y }, - } - } - - move_node(src_node, target_node, target_direct) { - var shadow_h = this.shadow.offsetTop - if (!!target_node && !!src_node && !jsMind.node.inherited(src_node, target_node)) { - // lookup before_node - var sibling_nodes = target_node.children - var sc = sibling_nodes.length - var node = null - var delta_y = Number.MAX_VALUE - var node_before = null - var beforeid = '_last_' - while (sc--) { - node = sibling_nodes[sc] - if (node.direction == target_direct && node.id != src_node.id) { - var dy = node.get_location().y - shadow_h - if (dy > 0 && dy < delta_y) { - delta_y = dy - node_before = node - beforeid = '_first_' - } - } - } - if (!!node_before) { - beforeid = node_before.id - } - this.jm.move_node(src_node.id, beforeid, target_node.id, target_direct) - } - this.active_node = null - this.target_node = null - this.target_direct = null - } - jm_event_handle(type, data) { - if (type === jsMind.event_type.resize) { - this.resize() - } - } -} - -var draggable_plugin = new jsMind.plugin('draggable_node', function (jm, options) { - var jd = new DraggableNode(jm, options) - jd.init() - jm.add_event_listener(function (type, data) { - jd.jm_event_handle.call(jd, type, data) - }) -}) - -jsMind.register_plugin(draggable_plugin) +import jsMind from '../jsmind.js' + +if (!jsMind) { + throw new Error('jsMind is not defined') +} + +const $ = jsMind.$ + +const clear_selection = + 'getSelection' in $.w + ? function () { + $.w.getSelection().removeAllRanges() + } + : function () { + $.d.selection.empty() + } + +const DEFAULT_OPTIONS = { + line_width: 5, + line_color: 'rgba(0,0,0,0.3)', + line_color_invalid: 'rgba(255,51,51,0.6)', + lookup_delay: 200, + lookup_interval: 100, + scrolling_trigger_width: 20, + scrolling_step_length: 10, + shadow_node_class_name: 'jsmind-draggable-shadow-node', +} + +class DraggableNode { + constructor(jm, options) { + var opts = {} + jsMind.util.json.merge(opts, DEFAULT_OPTIONS) + jsMind.util.json.merge(opts, options) + + this.version = '0.4.0' + this.jm = jm + this.options = opts + this.e_canvas = null + this.canvas_ctx = null + this.shadow = null + this.shadow_p_x = 0 + this.shadow_p_y = 0 + this.shadow_w = 0 + this.shadow_h = 0 + this.active_node = null + this.target_node = null + this.target_direct = null + this.client_w = 0 + this.client_h = 0 + this.offset_x = 0 + this.offset_y = 0 + this.hlookup_delay = 0 + this.hlookup_timer = 0 + this.capture = false + this.moved = false + this.canvas_draggable = jm.get_view_draggable() + this.view_panel = jm.view.e_panel + this.view_panel_rect = null + } + init() { + this.create_canvas() + this.create_shadow() + this.event_bind() + } + resize() { + this.jm.view.e_nodes.appendChild(this.shadow) + this.e_canvas.width = this.jm.view.size.w + this.e_canvas.height = this.jm.view.size.h + } + create_canvas() { + var c = $.c('canvas') + this.jm.view.e_panel.appendChild(c) + var ctx = c.getContext('2d') + this.e_canvas = c + this.canvas_ctx = ctx + } + create_shadow() { + var s = $.c('jmnode') + s.style.visibility = 'hidden' + s.style.zIndex = '3' + s.style.cursor = 'move' + s.style.opacity = '0.7' + s.className = this.options.shadow_node_class_name + this.shadow = s + } + reset_shadow(el) { + var s = this.shadow.style + this.shadow.innerHTML = el.innerHTML + s.left = el.style.left + s.top = el.style.top + s.width = el.style.width + s.height = el.style.height + s.backgroundImage = el.style.backgroundImage + s.backgroundSize = el.style.backgroundSize + s.transform = el.style.transform + this.shadow_w = this.shadow.clientWidth + this.shadow_h = this.shadow.clientHeight + } + show_shadow() { + if (!this.moved) { + this.shadow.style.visibility = 'visible' + } + } + hide_shadow() { + this.shadow.style.visibility = 'hidden' + } + magnet_shadow(shadow_p, node_p, invalid) { + this.canvas_ctx.lineWidth = this.options.line_width + this.canvas_ctx.strokeStyle = invalid + ? this.options.line_color_invalid + : this.options.line_color + this.canvas_ctx.lineCap = 'round' + this.clear_lines() + this.canvas_lineto(shadow_p.x, shadow_p.y, node_p.x, node_p.y) + } + clear_lines() { + this.canvas_ctx.clearRect(0, 0, this.jm.view.size.w, this.jm.view.size.h) + } + canvas_lineto(x1, y1, x2, y2) { + this.canvas_ctx.beginPath() + this.canvas_ctx.moveTo(x1, y1) + this.canvas_ctx.lineTo(x2, y2) + this.canvas_ctx.stroke() + } + event_bind() { + var jd = this + var container = this.jm.view.container + $.on(container, 'mousedown', function (e) { + if (e.button === 0) { + jd.dragstart.call(jd, e) + } + }) + $.on(container, 'mousemove', function (e) { + if (e.movementX !== 0 || e.movementY !== 0) { + jd.drag.call(jd, e) + } + }) + $.on(container, 'mouseup', function (e) { + jd.dragend.call(jd, e) + }) + $.on(container, 'touchstart', function (e) { + jd.dragstart.call(jd, e) + }) + $.on(container, 'touchmove', function (e) { + jd.drag.call(jd, e) + }) + $.on(container, 'touchend', function (e) { + jd.dragend.call(jd, e) + }) + } + dragstart(e) { + if (!this.jm.get_editable()) { + return + } + if (this.capture) { + return + } + var jview = this.jm.view + if (jview.is_editing()) { + return + } + this.active_node = null + this.view_draggable = this.jm.get_view_draggable() + + var el = this.find_node_element(e.target) + if (!el) { + return + } + if (this.view_draggable) { + this.jm.disable_view_draggable() + } + var nodeid = jview.get_binded_nodeid(el) + if (!!nodeid) { + var node = this.jm.get_node(nodeid) + if (!node.isroot) { + this.reset_shadow(el) + this.view_panel_rect = this.view_panel.getBoundingClientRect() + this.active_node = node + this.offset_x = + (e.clientX || e.touches[0].clientX) / jview.zoom_current - el.offsetLeft + this.offset_y = + (e.clientY || e.touches[0].clientY) / jview.zoom_current - el.offsetTop + this.client_hw = Math.floor(el.clientWidth / 2) + this.client_hh = Math.floor(el.clientHeight / 2) + if (this.hlookup_delay != 0) { + $.w.clearTimeout(this.hlookup_delay) + } + if (this.hlookup_timer != 0) { + $.w.clearInterval(this.hlookup_timer) + } + var jd = this + this.hlookup_delay = $.w.setTimeout(function () { + jd.hlookup_delay = 0 + jd.hlookup_timer = $.w.setInterval(function () { + jd.lookup_target_node.call(jd) + }, jd.options.lookup_interval) + }, this.options.lookup_delay) + jd.capture = true + } + } + } + drag(e) { + if (!this.jm.get_editable()) { + return + } + if (this.capture) { + e.preventDefault() + this.show_shadow() + this.moved = true + clear_selection() + var jview = this.jm.view + var px = (e.clientX || e.touches[0].clientX) / jview.zoom_current - this.offset_x + var py = (e.clientY || e.touches[0].clientY) / jview.zoom_current - this.offset_y + // scrolling container axisY if drag nodes exceeding container + if ( + e.clientY - this.view_panel_rect.top < this.options.scrolling_trigger_width && + this.view_panel.scrollTop > this.options.scrolling_step_length + ) { + this.view_panel.scrollBy(0, -this.options.scrolling_step_length) + this.offset_y += this.options.scrolling_step_length / jview.zoom_current + } else if ( + this.view_panel_rect.bottom - e.clientY < this.options.scrolling_trigger_width && + this.view_panel.scrollTop < + this.view_panel.scrollHeight - + this.view_panel_rect.height - + this.options.scrolling_step_length + ) { + this.view_panel.scrollBy(0, this.options.scrolling_step_length) + this.offset_y -= this.options.scrolling_step_length / jview.zoom_current + } + // scrolling container axisX if drag nodes exceeding container + if ( + e.clientX - this.view_panel_rect.left < this.options.scrolling_trigger_width && + this.view_panel.scrollLeft > this.options.scrolling_step_length + ) { + this.view_panel.scrollBy(-this.options.scrolling_step_length, 0) + this.offset_x += this.options.scrolling_step_length / jview.zoom_current + } else if ( + this.view_panel_rect.right - e.clientX < this.options.scrolling_trigger_width && + this.view_panel.scrollLeft < + this.view_panel.scrollWidth - + this.view_panel_rect.width - + this.options.scrolling_step_length + ) { + this.view_panel.scrollBy(this.options.scrolling_step_length, 0) + this.offset_x -= this.options.scrolling_step_length / jview.zoom_current + } + this.shadow.style.left = px + 'px' + this.shadow.style.top = py + 'px' + clear_selection() + } + } + dragend(e) { + if (!this.jm.get_editable()) { + return + } + if (this.view_draggable) { + this.jm.enable_view_draggable() + } + if (this.capture) { + if (this.hlookup_delay != 0) { + $.w.clearTimeout(this.hlookup_delay) + this.hlookup_delay = 0 + this.clear_lines() + } + if (this.hlookup_timer != 0) { + $.w.clearInterval(this.hlookup_timer) + this.hlookup_timer = 0 + this.clear_lines() + } + if (this.moved) { + var src_node = this.active_node + var target_node = this.target_node + var target_direct = this.target_direct + this.move_node(src_node, target_node, target_direct) + } + this.hide_shadow() + } + this.view_panel_rect = null + this.moved = false + this.capture = false + } + find_node_element(el) { + if ( + !el || + el === this.jm.view.e_nodes || + el === this.jm.view.e_panel || + el === this.jm.view.container + ) { + return null + } + if (el.tagName.toLowerCase() === 'jmnode') { + return el + } + return this.find_node_element(el.parentNode) + } + lookup_target_node() { + let sx = this.shadow.offsetLeft + let sy = this.shadow.offsetTop + if (sx === this.shadow_p_x && sy === this.shadow_p_y) { + return + } + this.shadow_p_x = sx + this.shadow_p_y = sy + + let target_direction = + this.shadow_p_x + this.shadow_w / 2 >= this.get_root_x() + ? jsMind.direction.right + : jsMind.direction.left + let overlapping_node = this.lookup_overlapping_node_parent(target_direction) + let target_node = overlapping_node || this.lookup_close_node(target_direction) + if (!!target_node) { + let points = this.calc_point_of_node(target_node, target_direction) + let invalid = jsMind.node.inherited(this.active_node, target_node) + this.magnet_shadow(points.sp, points.np, invalid) + this.target_node = target_node + this.target_direct = target_direction + } + } + get_root_x() { + let root = this.jm.get_root() + let root_location = root.get_location() + let root_size = root.get_size() + return root_location.x + root_size.w / 2 + } + + lookup_overlapping_node_parent(direction) { + let shadowRect = this.shadow.getBoundingClientRect() + let x = shadowRect.x + (shadowRect.width * (1 - direction)) / 2 + let deltaX = (this.jm.options.layout.hspace + this.jm.options.layout.pspace) * direction + let deltaY = shadowRect.height + let points = [ + [x, shadowRect.y], + [x, shadowRect.y + deltaY / 2], + [x, shadowRect.y + deltaY], + [x + deltaX / 2, shadowRect.y], + [x + deltaX / 2, shadowRect.y + deltaY / 2], + [x + deltaX / 2, shadowRect.y + deltaY], + [x + deltaX, shadowRect.y], + [x + deltaX, shadowRect.y + deltaY / 2], + [x + deltaX, shadowRect.y + deltaY], + ] + for (const p of points) { + let n = this.lookup_node_parent_by_location(p[0], p[1]) + if (!!n) { + return n + } + } + } + + lookup_node_parent_by_location(x, y) { + return $.d + .elementsFromPoint(x, y) + .filter( + (x) => x.tagName === 'JMNODE' && x.className !== this.options.shadow_node_class_name + ) + .map((el) => this.jm.view.get_binded_nodeid(el)) + .map((id) => id && this.jm.mind.nodes[id]) + .map((n) => n && n.parent) + .find((n) => n) + } + + lookup_close_node(direction) { + return Object.values(this.jm.mind.nodes) + .filter((n) => n.direction == direction || n.isroot) + .filter((n) => this.jm.layout.is_visible(n)) + .filter((n) => this.shadow_on_target_side(n, direction)) + .map((n) => ({ node: n, distance: this.shadow_to_node(n, direction) })) + .reduce( + (prev, curr) => { + return prev.distance < curr.distance ? prev : curr + }, + { node: this.jm.get_root(), distance: Number.MAX_VALUE } + ).node + } + + shadow_on_target_side(node, dir) { + return ( + (dir == jsMind.direction.right && this.shadow_to_right_of_node(node) > 0) || + (dir == jsMind.direction.left && this.shadow_to_left_of_node(node) > 0) + ) + } + + shadow_to_right_of_node(node) { + return this.shadow_p_x - node.get_location().x - node.get_size().w + } + + shadow_to_left_of_node(node) { + return node.get_location().x - this.shadow_p_x - this.shadow_w + } + + shadow_to_base_line_of_node(node) { + return this.shadow_p_y + this.shadow_h / 2 - node.get_location().y - node.get_size().h / 2 + } + + shadow_to_node(node, dir) { + let distance_x = + dir === jsMind.direction.right + ? Math.abs(this.shadow_to_right_of_node(node)) + : Math.abs(this.shadow_to_left_of_node(node)) + let distance_y = Math.abs(this.shadow_to_base_line_of_node(node)) + return distance_x + distance_y + } + + calc_point_of_node(node, dir) { + let ns = node.get_size() + let nl = node.get_location() + let node_x = node.isroot + ? nl.x + ns.w / 2 + : nl.x + (ns.w * (1 + dir)) / 2 + this.options.line_width * dir + let node_y = nl.y + ns.h / 2 + let shadow_x = + this.shadow_p_x + (this.shadow_w * (1 - dir)) / 2 - this.options.line_width * dir + let shadow_y = this.shadow_p_y + this.shadow_h / 2 + return { + sp: { x: shadow_x, y: shadow_y }, + np: { x: node_x, y: node_y }, + } + } + + move_node(src_node, target_node, target_direct) { + var shadow_h = this.shadow.offsetTop + if (!!target_node && !!src_node && !jsMind.node.inherited(src_node, target_node)) { + // lookup before_node + var sibling_nodes = target_node.children + var sc = sibling_nodes.length + var node = null + var delta_y = Number.MAX_VALUE + var node_before = null + var beforeid = '_last_' + while (sc--) { + node = sibling_nodes[sc] + if (node.direction == target_direct && node.id != src_node.id) { + var dy = node.get_location().y - shadow_h + if (dy > 0 && dy < delta_y) { + delta_y = dy + node_before = node + beforeid = '_first_' + } + } + } + if (!!node_before) { + beforeid = node_before.id + } + this.jm.move_node(src_node.id, beforeid, target_node.id, target_direct) + } + this.active_node = null + this.target_node = null + this.target_direct = null + } + jm_event_handle(type, data) { + if (type === jsMind.event_type.resize) { + this.resize() + } + } +} + +var draggable_plugin = new jsMind.plugin('draggable_node', function (jm, options) { + var jd = new DraggableNode(jm, options) + jd.init() + jm.add_event_listener(function (type, data) { + jd.jm_event_handle.call(jd, type, data) + }) +}) + +jsMind.register_plugin(draggable_plugin) diff --git a/app/assets/javascripts/mind_map/jsmind/plugins/jsmind.screenshot.js b/app/assets/javascripts/mind_map/jsmind/plugins/jsmind.screenshot.js old mode 100644 new mode 100755 index 340bdda..ccdc449 --- a/app/assets/javascripts/mind_map/jsmind/plugins/jsmind.screenshot.js +++ b/app/assets/javascripts/mind_map/jsmind/plugins/jsmind.screenshot.js @@ -1,158 +1,158 @@ -import jsMind from 'jsmind' -import domtoimage from 'dom-to-image' - -if (!jsMind) { - throw new Error('jsMind is not defined') -} - -if (!domtoimage) { - throw new Error('dom-to-image is required') -} - -const $ = jsMind.$ - -const DEFAULT_OPTIONS = { - filename: null, - watermark: { - left: $.w.location, - right: 'https://github.com/hizzgdev/jsmind', - }, - background: 'transparent', -} - -class JmScreenshot { - constructor(jm, options) { - var opts = {} - jsMind.util.json.merge(opts, DEFAULT_OPTIONS) - jsMind.util.json.merge(opts, options) - - this.version = '0.2.0' - this.jm = jm - this.options = opts - this.dpr = jm.view.device_pixel_ratio - } - - shoot() { - let c = this.create_canvas() - let ctx = c.getContext('2d') - ctx.scale(this.dpr, this.dpr) - Promise.resolve(ctx) - .then(() => this.draw_background(ctx)) - .then(() => this.draw_lines(ctx)) - .then(() => this.draw_nodes(ctx)) - .then(() => this.draw_watermark(c, ctx)) - .then(() => this.download(c)) - .then(() => this.clear(c)) - } - - create_canvas() { - let c = $.c('canvas') - const w = this.jm.view.size.w - const h = this.jm.view.size.h - c.width = w * this.dpr - c.height = h * this.dpr - c.style.width = w + 'px' - c.style.height = h + 'px' - - c.style.visibility = 'hidden' - this.jm.view.e_panel.appendChild(c) - return c - } - - clear(c) { - c.parentNode.removeChild(c) - } - - draw_background(ctx) { - return new Promise( - function (resolve, _) { - const bg = this.options.background - if (!!bg && bg !== 'transparent') { - ctx.fillStyle = this.options.background - ctx.fillRect(0, 0, this.jm.view.size.w, this.jm.view.size.h) - } - resolve(ctx) - }.bind(this) - ) - } - - draw_lines(ctx) { - return new Promise( - function (resolve, _) { - this.jm.view.graph.copy_to(ctx, function () { - resolve(ctx) - }) - }.bind(this) - ) - } - - draw_nodes(ctx) { - return domtoimage - .toSvg(this.jm.view.e_nodes, { style: { zoom: 1 } }) - .then(this.load_image) - .then(function (img) { - ctx.drawImage(img, 0, 0) - return ctx - }) - } - - draw_watermark(c, ctx) { - ctx.textBaseline = 'bottom' - ctx.fillStyle = '#000' - ctx.font = '11px Verdana,Arial,Helvetica,sans-serif' - if (!!this.options.watermark.left) { - ctx.textAlign = 'left' - ctx.fillText(this.options.watermark.left, 5.5, c.height - 2.5) - } - if (!!this.options.watermark.right) { - ctx.textAlign = 'right' - ctx.fillText(this.options.watermark.right, c.width - 5.5, c.height - 2.5) - } - return ctx - } - - load_image(url) { - return new Promise(function (resolve, reject) { - let img = new Image() - img.onload = function () { - resolve(img) - } - img.onerror = reject - img.src = url - }) - } - - download(c) { - var name = (this.options.filename || this.jm.mind.name) + '.png' - - if (navigator.msSaveBlob && !!c.msToBlob) { - var blob = c.msToBlob() - navigator.msSaveBlob(blob, name) - } else { - var blob_url = c.toDataURL() - var anchor = $.c('a') - if ('download' in anchor) { - anchor.style.visibility = 'hidden' - anchor.href = blob_url - anchor.download = name - $.d.body.appendChild(anchor) - var evt = $.d.createEvent('MouseEvents') - evt.initEvent('click', true, true) - anchor.dispatchEvent(evt) - $.d.body.removeChild(anchor) - } else { - location.href = blob_url - } - } - } -} - -let screenshot_plugin = new jsMind.plugin('screenshot', function (jm, options) { - var jmss = new JmScreenshot(jm, options) - jm.screenshot = jmss - jm.shoot = function () { - jmss.shoot() - } -}) - -jsMind.register_plugin(screenshot_plugin) +import jsMind from 'jsmind' +import domtoimage from 'dom-to-image' + +if (!jsMind) { + throw new Error('jsMind is not defined') +} + +if (!domtoimage) { + throw new Error('dom-to-image is required') +} + +const $ = jsMind.$ + +const DEFAULT_OPTIONS = { + filename: null, + watermark: { + left: $.w.location, + right: 'https://github.com/hizzgdev/jsmind', + }, + background: 'transparent', +} + +class JmScreenshot { + constructor(jm, options) { + var opts = {} + jsMind.util.json.merge(opts, DEFAULT_OPTIONS) + jsMind.util.json.merge(opts, options) + + this.version = '0.2.0' + this.jm = jm + this.options = opts + this.dpr = jm.view.device_pixel_ratio + } + + shoot() { + let c = this.create_canvas() + let ctx = c.getContext('2d') + ctx.scale(this.dpr, this.dpr) + Promise.resolve(ctx) + .then(() => this.draw_background(ctx)) + .then(() => this.draw_lines(ctx)) + .then(() => this.draw_nodes(ctx)) + .then(() => this.draw_watermark(c, ctx)) + .then(() => this.download(c)) + .then(() => this.clear(c)) + } + + create_canvas() { + let c = $.c('canvas') + const w = this.jm.view.size.w + const h = this.jm.view.size.h + c.width = w * this.dpr + c.height = h * this.dpr + c.style.width = w + 'px' + c.style.height = h + 'px' + + c.style.visibility = 'hidden' + this.jm.view.e_panel.appendChild(c) + return c + } + + clear(c) { + c.parentNode.removeChild(c) + } + + draw_background(ctx) { + return new Promise( + function (resolve, _) { + const bg = this.options.background + if (!!bg && bg !== 'transparent') { + ctx.fillStyle = this.options.background + ctx.fillRect(0, 0, this.jm.view.size.w, this.jm.view.size.h) + } + resolve(ctx) + }.bind(this) + ) + } + + draw_lines(ctx) { + return new Promise( + function (resolve, _) { + this.jm.view.graph.copy_to(ctx, function () { + resolve(ctx) + }) + }.bind(this) + ) + } + + draw_nodes(ctx) { + return domtoimage + .toSvg(this.jm.view.e_nodes, { style: { zoom: 1 } }) + .then(this.load_image) + .then(function (img) { + ctx.drawImage(img, 0, 0) + return ctx + }) + } + + draw_watermark(c, ctx) { + ctx.textBaseline = 'bottom' + ctx.fillStyle = '#000' + ctx.font = '11px Verdana,Arial,Helvetica,sans-serif' + if (!!this.options.watermark.left) { + ctx.textAlign = 'left' + ctx.fillText(this.options.watermark.left, 5.5, c.height - 2.5) + } + if (!!this.options.watermark.right) { + ctx.textAlign = 'right' + ctx.fillText(this.options.watermark.right, c.width - 5.5, c.height - 2.5) + } + return ctx + } + + load_image(url) { + return new Promise(function (resolve, reject) { + let img = new Image() + img.onload = function () { + resolve(img) + } + img.onerror = reject + img.src = url + }) + } + + download(c) { + var name = (this.options.filename || this.jm.mind.name) + '.png' + + if (navigator.msSaveBlob && !!c.msToBlob) { + var blob = c.msToBlob() + navigator.msSaveBlob(blob, name) + } else { + var blob_url = c.toDataURL() + var anchor = $.c('a') + if ('download' in anchor) { + anchor.style.visibility = 'hidden' + anchor.href = blob_url + anchor.download = name + $.d.body.appendChild(anchor) + var evt = $.d.createEvent('MouseEvents') + evt.initEvent('click', true, true) + anchor.dispatchEvent(evt) + $.d.body.removeChild(anchor) + } else { + location.href = blob_url + } + } + } +} + +let screenshot_plugin = new jsMind.plugin('screenshot', function (jm, options) { + var jmss = new JmScreenshot(jm, options) + jm.screenshot = jmss + jm.shoot = function () { + jmss.shoot() + } +}) + +jsMind.register_plugin(screenshot_plugin) diff --git a/app/assets/javascripts/mind_map/utils/custom.config.js b/app/assets/javascripts/mind_map/utils/custom.config.js old mode 100644 new mode 100755 index 09cd018..ac54e2b --- a/app/assets/javascripts/mind_map/utils/custom.config.js +++ b/app/assets/javascripts/mind_map/utils/custom.config.js @@ -1,103 +1,103 @@ -// 顏色選項集中管理 -// Color options management -export const PALETTE_COLORS = [ - '#c93a42', - '#fbefdb', - '#0000FF', - '#06c755', - '#FFBF48', - '#1e5b9e', - '#000000', - '#666666', - '#999999', - '#FFFFFF', - // 紅色系(Morandi Red) - '#c8a39e', - '#b4746c', - '#a16666', - '#d3a29d', - '#8e5d5a', - - // 橘色系(Morandi Orange) - '#d4a186', - '#e1b7a7', - '#c98e63', - '#e5b58e', - '#b57758', - - // 黃色系(Morandi Yellow) - '#d8c29d', - '#e6d3aa', - '#c4b07c', - '#e2c892', - '#a8985c', - - // 綠色系(Morandi Green) - '#a3b1a8', - '#8ca39b', - '#9fb7ad', - '#b0c0ae', - '#798d87', - - // 藍色系(Morandi Blue) - '#9ca8b8', - '#a0b1c2', - '#8193a8', - '#6e7d91', - '#c0c8d2', - - '#b8a9c9', - '#c1adc8', - '#a68ca9', - '#cabed4', - '#8f799e', - - '#a0a0a0', - '#bcbcbc', - '#8c8c8c', - '#747474', - '#5e5e5e', -] - -// 心智圖初始數據 -export const INITIAL_MIND = { - meta: {}, - format: 'node_array', - data: [ - { - id: 'root', - topic: 'FirstNode', - expanded: true, - isroot: true, - }, - ], -} - -// 模擬打 API -// Simulate an API call to search based on the query -export async function mockSearchApi(query, tableUID) { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open("GET", '/admin/universal_tables/get_entries?uid=' + tableUID + "&q=" + query + "&links=true"); - xhr.setRequestHeader("Accept", "application/json"); - - xhr.onload = function () { - if (xhr.status >= 200 && xhr.status < 300) { - try { - const data = JSON.parse(xhr.responseText); - resolve(data); // success - } catch (e) { - reject(new Error("Invalid JSON response")); - } - } else { - reject(new Error(`Request failed with status ${xhr.status}`)); - } - }; - - xhr.onerror = function () { - reject(new Error("Network error")); - }; - - xhr.send(); - }); -} +// 顏色選項集中管理 +// Color options management +export const PALETTE_COLORS = [ + '#c93a42', + '#fbefdb', + '#0000FF', + '#06c755', + '#FFBF48', + '#1e5b9e', + '#000000', + '#666666', + '#999999', + '#FFFFFF', + // 紅色系(Morandi Red) + '#c8a39e', + '#b4746c', + '#a16666', + '#d3a29d', + '#8e5d5a', + + // 橘色系(Morandi Orange) + '#d4a186', + '#e1b7a7', + '#c98e63', + '#e5b58e', + '#b57758', + + // 黃色系(Morandi Yellow) + '#d8c29d', + '#e6d3aa', + '#c4b07c', + '#e2c892', + '#a8985c', + + // 綠色系(Morandi Green) + '#a3b1a8', + '#8ca39b', + '#9fb7ad', + '#b0c0ae', + '#798d87', + + // 藍色系(Morandi Blue) + '#9ca8b8', + '#a0b1c2', + '#8193a8', + '#6e7d91', + '#c0c8d2', + + '#b8a9c9', + '#c1adc8', + '#a68ca9', + '#cabed4', + '#8f799e', + + '#a0a0a0', + '#bcbcbc', + '#8c8c8c', + '#747474', + '#5e5e5e', +] + +// 心智圖初始數據 +export const INITIAL_MIND = { + meta: {}, + format: 'node_array', + data: [ + { + id: 'root', + topic: 'FirstNode', + expanded: true, + isroot: true, + }, + ], +} + +// 模擬打 API +// Simulate an API call to search based on the query +export async function mockSearchApi(query, tableUID) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open("GET", '/admin/universal_tables/get_entries?uid=' + tableUID + "&q=" + query + "&links=true"); + xhr.setRequestHeader("Accept", "application/json"); + + xhr.onload = function () { + if (xhr.status >= 200 && xhr.status < 300) { + try { + const data = JSON.parse(xhr.responseText); + resolve(data); // success + } catch (e) { + reject(new Error("Invalid JSON response")); + } + } else { + reject(new Error(`Request failed with status ${xhr.status}`)); + } + }; + + xhr.onerror = function () { + reject(new Error("Network error")); + }; + + xhr.send(); + }); +} diff --git a/app/assets/javascripts/mind_map/utils/custom.main.js b/app/assets/javascripts/mind_map/utils/custom.main.js old mode 100644 new mode 100755 index e29c3de..0aacfbc --- a/app/assets/javascripts/mind_map/utils/custom.main.js +++ b/app/assets/javascripts/mind_map/utils/custom.main.js @@ -1,51 +1,51 @@ -import jsMind from '../jsmind/jsmind.js' -import '../jsmind/plugins/jsmind.draggable-node.js' -import { JsmindSearch } from './custom.search.js' -import { JsmindToolbar } from './custom.toolbar.js' -import { mockSearchApi } from './custom.config.js' - -/** - * 初始化 jsMind 心智圖 - * Initialize jsMind mind map - * @param {Object} mind - 心智圖的資料 (Mind map data) - * @param {Object} options - 配置選項 (Configuration options) - * @param {boolean} isEditable - 是否可編輯 (Is editable) - * @returns {Object} - jsMind 實例 (jsMind instance) - */ -export function initJsmind(mind, options, isEditable) { - const container = document.getElementById(options.container) - container.innerHTML = '' - - options.editable = isEditable - const jm = new jsMind(options) - - // 依據是否可編輯調整顯示為連結或文字 - // Adjust display as a link or text based on editability - const formattedData = mind.data.map((node) => { - node.topic = - !isEditable && node.link - ? `${node.text}` - : node.text || node.topic - return node - }) - jm.show({ meta: mind.meta, format: 'node_array', data: formattedData }) - - // 掛載附加模組(遠程搜尋 & 工具列) - // Attach additional modules (Remote search & Toolbar) - if (isEditable) { - new JsmindSearch(jm, mockSearchApi, options.tableUID); - new JsmindToolbar(jm, options) - } - - return jm -} - -/** - * 獲取當前心智圖數據 - * Get the current mind map data - * @param {Object} jm - jsMind 實例 (jsMind instance) - * @returns {Object} - 心智圖數據 (Mind map data) - */ -export function getJsmindData(jm) { - return jm.get_data('node_array') -} +import jsMind from '../jsmind/jsmind.js' +import '../jsmind/plugins/jsmind.draggable-node.js' +import { JsmindSearch } from './custom.search.js' +import { JsmindToolbar } from './custom.toolbar.js' +import { mockSearchApi } from './custom.config.js' + +/** + * 初始化 jsMind 心智圖 + * Initialize jsMind mind map + * @param {Object} mind - 心智圖的資料 (Mind map data) + * @param {Object} options - 配置選項 (Configuration options) + * @param {boolean} isEditable - 是否可編輯 (Is editable) + * @returns {Object} - jsMind 實例 (jsMind instance) + */ +export function initJsmind(mind, options, isEditable) { + const container = document.getElementById(options.container) + container.innerHTML = '' + + options.editable = isEditable + const jm = new jsMind(options) + + // 依據是否可編輯調整顯示為連結或文字 + // Adjust display as a link or text based on editability + const formattedData = mind.data.map((node) => { + node.topic = + !isEditable && node.link + ? `${node.text}` + : node.text || node.topic + return node + }) + jm.show({ meta: mind.meta, format: 'node_array', data: formattedData }) + + // 掛載附加模組(遠程搜尋 & 工具列) + // Attach additional modules (Remote search & Toolbar) + if (isEditable) { + new JsmindSearch(jm, mockSearchApi, options.tableUID); + new JsmindToolbar(jm, options) + } + + return jm +} + +/** + * 獲取當前心智圖數據 + * Get the current mind map data + * @param {Object} jm - jsMind 實例 (jsMind instance) + * @returns {Object} - 心智圖數據 (Mind map data) + */ +export function getJsmindData(jm) { + return jm.get_data('node_array') +} diff --git a/app/assets/javascripts/mind_map/utils/custom.overrides.js b/app/assets/javascripts/mind_map/utils/custom.overrides.js old mode 100644 new mode 100755 index cb7c725..06a409d --- a/app/assets/javascripts/mind_map/utils/custom.overrides.js +++ b/app/assets/javascripts/mind_map/utils/custom.overrides.js @@ -1,74 +1,74 @@ -import { util } from '../jsmind/jsmind.util.js' -import { Mind } from '../jsmind/jsmind.mind.js' -import { ViewProvider } from '../jsmind/jsmind.view_provider.js' -import jsMind from '../jsmind/jsmind.js' - -ViewProvider.prototype.edit_node_end = function () { - if (this.editing_node != null) { - var node = this.editing_node - this.editing_node = null - var view_data = node._data.view - var element = view_data.element - - // 客製化修改:顯示文字由 node.data 控制 - // Customization: Display text is controlled by node.data - var topic = node.data.text - element.style.zIndex = 'auto' - element.removeChild(this.e_editor) - if (util.text.is_empty(topic) || node.topic === topic) { - this.render_node(element, node) - } else { - this.jm.update_node(node.id, topic) - } - } - this.e_panel.focus() -} - -ViewProvider.prototype.select_node = function (node) { - if (!!this.selected_node) { - var element = this.selected_node._data.view.element - element.className = element.className.replace(/\s*selected\b/i, '') - this.restore_selected_node_custom_style(this.selected_node) - } - if (!!node) { - this.selected_node = node - node._data.view.element.className += ' selected' - // 客製化修改:不清除自定義樣式 - // Customization: Do not clear custom styles - // this.clear_selected_node_custom_style(node) - } -} - -const originalAddNode = Mind.prototype.add_node -Mind.prototype.add_node = function ( - parent_node, - node_id, - topic, - data = undefined, - direction, - expanded, - idx -) { - if (data == undefined) { - data = {} - for (let style of [ - 'leading-line-color', - 'background-color', - 'foreground-color', - ]) { - if (data[style] == undefined && parent_node.data?.[style]) { - data[style] = parent_node.data[style] - } - } - } - arguments[3] = data - - return originalAddNode.apply(this, arguments) -} - -const originalMousedownHandle = jsMind.prototype.mousedown_handle -jsMind.prototype.mousedown_handle = function (e) { - if (e.button !== 0) return - - return originalMousedownHandle.apply(this, arguments) -} +import { util } from '../jsmind/jsmind.util.js' +import { Mind } from '../jsmind/jsmind.mind.js' +import { ViewProvider } from '../jsmind/jsmind.view_provider.js' +import jsMind from '../jsmind/jsmind.js' + +ViewProvider.prototype.edit_node_end = function () { + if (this.editing_node != null) { + var node = this.editing_node + this.editing_node = null + var view_data = node._data.view + var element = view_data.element + + // 客製化修改:顯示文字由 node.data 控制 + // Customization: Display text is controlled by node.data + var topic = node.data.text + element.style.zIndex = 'auto' + element.removeChild(this.e_editor) + if (util.text.is_empty(topic) || node.topic === topic) { + this.render_node(element, node) + } else { + this.jm.update_node(node.id, topic) + } + } + this.e_panel.focus() +} + +ViewProvider.prototype.select_node = function (node) { + if (!!this.selected_node) { + var element = this.selected_node._data.view.element + element.className = element.className.replace(/\s*selected\b/i, '') + this.restore_selected_node_custom_style(this.selected_node) + } + if (!!node) { + this.selected_node = node + node._data.view.element.className += ' selected' + // 客製化修改:不清除自定義樣式 + // Customization: Do not clear custom styles + // this.clear_selected_node_custom_style(node) + } +} + +const originalAddNode = Mind.prototype.add_node +Mind.prototype.add_node = function ( + parent_node, + node_id, + topic, + data = undefined, + direction, + expanded, + idx +) { + if (data == undefined) { + data = {} + for (let style of [ + 'leading-line-color', + 'background-color', + 'foreground-color', + ]) { + if (data[style] == undefined && parent_node.data?.[style]) { + data[style] = parent_node.data[style] + } + } + } + arguments[3] = data + + return originalAddNode.apply(this, arguments) +} + +const originalMousedownHandle = jsMind.prototype.mousedown_handle +jsMind.prototype.mousedown_handle = function (e) { + if (e.button !== 0) return + + return originalMousedownHandle.apply(this, arguments) +} diff --git a/app/assets/javascripts/mind_map/utils/custom.search.js b/app/assets/javascripts/mind_map/utils/custom.search.js old mode 100644 new mode 100755 index 1517ac9..44a3a30 --- a/app/assets/javascripts/mind_map/utils/custom.search.js +++ b/app/assets/javascripts/mind_map/utils/custom.search.js @@ -1,267 +1,267 @@ -import { getRelativePosition } from './custom.util.js' - -const EDITOR_CLASS = 'jsmind-editor' // jsmind class name -const SUGGESTION_BOX_CLASS = 'jsmind-suggestions' -const SUGGESTION_ITEM_CLASS = 'suggestion-item' - -/** - * jsMind 搜尋管理 - * jsMind Search Manager - */ -export class JsmindSearch { - /** - * 建構搜尋 - * Constructor for search - * @param {Object} jm - jsMind 實例 (jsMind instance) - * @param {Function} searchAPI - 遠程搜尋 API 函式 (Remote search API function) - * @param {string} tableUID - */ - constructor(jm, searchAPI, tableUID) { - this.jm = jm - this.searchAPI = searchAPI - this.container = document.getElementById(jm.options.container) - this.suggestionBox = null - this.tableUID = tableUID - - // 新增記錄節點與事件 handler - this.currentNode = null - this._keydownHandler = null - this._inputHandler = null - - this.init() - } - - /** - * 初始化搜尋事件 - * Initialize search events - */ - init() { - // 確保不會重複綁定 dblclick 事件 - // Ensure double-click event is not bound multiple times - this.container.removeEventListener('dblclick', this.onDoubleClick) - this.container.addEventListener('dblclick', this.onDoubleClick.bind(this)) - } - - /** - * 處理雙擊事件以觸發搜尋 - * Handle double-click event to trigger search - * @param {Event} e - 事件對象 (Event object) - */ - onDoubleClick(e) { - // 非可編輯狀態不執行 - // Ignore if not editable - if (!this.jm.options.editable) return - - const node = this.jm.get_selected_node() - if (!node) return - - // 避免影響原生編輯功能,稍後執行 - // Prevent interfering with native edit mode - setTimeout(() => this.handleSearch(node), 100) - } - - /** - * 開始處理搜尋 - * Start handling search - * @param {Object} node - 當前選中節點 (Selected node) - */ - handleSearch(node) { - const inputField = document.querySelector(`.${EDITOR_CLASS}`) - if (!inputField) return - - // 記住目前的 node - this.currentNode = node - - // 清除之前的 handler - if (this._keydownHandler) inputField.removeEventListener('keydown', this._keydownHandler) - if (this._inputHandler) inputField.removeEventListener('input', this._inputHandler) - - // 新綁定 handler - this._keydownHandler = this.onKeyDown.bind(this) - this._inputHandler = this.onInput.bind(this) - - inputField.addEventListener('keydown', this._keydownHandler) - inputField.addEventListener('input', this._inputHandler) - } - /** - * 處理 Enter 鍵完成輸入 - * Handle Enter key to finalize input - * @param {Object} node - 當前節點 - * @param {KeyboardEvent} e - 鍵盤事件 - */ - onKeyDown(e) { - if (e.key === 'Enter') { - e.preventDefault() - - const input = e.target.value.trim() - const node = this.currentNode - if (input && node) { - node.data.text = input - this.jm.end_edit() - this.jm.update_node(node.id, input) - - if (this.suggestionBox) { - this.suggestionBox.style.display = 'none' - } - - this.currentNode = null // 清除參考 - } - } - } - - /** - * 處理使用者輸入 - * Handle user input - * @param {Object} node - 當前選中節點 (Selected node) - * @param {Event} e - 輸入事件 (Input event) - */ - async onInput(e) { - const query = e.target.value.trim() - if (!query) return - await new Promise((resolve) => setTimeout(resolve, 500)) - try { - const results = await this.searchAPI(query, this.tableUID) - this.showSuggestion(this.currentNode, e.target, results) - } catch (error) { - console.error('搜尋 API 錯誤:', error) - } - } - - /** - * 顯示搜尋建議框 - * Show search suggestion box - * @param {Object} node - 當前選中節點 (Selected node) - * @param {HTMLElement} inputElement - 輸入框 (Input field) - * @param {Array} results - 搜尋結果 (Search results) - */ - showSuggestion(node, inputElement, results) { - const container = this.container - const nodeElement = inputElement.parentNode - if (!nodeElement) return - - const { left, top, height } = getRelativePosition(nodeElement, container) - this.suggestionBox = this.suggestionBox || this.createSuggestionBox() - - // 更新建議框內容 - // Update suggestion box content - this.suggestionBox.innerHTML = results -.map(item => { - const fieldHtml = item.fields.map(f => { - const txt = f.url - ? `${f.text}` - : f.text; - return `
${f.title}: ${txt}
`; - }).join(""); - - return ` -
- ${fieldHtml} -
- `; -}) - - - .join('') - - this.suggestionBox.style.left = `${left}px` - this.suggestionBox.style.top = `${top + height}px` - this.suggestionBox.style.display = 'block' - - // 綁定建議點擊事件 - // Bind suggestion click events - document.querySelectorAll(`.${SUGGESTION_ITEM_CLASS}`).forEach((item) => { - item.removeEventListener('mousedown', this.onSuggestionClick) - item.addEventListener('mousedown', this.onSuggestionClick.bind(this, node)) - }) - } - - /** - * 建立搜尋建議框 - * Create search suggestion box - * @returns {HTMLElement} - 建議框 DOM (Suggestion box DOM) - */ - createSuggestionBox() { - let suggestionBox = document.getElementById(SUGGESTION_BOX_CLASS) - if (!suggestionBox) { - suggestionBox = document.createElement('div') - suggestionBox.classList.add(SUGGESTION_BOX_CLASS) - this.container.appendChild(suggestionBox) - } - return suggestionBox - } - - /** - * 處理點擊建議 - * Handle suggestion click - * @param {Object} node - 當前選中節點 (Selected node) - * @param {Event} e - 點擊事件 (Click event) - */ - // onSuggestionClick(node, e) { - // e.preventDefault() - - // const text = e.target.getAttribute('data-text') - // const link = e.target.getAttribute('data-link') - - // node.data.text = text - // node.data.link = link - - // this.jm.end_edit() - // this.jm.update_node(node.id, text) - - // // 選擇後隱藏建議框 - // // Hide suggestions after selection - // this.suggestionBox.style.display = 'none' - // } - onSuggestionClick(node, e) { - e.preventDefault() - - const item = e.currentTarget // 確保抓到整個 .suggestion-item DIV - const html = item.innerHTML // 取得完整 HTML 當作 topic - - node.data.text = html - node.data.link = item.getAttribute('data-link') - - this.jm.end_edit() - this.jm.update_node(node.id, html) - - this.suggestionBox.style.display = 'none' - } - -} -// ✅ 新增播放語音事件委派,支援動態插入的 voice-player -let audio; - -document.addEventListener('click', function(e) { - const target = e.target.closest('.voice-player'); - if (!target) return; - - e.preventDefault(); - - let status = target.getAttribute('status'); - if (audio) { - audio.pause(); - audio.currentTime = 0; - } - - if (status === 'playing') { - target.setAttribute('status', ''); - const icon = target.querySelector('i'); - icon?.classList.remove('fa-pause'); - icon?.classList.add('fa-play'); - } else { - let mp3_url = target.getAttribute('data-content'); - audio = new Audio(mp3_url); - audio.play(); - - target.setAttribute('status', 'playing'); - const icon = target.querySelector('i'); - icon?.classList.remove('fa-play'); - icon?.classList.add('fa-pause'); - - audio.onended = function() { - target.setAttribute('status', ''); - icon?.classList.remove('fa-pause'); - icon?.classList.add('fa-play'); - }; - } +import { getRelativePosition } from './custom.util.js' + +const EDITOR_CLASS = 'jsmind-editor' // jsmind class name +const SUGGESTION_BOX_CLASS = 'jsmind-suggestions' +const SUGGESTION_ITEM_CLASS = 'suggestion-item' + +/** + * jsMind 搜尋管理 + * jsMind Search Manager + */ +export class JsmindSearch { + /** + * 建構搜尋 + * Constructor for search + * @param {Object} jm - jsMind 實例 (jsMind instance) + * @param {Function} searchAPI - 遠程搜尋 API 函式 (Remote search API function) + * @param {string} tableUID + */ + constructor(jm, searchAPI, tableUID) { + this.jm = jm + this.searchAPI = searchAPI + this.container = document.getElementById(jm.options.container) + this.suggestionBox = null + this.tableUID = tableUID + + // 新增記錄節點與事件 handler + this.currentNode = null + this._keydownHandler = null + this._inputHandler = null + + this.init() + } + + /** + * 初始化搜尋事件 + * Initialize search events + */ + init() { + // 確保不會重複綁定 dblclick 事件 + // Ensure double-click event is not bound multiple times + this.container.removeEventListener('dblclick', this.onDoubleClick) + this.container.addEventListener('dblclick', this.onDoubleClick.bind(this)) + } + + /** + * 處理雙擊事件以觸發搜尋 + * Handle double-click event to trigger search + * @param {Event} e - 事件對象 (Event object) + */ + onDoubleClick(e) { + // 非可編輯狀態不執行 + // Ignore if not editable + if (!this.jm.options.editable) return + + const node = this.jm.get_selected_node() + if (!node) return + + // 避免影響原生編輯功能,稍後執行 + // Prevent interfering with native edit mode + setTimeout(() => this.handleSearch(node), 100) + } + + /** + * 開始處理搜尋 + * Start handling search + * @param {Object} node - 當前選中節點 (Selected node) + */ + handleSearch(node) { + const inputField = document.querySelector(`.${EDITOR_CLASS}`) + if (!inputField) return + + // 記住目前的 node + this.currentNode = node + + // 清除之前的 handler + if (this._keydownHandler) inputField.removeEventListener('keydown', this._keydownHandler) + if (this._inputHandler) inputField.removeEventListener('input', this._inputHandler) + + // 新綁定 handler + this._keydownHandler = this.onKeyDown.bind(this) + this._inputHandler = this.onInput.bind(this) + + inputField.addEventListener('keydown', this._keydownHandler) + inputField.addEventListener('input', this._inputHandler) + } + /** + * 處理 Enter 鍵完成輸入 + * Handle Enter key to finalize input + * @param {Object} node - 當前節點 + * @param {KeyboardEvent} e - 鍵盤事件 + */ + onKeyDown(e) { + if (e.key === 'Enter') { + e.preventDefault() + + const input = e.target.value.trim() + const node = this.currentNode + if (input && node) { + node.data.text = input + this.jm.end_edit() + this.jm.update_node(node.id, input) + + if (this.suggestionBox) { + this.suggestionBox.style.display = 'none' + } + + this.currentNode = null // 清除參考 + } + } + } + + /** + * 處理使用者輸入 + * Handle user input + * @param {Object} node - 當前選中節點 (Selected node) + * @param {Event} e - 輸入事件 (Input event) + */ + async onInput(e) { + const query = e.target.value.trim() + if (!query) return + await new Promise((resolve) => setTimeout(resolve, 500)) + try { + const results = await this.searchAPI(query, this.tableUID) + this.showSuggestion(this.currentNode, e.target, results) + } catch (error) { + console.error('搜尋 API 錯誤:', error) + } + } + + /** + * 顯示搜尋建議框 + * Show search suggestion box + * @param {Object} node - 當前選中節點 (Selected node) + * @param {HTMLElement} inputElement - 輸入框 (Input field) + * @param {Array} results - 搜尋結果 (Search results) + */ + showSuggestion(node, inputElement, results) { + const container = this.container + const nodeElement = inputElement.parentNode + if (!nodeElement) return + + const { left, top, height } = getRelativePosition(nodeElement, container) + this.suggestionBox = this.suggestionBox || this.createSuggestionBox() + + // 更新建議框內容 + // Update suggestion box content + this.suggestionBox.innerHTML = results +.map(item => { + const fieldHtml = item.fields.map(f => { + const txt = f.url + ? `${f.text}` + : f.text; + return `
${f.title}: ${txt}
`; + }).join(""); + + return ` +
+ ${fieldHtml} +
+ `; +}) + + + .join('') + + this.suggestionBox.style.left = `${left}px` + this.suggestionBox.style.top = `${top + height}px` + this.suggestionBox.style.display = 'block' + + // 綁定建議點擊事件 + // Bind suggestion click events + document.querySelectorAll(`.${SUGGESTION_ITEM_CLASS}`).forEach((item) => { + item.removeEventListener('mousedown', this.onSuggestionClick) + item.addEventListener('mousedown', this.onSuggestionClick.bind(this, node)) + }) + } + + /** + * 建立搜尋建議框 + * Create search suggestion box + * @returns {HTMLElement} - 建議框 DOM (Suggestion box DOM) + */ + createSuggestionBox() { + let suggestionBox = document.getElementById(SUGGESTION_BOX_CLASS) + if (!suggestionBox) { + suggestionBox = document.createElement('div') + suggestionBox.classList.add(SUGGESTION_BOX_CLASS) + this.container.appendChild(suggestionBox) + } + return suggestionBox + } + + /** + * 處理點擊建議 + * Handle suggestion click + * @param {Object} node - 當前選中節點 (Selected node) + * @param {Event} e - 點擊事件 (Click event) + */ + // onSuggestionClick(node, e) { + // e.preventDefault() + + // const text = e.target.getAttribute('data-text') + // const link = e.target.getAttribute('data-link') + + // node.data.text = text + // node.data.link = link + + // this.jm.end_edit() + // this.jm.update_node(node.id, text) + + // // 選擇後隱藏建議框 + // // Hide suggestions after selection + // this.suggestionBox.style.display = 'none' + // } + onSuggestionClick(node, e) { + e.preventDefault() + + const item = e.currentTarget // 確保抓到整個 .suggestion-item DIV + const html = item.innerHTML // 取得完整 HTML 當作 topic + + node.data.text = html + node.data.link = item.getAttribute('data-link') + + this.jm.end_edit() + this.jm.update_node(node.id, html) + + this.suggestionBox.style.display = 'none' + } + +} +// ✅ 新增播放語音事件委派,支援動態插入的 voice-player +let audio; + +document.addEventListener('click', function(e) { + const target = e.target.closest('.voice-player'); + if (!target) return; + + e.preventDefault(); + + let status = target.getAttribute('status'); + if (audio) { + audio.pause(); + audio.currentTime = 0; + } + + if (status === 'playing') { + target.setAttribute('status', ''); + const icon = target.querySelector('i'); + icon?.classList.remove('fa-pause'); + icon?.classList.add('fa-play'); + } else { + let mp3_url = target.getAttribute('data-content'); + audio = new Audio(mp3_url); + audio.play(); + + target.setAttribute('status', 'playing'); + const icon = target.querySelector('i'); + icon?.classList.remove('fa-play'); + icon?.classList.add('fa-pause'); + + audio.onended = function() { + target.setAttribute('status', ''); + icon?.classList.remove('fa-pause'); + icon?.classList.add('fa-play'); + }; + } }); \ No newline at end of file diff --git a/app/assets/javascripts/mind_map/utils/custom.toolbar.js b/app/assets/javascripts/mind_map/utils/custom.toolbar.js old mode 100644 new mode 100755 index 7f1caa3..b0b5480 --- a/app/assets/javascripts/mind_map/utils/custom.toolbar.js +++ b/app/assets/javascripts/mind_map/utils/custom.toolbar.js @@ -1,266 +1,266 @@ -import { util } from '../jsmind/jsmind.util.js' -import { getRelativePosition } from './custom.util.js' -import { PALETTE_COLORS } from './custom.config.js' - -const TOOLBAR_ID = 'jsmind-toolbar' - -/** - * jsMind 工具列管理 - * jsMind Toolbar Manager - */ -export class JsmindToolbar { - /** - * 建構工具列 - * Constructor for toolbar - * @param {Object} jm - jsMind 實例 (jsMind instance) - * @param {Object} options - jsMind 實例 (options) - */ - constructor(jm, options) { - this.jm = jm - this.container = document.getElementById(jm.options.container) - this.toolbarNodeId = null - this.toolbar = null - this.bgColorPalette = null - this.strokeColorPalette = null - this.textColorPalette = null - this.options = options - this.init() - } - - /** - * 初始化工具列事件 - * Initialize toolbar events - */ - init() { - // 監聽節點選取事件 - // Listen for node selection events - this.jm.add_event_listener((e, f, g) => { - // 忽略非選擇節點事件 - // Ignore non-selection events - if (e !== 4) return - - const node = this.jm.get_selected_node() - if (!node || node.id === this.toolbarNodeId) return - - this.toolbarNodeId = node.id - if (!this.toolbar) { - this.createToolbar() - } - this.moveToolbar(node) - }) - - // 確保不會重複綁定點擊事件 - // Ensure click event is not bound multiple times - this.container.removeEventListener('click', this.onClickOutside) - this.container.addEventListener('click', this.onClickOutside.bind(this)) - } - - /** - * 處理點擊事件來隱藏工具列 - * Handle click event to hide toolbar - * @param {Event} e - 事件對象 (Event object) - */ - onClickOutside(e) { - const clickedNode = e.target.tagName === 'JMNODE' - const clickedToolbar = e.target.closest(`#${TOOLBAR_ID}`) - if (!clickedNode && !clickedToolbar && this.toolbar) { - this.hideToolbar() - } - } - - /** - * 建立工具列 UI - * Create toolbar UI - */ - createToolbar() { - this.toolbar = document.createElement('div') - this.toolbar.id = TOOLBAR_ID - - // 建立工具列按鈕 - // Create toolbar buttons - const buttons = [ - { id: 'toolbar-add-child-btn', text: this.options.text.addNode, onClick: this.handleAddChild.bind(this) }, - { id: 'toolbar-delete-btn', text: this.options.text.deleteNode, onClick: this.handleDelete.bind(this) }, - { - id: 'toolbar-stroke-color-btn', - text: this.options.text.strokeColor, - onClick: this.handleStrokeColor.bind(this), - }, - { - id: 'toolbar-bg-color-btn', - text: this.options.text.bgColor, - onClick: this.handleBgColor.bind(this), - }, - { - id: 'toolbar-text-color-btn', - text: this.options.text.textColor, - onClick: this.handleTextColor.bind(this), - }, - ] - buttons.forEach((button) => { - const btn = document.createElement('button') - btn.id = button.id - btn.innerText = button.text - btn.onclick = button.onClick - this.toolbar.appendChild(btn) - // 附加顏色選單 - // Append color palettes to corresponding buttons - if (button.id === 'toolbar-bg-color-btn') { - this.bgColorPalette = this.createColorPalette( - (color) => this.setNodeStyle('background-color', color), - btn - ) - } - if (button.id === 'toolbar-stroke-color-btn') { - this.strokeColorPalette = this.createColorPalette( - (color) => this.setNodeStyle('leading-line-color', color), - btn - ) - } - if (button.id === 'toolbar-text-color-btn') { - this.textColorPalette = this.createColorPalette( - (color) => this.setNodeStyle('foreground-color', color), - btn - ) - } - }) - - this.container.appendChild(this.toolbar) - } - - /** - * 移動工具列至選中節點 - * Move the toolbar to the selected node - */ - moveToolbar(node) { - const nodeElement = node._data.view.element - if (!nodeElement) return - - const { left, top } = getRelativePosition(nodeElement, this.container) - this.toolbar.style.left = `${left}px` - this.toolbar.style.top = `${top - 40}px` - this.toolbar.style.display = 'block' - - // 根節點則隱藏刪除與線條顏色按鈕 - // Hide delete & stroke color buttons if the node is root - const deleteBtn = this.toolbar.querySelector('#toolbar-delete-btn') - const strokeColorBtn = this.toolbar.querySelector('#toolbar-stroke-color-btn') - - if (node.id === 'root') { - deleteBtn.style.display = 'none' - strokeColorBtn.style.display = 'none' - } else { - deleteBtn.style.display = 'inline-block' - strokeColorBtn.style.display = 'inline-block' - } - } - - /** - * 隱藏工具列 - * Hide the toolbar - */ - hideToolbar() { - this.toolbar.style.display = 'none' - this.bgColorPalette.style.display = 'none' - this.strokeColorPalette.style.display = 'none' - this.textColorPalette.style.display = 'none' - this.toolbarNodeId = null - } - - /** - * 建立顏色選單 - * Create color palette - */ - createColorPalette(onSelect, button) { - const colorPalette = document.createElement('div') - colorPalette.classList.add('toolbar-color-palette') - colorPalette.style.display = 'none' - - PALETTE_COLORS.forEach((color) => { - const colorBox = document.createElement('div') - colorBox.classList.add('toolbar-color-palette-box') - colorBox.style.backgroundColor = color - colorBox.onclick = () => { - onSelect(color) - colorPalette.style.display = 'none' - } - colorPalette.appendChild(colorBox) - }) - - button.appendChild(colorPalette) - return colorPalette - } - - /** - * 設定節點樣式 - * Set node style - */ - setNodeStyle(style, color) { - if (!this.toolbarNodeId) return - const node = this.jm.get_node(this.toolbarNodeId) - if (!node) return - node.data[style] = color - if (style === 'leading-line-color') this.jm.view.show_lines() - else this.jm.view.restore_selected_node_custom_style(node) - } - - /** - * 處理新增節點事件 - * Handle add child node event - */ - handleAddChild(e) { - e.preventDefault(); - e.stopPropagation() - if (!this.toolbarNodeId) return - const node = this.jm.get_node(this.toolbarNodeId) - if (!node) return - - const newNode = this.jm.add_node(node, util.uuid.newid(), 'NewNode') - this.jm.select_node(newNode) - } - - /** - * 處理刪除節點事件 - * Handle delete node event - */ - handleDelete(e) { - e.preventDefault(); - e.stopPropagation() - if (!this.toolbarNodeId) return - const node = this.jm.get_node(this.toolbarNodeId) - if (!node) return - this.jm.remove_node(node) - this.hideToolbar() - } - - /** - * 處理其他樣式設定事件 - * Handle style setting event - */ - handleStrokeColor(e) { - e.preventDefault(); - e.stopPropagation() - this.toggleColorPalette(this.strokeColorPalette) - } - handleBgColor(e) { - e.preventDefault(); - e.stopPropagation() - this.toggleColorPalette(this.bgColorPalette) - } - handleTextColor(e) { - e.preventDefault(); - e.stopPropagation() - this.toggleColorPalette(this.textColorPalette) - } - - /** - * 顯示或隱藏顏色選單 - * Toggle color palette display - */ - toggleColorPalette(palette) { - ;[this.bgColorPalette, this.strokeColorPalette, this.textColorPalette].forEach((p) => { - if (p !== palette) p.style.display = 'none' - }) - palette.style.display = palette.style.display === 'block' ? 'none' : 'block' - } -} +import { util } from '../jsmind/jsmind.util.js' +import { getRelativePosition } from './custom.util.js' +import { PALETTE_COLORS } from './custom.config.js' + +const TOOLBAR_ID = 'jsmind-toolbar' + +/** + * jsMind 工具列管理 + * jsMind Toolbar Manager + */ +export class JsmindToolbar { + /** + * 建構工具列 + * Constructor for toolbar + * @param {Object} jm - jsMind 實例 (jsMind instance) + * @param {Object} options - jsMind 實例 (options) + */ + constructor(jm, options) { + this.jm = jm + this.container = document.getElementById(jm.options.container) + this.toolbarNodeId = null + this.toolbar = null + this.bgColorPalette = null + this.strokeColorPalette = null + this.textColorPalette = null + this.options = options + this.init() + } + + /** + * 初始化工具列事件 + * Initialize toolbar events + */ + init() { + // 監聽節點選取事件 + // Listen for node selection events + this.jm.add_event_listener((e, f, g) => { + // 忽略非選擇節點事件 + // Ignore non-selection events + if (e !== 4) return + + const node = this.jm.get_selected_node() + if (!node || node.id === this.toolbarNodeId) return + + this.toolbarNodeId = node.id + if (!this.toolbar) { + this.createToolbar() + } + this.moveToolbar(node) + }) + + // 確保不會重複綁定點擊事件 + // Ensure click event is not bound multiple times + this.container.removeEventListener('click', this.onClickOutside) + this.container.addEventListener('click', this.onClickOutside.bind(this)) + } + + /** + * 處理點擊事件來隱藏工具列 + * Handle click event to hide toolbar + * @param {Event} e - 事件對象 (Event object) + */ + onClickOutside(e) { + const clickedNode = e.target.tagName === 'JMNODE' + const clickedToolbar = e.target.closest(`#${TOOLBAR_ID}`) + if (!clickedNode && !clickedToolbar && this.toolbar) { + this.hideToolbar() + } + } + + /** + * 建立工具列 UI + * Create toolbar UI + */ + createToolbar() { + this.toolbar = document.createElement('div') + this.toolbar.id = TOOLBAR_ID + + // 建立工具列按鈕 + // Create toolbar buttons + const buttons = [ + { id: 'toolbar-add-child-btn', text: this.options.text.addNode, onClick: this.handleAddChild.bind(this) }, + { id: 'toolbar-delete-btn', text: this.options.text.deleteNode, onClick: this.handleDelete.bind(this) }, + { + id: 'toolbar-stroke-color-btn', + text: this.options.text.strokeColor, + onClick: this.handleStrokeColor.bind(this), + }, + { + id: 'toolbar-bg-color-btn', + text: this.options.text.bgColor, + onClick: this.handleBgColor.bind(this), + }, + { + id: 'toolbar-text-color-btn', + text: this.options.text.textColor, + onClick: this.handleTextColor.bind(this), + }, + ] + buttons.forEach((button) => { + const btn = document.createElement('button') + btn.id = button.id + btn.innerText = button.text + btn.onclick = button.onClick + this.toolbar.appendChild(btn) + // 附加顏色選單 + // Append color palettes to corresponding buttons + if (button.id === 'toolbar-bg-color-btn') { + this.bgColorPalette = this.createColorPalette( + (color) => this.setNodeStyle('background-color', color), + btn + ) + } + if (button.id === 'toolbar-stroke-color-btn') { + this.strokeColorPalette = this.createColorPalette( + (color) => this.setNodeStyle('leading-line-color', color), + btn + ) + } + if (button.id === 'toolbar-text-color-btn') { + this.textColorPalette = this.createColorPalette( + (color) => this.setNodeStyle('foreground-color', color), + btn + ) + } + }) + + this.container.appendChild(this.toolbar) + } + + /** + * 移動工具列至選中節點 + * Move the toolbar to the selected node + */ + moveToolbar(node) { + const nodeElement = node._data.view.element + if (!nodeElement) return + + const { left, top } = getRelativePosition(nodeElement, this.container) + this.toolbar.style.left = `${left}px` + this.toolbar.style.top = `${top - 40}px` + this.toolbar.style.display = 'block' + + // 根節點則隱藏刪除與線條顏色按鈕 + // Hide delete & stroke color buttons if the node is root + const deleteBtn = this.toolbar.querySelector('#toolbar-delete-btn') + const strokeColorBtn = this.toolbar.querySelector('#toolbar-stroke-color-btn') + + if (node.id === 'root') { + deleteBtn.style.display = 'none' + strokeColorBtn.style.display = 'none' + } else { + deleteBtn.style.display = 'inline-block' + strokeColorBtn.style.display = 'inline-block' + } + } + + /** + * 隱藏工具列 + * Hide the toolbar + */ + hideToolbar() { + this.toolbar.style.display = 'none' + this.bgColorPalette.style.display = 'none' + this.strokeColorPalette.style.display = 'none' + this.textColorPalette.style.display = 'none' + this.toolbarNodeId = null + } + + /** + * 建立顏色選單 + * Create color palette + */ + createColorPalette(onSelect, button) { + const colorPalette = document.createElement('div') + colorPalette.classList.add('toolbar-color-palette') + colorPalette.style.display = 'none' + + PALETTE_COLORS.forEach((color) => { + const colorBox = document.createElement('div') + colorBox.classList.add('toolbar-color-palette-box') + colorBox.style.backgroundColor = color + colorBox.onclick = () => { + onSelect(color) + colorPalette.style.display = 'none' + } + colorPalette.appendChild(colorBox) + }) + + button.appendChild(colorPalette) + return colorPalette + } + + /** + * 設定節點樣式 + * Set node style + */ + setNodeStyle(style, color) { + if (!this.toolbarNodeId) return + const node = this.jm.get_node(this.toolbarNodeId) + if (!node) return + node.data[style] = color + if (style === 'leading-line-color') this.jm.view.show_lines() + else this.jm.view.restore_selected_node_custom_style(node) + } + + /** + * 處理新增節點事件 + * Handle add child node event + */ + handleAddChild(e) { + e.preventDefault(); + e.stopPropagation() + if (!this.toolbarNodeId) return + const node = this.jm.get_node(this.toolbarNodeId) + if (!node) return + + const newNode = this.jm.add_node(node, util.uuid.newid(), 'NewNode') + this.jm.select_node(newNode) + } + + /** + * 處理刪除節點事件 + * Handle delete node event + */ + handleDelete(e) { + e.preventDefault(); + e.stopPropagation() + if (!this.toolbarNodeId) return + const node = this.jm.get_node(this.toolbarNodeId) + if (!node) return + this.jm.remove_node(node) + this.hideToolbar() + } + + /** + * 處理其他樣式設定事件 + * Handle style setting event + */ + handleStrokeColor(e) { + e.preventDefault(); + e.stopPropagation() + this.toggleColorPalette(this.strokeColorPalette) + } + handleBgColor(e) { + e.preventDefault(); + e.stopPropagation() + this.toggleColorPalette(this.bgColorPalette) + } + handleTextColor(e) { + e.preventDefault(); + e.stopPropagation() + this.toggleColorPalette(this.textColorPalette) + } + + /** + * 顯示或隱藏顏色選單 + * Toggle color palette display + */ + toggleColorPalette(palette) { + ;[this.bgColorPalette, this.strokeColorPalette, this.textColorPalette].forEach((p) => { + if (p !== palette) p.style.display = 'none' + }) + palette.style.display = palette.style.display === 'block' ? 'none' : 'block' + } +} diff --git a/app/assets/javascripts/mind_map/utils/custom.util.js b/app/assets/javascripts/mind_map/utils/custom.util.js old mode 100644 new mode 100755 index a79f840..a55637c --- a/app/assets/javascripts/mind_map/utils/custom.util.js +++ b/app/assets/javascripts/mind_map/utils/custom.util.js @@ -1,17 +1,17 @@ -/** - * 獲取元素相對於指定容器的位置 - * Get the relative position of an element within a given container - * @param {HTMLElement} element - 目標元素 (Target element) - * @param {HTMLElement} container - 參考容器 (Reference container) - * @returns {Object} - { left, top, height } 位置資訊 (Position details) - */ -export function getRelativePosition(element, container) { - let nodeRect = element.getBoundingClientRect() - let containerRect = container.getBoundingClientRect() - - return { - left: nodeRect.left - containerRect.left, - top: nodeRect.top - containerRect.top, - height: nodeRect.height, - } -} +/** + * 獲取元素相對於指定容器的位置 + * Get the relative position of an element within a given container + * @param {HTMLElement} element - 目標元素 (Target element) + * @param {HTMLElement} container - 參考容器 (Reference container) + * @returns {Object} - { left, top, height } 位置資訊 (Position details) + */ +export function getRelativePosition(element, container) { + let nodeRect = element.getBoundingClientRect() + let containerRect = container.getBoundingClientRect() + + return { + left: nodeRect.left - containerRect.left, + top: nodeRect.top - containerRect.top, + height: nodeRect.height, + } +} diff --git a/app/assets/javascripts/universal_table/application.js b/app/assets/javascripts/universal_table/application.js old mode 100644 new mode 100755 index a1873dd..5aacd2a --- a/app/assets/javascripts/universal_table/application.js +++ b/app/assets/javascripts/universal_table/application.js @@ -1,13 +1,13 @@ -// This is a manifest file that'll be compiled into application.js, which will include all the files -// listed below. -// -// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. -// -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// compiled file. -// -// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details -// about supported directives. -// -//= require_tree . +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require_tree . diff --git a/app/assets/javascripts/universal_table/jquery-ui.min.js b/app/assets/javascripts/universal_table/jquery-ui.min.js old mode 100644 new mode 100755 index 8475a32..219c461 --- a/app/assets/javascripts/universal_table/jquery-ui.min.js +++ b/app/assets/javascripts/universal_table/jquery-ui.min.js @@ -1,7 +1,7 @@ -/*! jQuery UI - v1.11.4 - 2015-11-18 -* http://jqueryui.com -* Includes: core.js, widget.js, mouse.js, sortable.js -* Copyright jQuery Foundation and other contributors; Licensed MIT */ - -(function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e(jQuery)})(function(e){function t(t,s){var n,a,o,r=t.nodeName.toLowerCase();return"area"===r?(n=t.parentNode,a=n.name,t.href&&a&&"map"===n.nodeName.toLowerCase()?(o=e("img[usemap='#"+a+"']")[0],!!o&&i(o)):!1):(/^(input|select|textarea|button|object)$/.test(r)?!t.disabled:"a"===r?t.href||s:s)&&i(t)}function i(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}e.ui=e.ui||{},e.extend(e.ui,{version:"1.11.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({scrollParent:function(t){var i=this.css("position"),s="absolute"===i,n=t?/(auto|scroll|hidden)/:/(auto|scroll)/,a=this.parents().filter(function(){var t=e(this);return s&&"static"===t.css("position")?!1:n.test(t.css("overflow")+t.css("overflow-y")+t.css("overflow-x"))}).eq(0);return"fixed"!==i&&a.length?a:e(this[0].ownerDocument||document)},uniqueId:function(){var e=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++e)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,s){return!!e.data(t,s[3])},focusable:function(i){return t(i,!isNaN(e.attr(i,"tabindex")))},tabbable:function(i){var s=e.attr(i,"tabindex"),n=isNaN(s);return(n||s>=0)&&t(i,!n)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(t,i){function s(t,i,s,a){return e.each(n,function(){i-=parseFloat(e.css(t,"padding"+this))||0,s&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),a&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],a=i.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+i]=function(t){return void 0===t?o["inner"+i].call(this):this.each(function(){e(this).css(a,s(this,t)+"px")})},e.fn["outer"+i]=function(t,n){return"number"!=typeof t?o["outer"+i].call(this,t):this.each(function(){e(this).css(a,s(this,t,!0,n)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.fn.extend({focus:function(t){return function(i,s){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),s&&s.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),disableSelection:function(){var e="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.bind(e+".ui-disableSelection",function(e){e.preventDefault()})}}(),enableSelection:function(){return this.unbind(".ui-disableSelection")},zIndex:function(t){if(void 0!==t)return this.css("zIndex",t);if(this.length)for(var i,s,n=e(this[0]);n.length&&n[0]!==document;){if(i=n.css("position"),("absolute"===i||"relative"===i||"fixed"===i)&&(s=parseInt(n.css("zIndex"),10),!isNaN(s)&&0!==s))return s;n=n.parent()}return 0}}),e.ui.plugin={add:function(t,i,s){var n,a=e.ui[t].prototype;for(n in s)a.plugins[n]=a.plugins[n]||[],a.plugins[n].push([i,s[n]])},call:function(e,t,i,s){var n,a=e.plugins[t];if(a&&(s||e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType))for(n=0;a.length>n;n++)e.options[a[n][0]]&&a[n][1].apply(e.element,i)}};var s=0,n=Array.prototype.slice;e.cleanData=function(t){return function(i){var s,n,a;for(a=0;null!=(n=i[a]);a++)try{s=e._data(n,"events"),s&&s.remove&&e(n).triggerHandler("remove")}catch(o){}t(i)}}(e.cleanData),e.widget=function(t,i,s){var n,a,o,r,h={},l=t.split(".")[0];return t=t.split(".")[1],n=l+"-"+t,s||(s=i,i=e.Widget),e.expr[":"][n.toLowerCase()]=function(t){return!!e.data(t,n)},e[l]=e[l]||{},a=e[l][t],o=e[l][t]=function(e,t){return this._createWidget?(arguments.length&&this._createWidget(e,t),void 0):new o(e,t)},e.extend(o,a,{version:s.version,_proto:e.extend({},s),_childConstructors:[]}),r=new i,r.options=e.widget.extend({},r.options),e.each(s,function(t,s){return e.isFunction(s)?(h[t]=function(){var e=function(){return i.prototype[t].apply(this,arguments)},n=function(e){return i.prototype[t].apply(this,e)};return function(){var t,i=this._super,a=this._superApply;return this._super=e,this._superApply=n,t=s.apply(this,arguments),this._super=i,this._superApply=a,t}}(),void 0):(h[t]=s,void 0)}),o.prototype=e.widget.extend(r,{widgetEventPrefix:a?r.widgetEventPrefix||t:t},h,{constructor:o,namespace:l,widgetName:t,widgetFullName:n}),a?(e.each(a._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete a._childConstructors):i._childConstructors.push(o),e.widget.bridge(t,o),o},e.widget.extend=function(t){for(var i,s,a=n.call(arguments,1),o=0,r=a.length;r>o;o++)for(i in a[o])s=a[o][i],a[o].hasOwnProperty(i)&&void 0!==s&&(t[i]=e.isPlainObject(s)?e.isPlainObject(t[i])?e.widget.extend({},t[i],s):e.widget.extend({},s):s);return t},e.widget.bridge=function(t,i){var s=i.prototype.widgetFullName||t;e.fn[t]=function(a){var o="string"==typeof a,r=n.call(arguments,1),h=this;return o?this.each(function(){var i,n=e.data(this,s);return"instance"===a?(h=n,!1):n?e.isFunction(n[a])&&"_"!==a.charAt(0)?(i=n[a].apply(n,r),i!==n&&void 0!==i?(h=i&&i.jquery?h.pushStack(i.get()):i,!1):void 0):e.error("no such method '"+a+"' for "+t+" widget instance"):e.error("cannot call methods on "+t+" prior to initialization; "+"attempted to call method '"+a+"'")}):(r.length&&(a=e.widget.extend.apply(null,[a].concat(r))),this.each(function(){var t=e.data(this,s);t?(t.option(a||{}),t._init&&t._init()):e.data(this,s,new i(a,this))})),h}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(t,i){i=e(i||this.defaultElement||this)[0],this.element=e(i),this.uuid=s++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=e(),this.hoverable=e(),this.focusable=e(),i!==this&&(e.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===i&&this.destroy()}}),this.document=e(i.style?i.ownerDocument:i.document||i),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(t,i){var s,n,a,o=t;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof t)if(o={},s=t.split("."),t=s.shift(),s.length){for(n=o[t]=e.widget.extend({},this.options[t]),a=0;s.length-1>a;a++)n[s[a]]=n[s[a]]||{},n=n[s[a]];if(t=s.pop(),1===arguments.length)return void 0===n[t]?null:n[t];n[t]=i}else{if(1===arguments.length)return void 0===this.options[t]?null:this.options[t];o[t]=i}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled",!!t),t&&(this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus"))),this},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_on:function(t,i,s){var n,a=this;"boolean"!=typeof t&&(s=i,i=t,t=!1),s?(i=n=e(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),e.each(s,function(s,o){function r(){return t||a.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?a[o]:o).apply(a,arguments):void 0}"string"!=typeof o&&(r.guid=o.guid=o.guid||r.guid||e.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+a.eventNamespace,u=h[2];u?n.delegate(u,l,r):i.bind(l,r)})},_off:function(t,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(i).undelegate(i),this.bindings=e(this.bindings.not(t).get()),this.focusable=e(this.focusable.not(t).get()),this.hoverable=e(this.hoverable.not(t).get())},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var n,a,o=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],a=i.originalEvent)for(n in a)n in i||(i[n]=a[n]);return this.element.trigger(i,s),!(e.isFunction(o)&&o.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,n,a){"string"==typeof n&&(n={effect:n});var o,r=n?n===!0||"number"==typeof n?i:n.effect||i:t;n=n||{},"number"==typeof n&&(n={duration:n}),o=!e.isEmptyObject(n),n.complete=a,n.delay&&s.delay(n.delay),o&&e.effects&&e.effects.effect[r]?s[t](n):r!==t&&s[r]?s[r](n.duration,n.easing,a):s.queue(function(i){e(this)[t](),a&&a.call(s[0]),i()})}}),e.widget;var a=!1;e(document).mouseup(function(){a=!1}),e.widget("ui.mouse",{version:"1.11.4",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(i){return!0===e.data(i.target,t.widgetName+".preventClickEvent")?(e.removeData(i.target,t.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(!a){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var i=this,s=1===t.which,n="string"==typeof this.options.cancel&&t.target.nodeName?e(t.target).closest(this.options.cancel).length:!1;return s&&!n&&this._mouseCapture(t)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(t)!==!1,!this._mouseStarted)?(t.preventDefault(),!0):(!0===e.data(t.target,this.widgetName+".preventClickEvent")&&e.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return i._mouseMove(e)},this._mouseUpDelegate=function(e){return i._mouseUp(e)},this.document.bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),a=!0,!0)):!0}},_mouseMove:function(t){if(this._mouseMoved){if(e.ui.ie&&(!document.documentMode||9>document.documentMode)&&!t.button)return this._mouseUp(t);if(!t.which)return this._mouseUp(t)}return(t.which||t.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),a=!1,!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),e.widget("ui.sortable",e.ui.mouse,{version:"1.11.4",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(e,t,i){return e>=t&&t+i>e},_isFloating:function(e){return/left|right/.test(e.css("float"))||/inline|table-cell/.test(e.css("display"))},_create:function(){this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(e,t){this._super(e,t),"handle"===e&&this._setHandleClassName()},_setHandleClassName:function(){this.element.find(".ui-sortable-handle").removeClass("ui-sortable-handle"),e.each(this.items,function(){(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item).addClass("ui-sortable-handle")})},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").find(".ui-sortable-handle").removeClass("ui-sortable-handle"),this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--)this.items[e].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(t,i){var s=null,n=!1,a=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(t),e(t.target).parents().each(function(){return e.data(this,a.widgetName+"-item")===a?(s=e(this),!1):void 0}),e.data(t.target,a.widgetName+"-item")===a&&(s=e(t.target)),s?!this.options.handle||i||(e(this.options.handle,s).find("*").addBack().each(function(){this===t.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(t,i,s){var n,a,o=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(t),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,o.cursorAt&&this._adjustOffsetFromHelper(o.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),o.containment&&this._setContainment(),o.cursor&&"auto"!==o.cursor&&(a=this.document.find("body"),this.storedCursor=a.css("cursor"),a.css("cursor",o.cursor),this.storedStylesheet=e("").appendTo(a)),o.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",o.opacity)),o.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",o.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",t,this._uiHash(this));return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!o.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){var i,s,n,a,o=this.options,r=!1;for(this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY=0;i--)if(s=this.items[i],n=s.item[0],a=this._intersectsWithPointer(s),a&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===a?"next":"prev"]()[0]!==n&&!e.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!e.contains(this.element[0],n):!0)){if(this.direction=1===a?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(t,s),this._trigger("change",t,this._uiHash());break}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,i){if(t){if(e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t),this.options.revert){var s=this,n=this.placeholder.offset(),a=this.options.axis,o={};a&&"x"!==a||(o.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),a&&"y"!==a||(o.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,e(this.helper).animate(o,parseInt(this.options.revert,10)||500,function(){s._clear(t)})}else this._clear(t,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var i=this._getItemsAsjQuery(t&&t.connected),s=[];return t=t||{},e(i).each(function(){var i=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[\-=_](.+)/);i&&s.push((t.key||i[1]+"[]")+"="+(t.key&&t.expression?i[1]:i[2]))}),!s.length&&t.key&&s.push(t.key+"="),s.join("&")},toArray:function(t){var i=this._getItemsAsjQuery(t&&t.connected),s=[];return t=t||{},i.each(function(){s.push(e(t.item||this).attr(t.attribute||"id")||"")}),s},_intersectsWith:function(e){var t=this.positionAbs.left,i=t+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,a=e.left,o=a+e.width,r=e.top,h=r+e.height,l=this.offset.click.top,u=this.offset.click.left,d="x"===this.options.axis||s+l>r&&h>s+l,c="y"===this.options.axis||t+u>a&&o>t+u,p=d&&c;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>e[this.floating?"width":"height"]?p:t+this.helperProportions.width/2>a&&o>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(e){var t="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,e.top,e.height),i="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,e.left,e.width),s=t&&i,n=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return s?this.floating?a&&"right"===a||"down"===n?2:1:n&&("down"===n?2:1):!1},_intersectsWithSides:function(e){var t=this._isOverAxis(this.positionAbs.top+this.offset.click.top,e.top+e.height/2,e.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,e.left+e.width/2,e.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&t||"up"===s&&!t)},_getDragVerticalDirection:function(){var e=this.positionAbs.top-this.lastPositionAbs.top;return 0!==e&&(e>0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return 0!==e&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor===String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){function i(){r.push(this)}var s,n,a,o,r=[],h=[],l=this._connectWith();if(l&&t)for(s=l.length-1;s>=0;s--)for(a=e(l[s],this.document[0]),n=a.length-1;n>=0;n--)o=e.data(a[n],this.widgetFullName),o&&o!==this&&!o.options.disabled&&h.push([e.isFunction(o.options.items)?o.options.items.call(o.element):e(o.options.items,o.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),o]);for(h.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return e(r)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var i=0;t.length>i;i++)if(t[i]===e.item[0])return!1;return!0})},_refreshItems:function(t){this.items=[],this.containers=[this];var i,s,n,a,o,r,h,l,u=this.items,d=[[e.isFunction(this.options.items)?this.options.items.call(this.element[0],t,{item:this.currentItem}):e(this.options.items,this.element),this]],c=this._connectWith();if(c&&this.ready)for(i=c.length-1;i>=0;i--)for(n=e(c[i],this.document[0]),s=n.length-1;s>=0;s--)a=e.data(n[s],this.widgetFullName),a&&a!==this&&!a.options.disabled&&(d.push([e.isFunction(a.options.items)?a.options.items.call(a.element[0],t,{item:this.currentItem}):e(a.options.items,a.element),a]),this.containers.push(a));for(i=d.length-1;i>=0;i--)for(o=d[i][1],r=d[i][0],s=0,l=r.length;l>s;s++)h=e(r[s]),h.data(this.widgetName+"-item",o),u.push({item:h,instance:o,width:0,height:0,left:0,top:0})},refreshPositions:function(t){this.floating=this.items.length?"x"===this.options.axis||this._isFloating(this.items[0].item):!1,this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,a;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?e(this.options.toleranceElement,s.item):s.item,t||(s.width=n.outerWidth(),s.height=n.outerHeight()),a=n.offset(),s.left=a.left,s.top=a.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)a=this.containers[i].element.offset(),this.containers[i].containerCache.left=a.left,this.containers[i].containerCache.top=a.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(t){t=t||this;var i,s=t.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=t.currentItem[0].nodeName.toLowerCase(),n=e("<"+s+">",t.document[0]).addClass(i||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tbody"===s?t._createTrPlaceholder(t.currentItem.find("tr").eq(0),e("",t.document[0]).appendTo(n)):"tr"===s?t._createTrPlaceholder(t.currentItem,n):"img"===s&&n.attr("src",t.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(e,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10)))}}),t.placeholder=e(s.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),s.placeholder.update(t,t.placeholder)},_createTrPlaceholder:function(t,i){var s=this;t.children().each(function(){e(" ",s.document[0]).attr("colspan",e(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(t){var i,s,n,a,o,r,h,l,u,d,c=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!e.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(c&&e.contains(this.containers[i].element[0],c.element[0]))continue;c=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",t,this._uiHash(this)),this.containers[i].containerCache.over=0);if(c)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",t,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,a=null,u=c.floating||this._isFloating(this.currentItem),o=u?"left":"top",r=u?"width":"height",d=u?"clientX":"clientY",s=this.items.length-1;s>=0;s--)e.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(h=this.items[s].item.offset()[o],l=!1,t[d]-h>this.items[s][r]/2&&(l=!0),n>Math.abs(t[d]-h)&&(n=Math.abs(t[d]-h),a=this.items[s],this.direction=l?"up":"down"));if(!a&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return this.currentContainer.containerCache.over||(this.containers[p]._trigger("over",t,this._uiHash()),this.currentContainer.containerCache.over=1),void 0;a?this._rearrange(t,a,null,!0):this._rearrange(t,null,this.containers[p].element,!0),this._trigger("change",t,this._uiHash()),this.containers[p]._trigger("change",t,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",t,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(t){var i=this.options,s=e.isFunction(i.helper)?e(i.helper.apply(this.element[0],[t,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||e("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(t){"string"==typeof t&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&e.ui.ie)&&(t={top:0,left:0}),{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var e=this.currentItem.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===n.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===n.containment?this.document.width():this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(t=e(n.containment)[0],i=e(n.containment).offset(),s="hidden"!==e(t).css("overflow"),this.containment=[i.left+(parseInt(e(t).css("borderLeftWidth"),10)||0)+(parseInt(e(t).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(e(t).css("borderTopWidth"),10)||0)+(parseInt(e(t).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(t.scrollWidth,t.offsetWidth):t.offsetWidth)-(parseInt(e(t).css("borderLeftWidth"),10)||0)-(parseInt(e(t).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(t.scrollHeight,t.offsetHeight):t.offsetHeight)-(parseInt(e(t).css("borderTopWidth"),10)||0)-(parseInt(e(t).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]) +/*! jQuery UI - v1.11.4 - 2015-11-18 +* http://jqueryui.com +* Includes: core.js, widget.js, mouse.js, sortable.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +(function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e(jQuery)})(function(e){function t(t,s){var n,a,o,r=t.nodeName.toLowerCase();return"area"===r?(n=t.parentNode,a=n.name,t.href&&a&&"map"===n.nodeName.toLowerCase()?(o=e("img[usemap='#"+a+"']")[0],!!o&&i(o)):!1):(/^(input|select|textarea|button|object)$/.test(r)?!t.disabled:"a"===r?t.href||s:s)&&i(t)}function i(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}e.ui=e.ui||{},e.extend(e.ui,{version:"1.11.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({scrollParent:function(t){var i=this.css("position"),s="absolute"===i,n=t?/(auto|scroll|hidden)/:/(auto|scroll)/,a=this.parents().filter(function(){var t=e(this);return s&&"static"===t.css("position")?!1:n.test(t.css("overflow")+t.css("overflow-y")+t.css("overflow-x"))}).eq(0);return"fixed"!==i&&a.length?a:e(this[0].ownerDocument||document)},uniqueId:function(){var e=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++e)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,s){return!!e.data(t,s[3])},focusable:function(i){return t(i,!isNaN(e.attr(i,"tabindex")))},tabbable:function(i){var s=e.attr(i,"tabindex"),n=isNaN(s);return(n||s>=0)&&t(i,!n)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(t,i){function s(t,i,s,a){return e.each(n,function(){i-=parseFloat(e.css(t,"padding"+this))||0,s&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),a&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],a=i.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+i]=function(t){return void 0===t?o["inner"+i].call(this):this.each(function(){e(this).css(a,s(this,t)+"px")})},e.fn["outer"+i]=function(t,n){return"number"!=typeof t?o["outer"+i].call(this,t):this.each(function(){e(this).css(a,s(this,t,!0,n)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.fn.extend({focus:function(t){return function(i,s){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),s&&s.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),disableSelection:function(){var e="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.bind(e+".ui-disableSelection",function(e){e.preventDefault()})}}(),enableSelection:function(){return this.unbind(".ui-disableSelection")},zIndex:function(t){if(void 0!==t)return this.css("zIndex",t);if(this.length)for(var i,s,n=e(this[0]);n.length&&n[0]!==document;){if(i=n.css("position"),("absolute"===i||"relative"===i||"fixed"===i)&&(s=parseInt(n.css("zIndex"),10),!isNaN(s)&&0!==s))return s;n=n.parent()}return 0}}),e.ui.plugin={add:function(t,i,s){var n,a=e.ui[t].prototype;for(n in s)a.plugins[n]=a.plugins[n]||[],a.plugins[n].push([i,s[n]])},call:function(e,t,i,s){var n,a=e.plugins[t];if(a&&(s||e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType))for(n=0;a.length>n;n++)e.options[a[n][0]]&&a[n][1].apply(e.element,i)}};var s=0,n=Array.prototype.slice;e.cleanData=function(t){return function(i){var s,n,a;for(a=0;null!=(n=i[a]);a++)try{s=e._data(n,"events"),s&&s.remove&&e(n).triggerHandler("remove")}catch(o){}t(i)}}(e.cleanData),e.widget=function(t,i,s){var n,a,o,r,h={},l=t.split(".")[0];return t=t.split(".")[1],n=l+"-"+t,s||(s=i,i=e.Widget),e.expr[":"][n.toLowerCase()]=function(t){return!!e.data(t,n)},e[l]=e[l]||{},a=e[l][t],o=e[l][t]=function(e,t){return this._createWidget?(arguments.length&&this._createWidget(e,t),void 0):new o(e,t)},e.extend(o,a,{version:s.version,_proto:e.extend({},s),_childConstructors:[]}),r=new i,r.options=e.widget.extend({},r.options),e.each(s,function(t,s){return e.isFunction(s)?(h[t]=function(){var e=function(){return i.prototype[t].apply(this,arguments)},n=function(e){return i.prototype[t].apply(this,e)};return function(){var t,i=this._super,a=this._superApply;return this._super=e,this._superApply=n,t=s.apply(this,arguments),this._super=i,this._superApply=a,t}}(),void 0):(h[t]=s,void 0)}),o.prototype=e.widget.extend(r,{widgetEventPrefix:a?r.widgetEventPrefix||t:t},h,{constructor:o,namespace:l,widgetName:t,widgetFullName:n}),a?(e.each(a._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete a._childConstructors):i._childConstructors.push(o),e.widget.bridge(t,o),o},e.widget.extend=function(t){for(var i,s,a=n.call(arguments,1),o=0,r=a.length;r>o;o++)for(i in a[o])s=a[o][i],a[o].hasOwnProperty(i)&&void 0!==s&&(t[i]=e.isPlainObject(s)?e.isPlainObject(t[i])?e.widget.extend({},t[i],s):e.widget.extend({},s):s);return t},e.widget.bridge=function(t,i){var s=i.prototype.widgetFullName||t;e.fn[t]=function(a){var o="string"==typeof a,r=n.call(arguments,1),h=this;return o?this.each(function(){var i,n=e.data(this,s);return"instance"===a?(h=n,!1):n?e.isFunction(n[a])&&"_"!==a.charAt(0)?(i=n[a].apply(n,r),i!==n&&void 0!==i?(h=i&&i.jquery?h.pushStack(i.get()):i,!1):void 0):e.error("no such method '"+a+"' for "+t+" widget instance"):e.error("cannot call methods on "+t+" prior to initialization; "+"attempted to call method '"+a+"'")}):(r.length&&(a=e.widget.extend.apply(null,[a].concat(r))),this.each(function(){var t=e.data(this,s);t?(t.option(a||{}),t._init&&t._init()):e.data(this,s,new i(a,this))})),h}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(t,i){i=e(i||this.defaultElement||this)[0],this.element=e(i),this.uuid=s++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=e(),this.hoverable=e(),this.focusable=e(),i!==this&&(e.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===i&&this.destroy()}}),this.document=e(i.style?i.ownerDocument:i.document||i),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(t,i){var s,n,a,o=t;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof t)if(o={},s=t.split("."),t=s.shift(),s.length){for(n=o[t]=e.widget.extend({},this.options[t]),a=0;s.length-1>a;a++)n[s[a]]=n[s[a]]||{},n=n[s[a]];if(t=s.pop(),1===arguments.length)return void 0===n[t]?null:n[t];n[t]=i}else{if(1===arguments.length)return void 0===this.options[t]?null:this.options[t];o[t]=i}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled",!!t),t&&(this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus"))),this},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_on:function(t,i,s){var n,a=this;"boolean"!=typeof t&&(s=i,i=t,t=!1),s?(i=n=e(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),e.each(s,function(s,o){function r(){return t||a.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?a[o]:o).apply(a,arguments):void 0}"string"!=typeof o&&(r.guid=o.guid=o.guid||r.guid||e.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+a.eventNamespace,u=h[2];u?n.delegate(u,l,r):i.bind(l,r)})},_off:function(t,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(i).undelegate(i),this.bindings=e(this.bindings.not(t).get()),this.focusable=e(this.focusable.not(t).get()),this.hoverable=e(this.hoverable.not(t).get())},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var n,a,o=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],a=i.originalEvent)for(n in a)n in i||(i[n]=a[n]);return this.element.trigger(i,s),!(e.isFunction(o)&&o.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,n,a){"string"==typeof n&&(n={effect:n});var o,r=n?n===!0||"number"==typeof n?i:n.effect||i:t;n=n||{},"number"==typeof n&&(n={duration:n}),o=!e.isEmptyObject(n),n.complete=a,n.delay&&s.delay(n.delay),o&&e.effects&&e.effects.effect[r]?s[t](n):r!==t&&s[r]?s[r](n.duration,n.easing,a):s.queue(function(i){e(this)[t](),a&&a.call(s[0]),i()})}}),e.widget;var a=!1;e(document).mouseup(function(){a=!1}),e.widget("ui.mouse",{version:"1.11.4",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(i){return!0===e.data(i.target,t.widgetName+".preventClickEvent")?(e.removeData(i.target,t.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(!a){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var i=this,s=1===t.which,n="string"==typeof this.options.cancel&&t.target.nodeName?e(t.target).closest(this.options.cancel).length:!1;return s&&!n&&this._mouseCapture(t)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(t)!==!1,!this._mouseStarted)?(t.preventDefault(),!0):(!0===e.data(t.target,this.widgetName+".preventClickEvent")&&e.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return i._mouseMove(e)},this._mouseUpDelegate=function(e){return i._mouseUp(e)},this.document.bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),a=!0,!0)):!0}},_mouseMove:function(t){if(this._mouseMoved){if(e.ui.ie&&(!document.documentMode||9>document.documentMode)&&!t.button)return this._mouseUp(t);if(!t.which)return this._mouseUp(t)}return(t.which||t.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return this.document.unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),a=!1,!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),e.widget("ui.sortable",e.ui.mouse,{version:"1.11.4",widgetEventPrefix:"sort",ready:!1,options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3,activate:null,beforeStop:null,change:null,deactivate:null,out:null,over:null,receive:null,remove:null,sort:null,start:null,stop:null,update:null},_isOverAxis:function(e,t,i){return e>=t&&t+i>e},_isFloating:function(e){return/left|right/.test(e.css("float"))||/inline|table-cell/.test(e.css("display"))},_create:function(){this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.offset=this.element.offset(),this._mouseInit(),this._setHandleClassName(),this.ready=!0},_setOption:function(e,t){this._super(e,t),"handle"===e&&this._setHandleClassName()},_setHandleClassName:function(){this.element.find(".ui-sortable-handle").removeClass("ui-sortable-handle"),e.each(this.items,function(){(this.instance.options.handle?this.item.find(this.instance.options.handle):this.item).addClass("ui-sortable-handle")})},_destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").find(".ui-sortable-handle").removeClass("ui-sortable-handle"),this._mouseDestroy();for(var e=this.items.length-1;e>=0;e--)this.items[e].item.removeData(this.widgetName+"-item");return this},_mouseCapture:function(t,i){var s=null,n=!1,a=this;return this.reverting?!1:this.options.disabled||"static"===this.options.type?!1:(this._refreshItems(t),e(t.target).parents().each(function(){return e.data(this,a.widgetName+"-item")===a?(s=e(this),!1):void 0}),e.data(t.target,a.widgetName+"-item")===a&&(s=e(t.target)),s?!this.options.handle||i||(e(this.options.handle,s).find("*").addBack().each(function(){this===t.target&&(n=!0)}),n)?(this.currentItem=s,this._removeCurrentsFromItems(),!0):!1:!1)},_mouseStart:function(t,i,s){var n,a,o=this.options;if(this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(t),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},e.extend(this.offset,{click:{left:t.pageX-this.offset.left,top:t.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),this.originalPosition=this._generatePosition(t),this.originalPageX=t.pageX,this.originalPageY=t.pageY,o.cursorAt&&this._adjustOffsetFromHelper(o.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!==this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),o.containment&&this._setContainment(),o.cursor&&"auto"!==o.cursor&&(a=this.document.find("body"),this.storedCursor=a.css("cursor"),a.css("cursor",o.cursor),this.storedStylesheet=e("").appendTo(a)),o.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",o.opacity)),o.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",o.zIndex)),this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",t,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions(),!s)for(n=this.containers.length-1;n>=0;n--)this.containers[n]._trigger("activate",t,this._uiHash(this));return e.ui.ddmanager&&(e.ui.ddmanager.current=this),e.ui.ddmanager&&!o.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(t),!0},_mouseDrag:function(t){var i,s,n,a,o=this.options,r=!1;for(this.position=this._generatePosition(t),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs),this.options.scroll&&(this.scrollParent[0]!==this.document[0]&&"HTML"!==this.scrollParent[0].tagName?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-t.pageY=0;i--)if(s=this.items[i],n=s.item[0],a=this._intersectsWithPointer(s),a&&s.instance===this.currentContainer&&n!==this.currentItem[0]&&this.placeholder[1===a?"next":"prev"]()[0]!==n&&!e.contains(this.placeholder[0],n)&&("semi-dynamic"===this.options.type?!e.contains(this.element[0],n):!0)){if(this.direction=1===a?"down":"up","pointer"!==this.options.tolerance&&!this._intersectsWithSides(s))break;this._rearrange(t,s),this._trigger("change",t,this._uiHash());break}return this._contactContainers(t),e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),this._trigger("sort",t,this._uiHash()),this.lastPositionAbs=this.positionAbs,!1},_mouseStop:function(t,i){if(t){if(e.ui.ddmanager&&!this.options.dropBehaviour&&e.ui.ddmanager.drop(this,t),this.options.revert){var s=this,n=this.placeholder.offset(),a=this.options.axis,o={};a&&"x"!==a||(o.left=n.left-this.offset.parent.left-this.margins.left+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollLeft)),a&&"y"!==a||(o.top=n.top-this.offset.parent.top-this.margins.top+(this.offsetParent[0]===this.document[0].body?0:this.offsetParent[0].scrollTop)),this.reverting=!0,e(this.helper).animate(o,parseInt(this.options.revert,10)||500,function(){s._clear(t)})}else this._clear(t,i);return!1}},cancel:function(){if(this.dragging){this._mouseUp({target:null}),"original"===this.options.helper?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var t=this.containers.length-1;t>=0;t--)this.containers[t]._trigger("deactivate",null,this._uiHash(this)),this.containers[t].containerCache.over&&(this.containers[t]._trigger("out",null,this._uiHash(this)),this.containers[t].containerCache.over=0)}return this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),"original"!==this.options.helper&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),e.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?e(this.domPosition.prev).after(this.currentItem):e(this.domPosition.parent).prepend(this.currentItem)),this},serialize:function(t){var i=this._getItemsAsjQuery(t&&t.connected),s=[];return t=t||{},e(i).each(function(){var i=(e(t.item||this).attr(t.attribute||"id")||"").match(t.expression||/(.+)[\-=_](.+)/);i&&s.push((t.key||i[1]+"[]")+"="+(t.key&&t.expression?i[1]:i[2]))}),!s.length&&t.key&&s.push(t.key+"="),s.join("&")},toArray:function(t){var i=this._getItemsAsjQuery(t&&t.connected),s=[];return t=t||{},i.each(function(){s.push(e(t.item||this).attr(t.attribute||"id")||"")}),s},_intersectsWith:function(e){var t=this.positionAbs.left,i=t+this.helperProportions.width,s=this.positionAbs.top,n=s+this.helperProportions.height,a=e.left,o=a+e.width,r=e.top,h=r+e.height,l=this.offset.click.top,u=this.offset.click.left,d="x"===this.options.axis||s+l>r&&h>s+l,c="y"===this.options.axis||t+u>a&&o>t+u,p=d&&c;return"pointer"===this.options.tolerance||this.options.forcePointerForContainers||"pointer"!==this.options.tolerance&&this.helperProportions[this.floating?"width":"height"]>e[this.floating?"width":"height"]?p:t+this.helperProportions.width/2>a&&o>i-this.helperProportions.width/2&&s+this.helperProportions.height/2>r&&h>n-this.helperProportions.height/2},_intersectsWithPointer:function(e){var t="x"===this.options.axis||this._isOverAxis(this.positionAbs.top+this.offset.click.top,e.top,e.height),i="y"===this.options.axis||this._isOverAxis(this.positionAbs.left+this.offset.click.left,e.left,e.width),s=t&&i,n=this._getDragVerticalDirection(),a=this._getDragHorizontalDirection();return s?this.floating?a&&"right"===a||"down"===n?2:1:n&&("down"===n?2:1):!1},_intersectsWithSides:function(e){var t=this._isOverAxis(this.positionAbs.top+this.offset.click.top,e.top+e.height/2,e.height),i=this._isOverAxis(this.positionAbs.left+this.offset.click.left,e.left+e.width/2,e.width),s=this._getDragVerticalDirection(),n=this._getDragHorizontalDirection();return this.floating&&n?"right"===n&&i||"left"===n&&!i:s&&("down"===s&&t||"up"===s&&!t)},_getDragVerticalDirection:function(){var e=this.positionAbs.top-this.lastPositionAbs.top;return 0!==e&&(e>0?"down":"up")},_getDragHorizontalDirection:function(){var e=this.positionAbs.left-this.lastPositionAbs.left;return 0!==e&&(e>0?"right":"left")},refresh:function(e){return this._refreshItems(e),this._setHandleClassName(),this.refreshPositions(),this},_connectWith:function(){var e=this.options;return e.connectWith.constructor===String?[e.connectWith]:e.connectWith},_getItemsAsjQuery:function(t){function i(){r.push(this)}var s,n,a,o,r=[],h=[],l=this._connectWith();if(l&&t)for(s=l.length-1;s>=0;s--)for(a=e(l[s],this.document[0]),n=a.length-1;n>=0;n--)o=e.data(a[n],this.widgetFullName),o&&o!==this&&!o.options.disabled&&h.push([e.isFunction(o.options.items)?o.options.items.call(o.element):e(o.options.items,o.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),o]);for(h.push([e.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):e(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]),s=h.length-1;s>=0;s--)h[s][0].each(i);return e(r)},_removeCurrentsFromItems:function(){var t=this.currentItem.find(":data("+this.widgetName+"-item)");this.items=e.grep(this.items,function(e){for(var i=0;t.length>i;i++)if(t[i]===e.item[0])return!1;return!0})},_refreshItems:function(t){this.items=[],this.containers=[this];var i,s,n,a,o,r,h,l,u=this.items,d=[[e.isFunction(this.options.items)?this.options.items.call(this.element[0],t,{item:this.currentItem}):e(this.options.items,this.element),this]],c=this._connectWith();if(c&&this.ready)for(i=c.length-1;i>=0;i--)for(n=e(c[i],this.document[0]),s=n.length-1;s>=0;s--)a=e.data(n[s],this.widgetFullName),a&&a!==this&&!a.options.disabled&&(d.push([e.isFunction(a.options.items)?a.options.items.call(a.element[0],t,{item:this.currentItem}):e(a.options.items,a.element),a]),this.containers.push(a));for(i=d.length-1;i>=0;i--)for(o=d[i][1],r=d[i][0],s=0,l=r.length;l>s;s++)h=e(r[s]),h.data(this.widgetName+"-item",o),u.push({item:h,instance:o,width:0,height:0,left:0,top:0})},refreshPositions:function(t){this.floating=this.items.length?"x"===this.options.axis||this._isFloating(this.items[0].item):!1,this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());var i,s,n,a;for(i=this.items.length-1;i>=0;i--)s=this.items[i],s.instance!==this.currentContainer&&this.currentContainer&&s.item[0]!==this.currentItem[0]||(n=this.options.toleranceElement?e(this.options.toleranceElement,s.item):s.item,t||(s.width=n.outerWidth(),s.height=n.outerHeight()),a=n.offset(),s.left=a.left,s.top=a.top);if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(i=this.containers.length-1;i>=0;i--)a=this.containers[i].element.offset(),this.containers[i].containerCache.left=a.left,this.containers[i].containerCache.top=a.top,this.containers[i].containerCache.width=this.containers[i].element.outerWidth(),this.containers[i].containerCache.height=this.containers[i].element.outerHeight();return this},_createPlaceholder:function(t){t=t||this;var i,s=t.options;s.placeholder&&s.placeholder.constructor!==String||(i=s.placeholder,s.placeholder={element:function(){var s=t.currentItem[0].nodeName.toLowerCase(),n=e("<"+s+">",t.document[0]).addClass(i||t.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper");return"tbody"===s?t._createTrPlaceholder(t.currentItem.find("tr").eq(0),e("",t.document[0]).appendTo(n)):"tr"===s?t._createTrPlaceholder(t.currentItem,n):"img"===s&&n.attr("src",t.currentItem.attr("src")),i||n.css("visibility","hidden"),n},update:function(e,n){(!i||s.forcePlaceholderSize)&&(n.height()||n.height(t.currentItem.innerHeight()-parseInt(t.currentItem.css("paddingTop")||0,10)-parseInt(t.currentItem.css("paddingBottom")||0,10)),n.width()||n.width(t.currentItem.innerWidth()-parseInt(t.currentItem.css("paddingLeft")||0,10)-parseInt(t.currentItem.css("paddingRight")||0,10)))}}),t.placeholder=e(s.placeholder.element.call(t.element,t.currentItem)),t.currentItem.after(t.placeholder),s.placeholder.update(t,t.placeholder)},_createTrPlaceholder:function(t,i){var s=this;t.children().each(function(){e(" ",s.document[0]).attr("colspan",e(this).attr("colspan")||1).appendTo(i)})},_contactContainers:function(t){var i,s,n,a,o,r,h,l,u,d,c=null,p=null;for(i=this.containers.length-1;i>=0;i--)if(!e.contains(this.currentItem[0],this.containers[i].element[0]))if(this._intersectsWith(this.containers[i].containerCache)){if(c&&e.contains(this.containers[i].element[0],c.element[0]))continue;c=this.containers[i],p=i}else this.containers[i].containerCache.over&&(this.containers[i]._trigger("out",t,this._uiHash(this)),this.containers[i].containerCache.over=0);if(c)if(1===this.containers.length)this.containers[p].containerCache.over||(this.containers[p]._trigger("over",t,this._uiHash(this)),this.containers[p].containerCache.over=1);else{for(n=1e4,a=null,u=c.floating||this._isFloating(this.currentItem),o=u?"left":"top",r=u?"width":"height",d=u?"clientX":"clientY",s=this.items.length-1;s>=0;s--)e.contains(this.containers[p].element[0],this.items[s].item[0])&&this.items[s].item[0]!==this.currentItem[0]&&(h=this.items[s].item.offset()[o],l=!1,t[d]-h>this.items[s][r]/2&&(l=!0),n>Math.abs(t[d]-h)&&(n=Math.abs(t[d]-h),a=this.items[s],this.direction=l?"up":"down"));if(!a&&!this.options.dropOnEmpty)return;if(this.currentContainer===this.containers[p])return this.currentContainer.containerCache.over||(this.containers[p]._trigger("over",t,this._uiHash()),this.currentContainer.containerCache.over=1),void 0;a?this._rearrange(t,a,null,!0):this._rearrange(t,null,this.containers[p].element,!0),this._trigger("change",t,this._uiHash()),this.containers[p]._trigger("change",t,this._uiHash(this)),this.currentContainer=this.containers[p],this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[p]._trigger("over",t,this._uiHash(this)),this.containers[p].containerCache.over=1}},_createHelper:function(t){var i=this.options,s=e.isFunction(i.helper)?e(i.helper.apply(this.element[0],[t,this.currentItem])):"clone"===i.helper?this.currentItem.clone():this.currentItem;return s.parents("body").length||e("parent"!==i.appendTo?i.appendTo:this.currentItem[0].parentNode)[0].appendChild(s[0]),s[0]===this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(!s[0].style.width||i.forceHelperSize)&&s.width(this.currentItem.width()),(!s[0].style.height||i.forceHelperSize)&&s.height(this.currentItem.height()),s},_adjustOffsetFromHelper:function(t){"string"==typeof t&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var t=this.offsetParent.offset();return"absolute"===this.cssPosition&&this.scrollParent[0]!==this.document[0]&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop()),(this.offsetParent[0]===this.document[0].body||this.offsetParent[0].tagName&&"html"===this.offsetParent[0].tagName.toLowerCase()&&e.ui.ie)&&(t={top:0,left:0}),{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"===this.cssPosition){var e=this.currentItem.position();return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:e.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,i,s,n=this.options;"parent"===n.containment&&(n.containment=this.helper[0].parentNode),("document"===n.containment||"window"===n.containment)&&(this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,"document"===n.containment?this.document.width():this.window.width()-this.helperProportions.width-this.margins.left,("document"===n.containment?this.document.width():this.window.height()||this.document[0].body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top]),/^(document|window|parent)$/.test(n.containment)||(t=e(n.containment)[0],i=e(n.containment).offset(),s="hidden"!==e(t).css("overflow"),this.containment=[i.left+(parseInt(e(t).css("borderLeftWidth"),10)||0)+(parseInt(e(t).css("paddingLeft"),10)||0)-this.margins.left,i.top+(parseInt(e(t).css("borderTopWidth"),10)||0)+(parseInt(e(t).css("paddingTop"),10)||0)-this.margins.top,i.left+(s?Math.max(t.scrollWidth,t.offsetWidth):t.offsetWidth)-(parseInt(e(t).css("borderLeftWidth"),10)||0)-(parseInt(e(t).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,i.top+(s?Math.max(t.scrollHeight,t.offsetHeight):t.offsetHeight)-(parseInt(e(t).css("borderTopWidth"),10)||0)-(parseInt(e(t).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]) },_convertPositionTo:function(t,i){i||(i=this.position);var s="absolute"===t?1:-1,n="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,a=/(html|body)/i.test(n[0].tagName);return{top:i.top+this.offset.relative.top*s+this.offset.parent.top*s-("fixed"===this.cssPosition?-this.scrollParent.scrollTop():a?0:n.scrollTop())*s,left:i.left+this.offset.relative.left*s+this.offset.parent.left*s-("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():a?0:n.scrollLeft())*s}},_generatePosition:function(t){var i,s,n=this.options,a=t.pageX,o=t.pageY,r="absolute"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&e.contains(this.scrollParent[0],this.offsetParent[0])?this.scrollParent:this.offsetParent,h=/(html|body)/i.test(r[0].tagName);return"relative"!==this.cssPosition||this.scrollParent[0]!==this.document[0]&&this.scrollParent[0]!==this.offsetParent[0]||(this.offset.relative=this._getRelativeOffset()),this.originalPosition&&(this.containment&&(t.pageX-this.offset.click.leftthis.containment[2]&&(a=this.containment[2]+this.offset.click.left),t.pageY-this.offset.click.top>this.containment[3]&&(o=this.containment[3]+this.offset.click.top)),n.grid&&(i=this.originalPageY+Math.round((o-this.originalPageY)/n.grid[1])*n.grid[1],o=this.containment?i-this.offset.click.top>=this.containment[1]&&i-this.offset.click.top<=this.containment[3]?i:i-this.offset.click.top>=this.containment[1]?i-n.grid[1]:i+n.grid[1]:i,s=this.originalPageX+Math.round((a-this.originalPageX)/n.grid[0])*n.grid[0],a=this.containment?s-this.offset.click.left>=this.containment[0]&&s-this.offset.click.left<=this.containment[2]?s:s-this.offset.click.left>=this.containment[0]?s-n.grid[0]:s+n.grid[0]:s)),{top:o-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.scrollParent.scrollTop():h?0:r.scrollTop()),left:a-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.scrollParent.scrollLeft():h?0:r.scrollLeft())}},_rearrange:function(e,t,i,s){i?i[0].appendChild(this.placeholder[0]):t.item[0].parentNode.insertBefore(this.placeholder[0],"down"===this.direction?t.item[0]:t.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var n=this.counter;this._delay(function(){n===this.counter&&this.refreshPositions(!s)})},_clear:function(e,t){function i(e,t,i){return function(s){i._trigger(e,s,t._uiHash(t))}}this.reverting=!1;var s,n=[];if(!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null,this.helper[0]===this.currentItem[0]){for(s in this._storedCSS)("auto"===this._storedCSS[s]||"static"===this._storedCSS[s])&&(this._storedCSS[s]="");this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();for(this.fromOutside&&!t&&n.push(function(e){this._trigger("receive",e,this._uiHash(this.fromOutside))}),!this.fromOutside&&this.domPosition.prev===this.currentItem.prev().not(".ui-sortable-helper")[0]&&this.domPosition.parent===this.currentItem.parent()[0]||t||n.push(function(e){this._trigger("update",e,this._uiHash())}),this!==this.currentContainer&&(t||(n.push(function(e){this._trigger("remove",e,this._uiHash())}),n.push(function(e){return function(t){e._trigger("receive",t,this._uiHash(this))}}.call(this,this.currentContainer)),n.push(function(e){return function(t){e._trigger("update",t,this._uiHash(this))}}.call(this,this.currentContainer)))),s=this.containers.length-1;s>=0;s--)t||n.push(i("deactivate",this,this.containers[s])),this.containers[s].containerCache.over&&(n.push(i("out",this,this.containers[s])),this.containers[s].containerCache.over=0);if(this.storedCursor&&(this.document.find("body").css("cursor",this.storedCursor),this.storedStylesheet.remove()),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex","auto"===this._storedZIndex?"":this._storedZIndex),this.dragging=!1,t||this._trigger("beforeStop",e,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.cancelHelperRemoval||(this.helper[0]!==this.currentItem[0]&&this.helper.remove(),this.helper=null),!t){for(s=0;n.length>s;s++)n[s].call(this,e);this._trigger("stop",e,this._uiHash())}return this.fromOutside=!1,!this.cancelHelperRemoval},_trigger:function(){e.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(t){var i=t||this;return{helper:i.helper,placeholder:i.placeholder||e([]),position:i.position,originalPosition:i.originalPosition,offset:i.positionAbs,item:i.currentItem,sender:t?t.element:null}}})}); \ No newline at end of file diff --git a/app/assets/stylesheets/mind_map/mindmap.css b/app/assets/stylesheets/mind_map/mindmap.css old mode 100644 new mode 100755 index b65fb0a..61671b3 --- a/app/assets/stylesheets/mind_map/mindmap.css +++ b/app/assets/stylesheets/mind_map/mindmap.css @@ -1,192 +1,192 @@ -/* ============================ - 基礎布局 (Layout) - ============================ */ -.jsmind-inner { - position: relative; - overflow: auto; - width: 100%; - height: 100%; - outline: none; - user-select: none; /* 防止文字選取 */ -} - -.jsmind-inner canvas { - position: absolute; -} - -#jsmind_container { - position: relative; - height: 800px; - border: 1px solid #ccc; - background: #f4f4f4; -} - -/* ============================ - 層級管理 (Z-index) - ============================ */ -svg.jsmind, -canvas.jsmind { - position: absolute; - z-index: 1; -} - -jmnodes { - position: absolute; - z-index: 2; - background-color: rgba(0, 0, 0, 0); /* 透明背景,確保可點擊 */ -} - -jmnode { - position: absolute; - cursor: default; - max-width: 400px; -} - -jmexpander { - position: absolute; - width: 11px; - height: 11px; - display: block; - overflow: hidden; - line-height: 12px; - font-size: 10px; - text-align: center; - border-radius: 6px; - border-width: 1px; - border-style: solid; - cursor: pointer; -} - -/* ============================ - 文字溢出控制 (Overflow) - ============================ */ -.jmnode-overflow-wrap jmnodes { - min-width: 420px; -} - -.jmnode-overflow-hidden jmnode { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -/* ============================ - 預設主題 (Default Theme) - ============================ */ -jmnode { - padding: 10px; - background-color: #fff; - color: #333; - border-radius: 5px; - box-shadow: 1px 1px 1px #666; - font: 1em/1.125 Verdana, Arial, Helvetica, sans-serif; -} - -jmnode:hover { - box-shadow: 2px 2px 8px #000; - filter: brightness(95%); - a{ - color: unset!important; - } -} - -jmnode.selected { - box-shadow: 2px 2px 8px #000; - filter: brightness(90%); -} - -jmnode.root { - font-size:1.2em; -/* 展開/收合按鈕 */ - border-color: gray; -} - -jmexpander:hover { - border-color: #000; -} - -/* ============================ - 響應式設計 (Responsive) - ============================ */ -@media screen and (max-device-width: 1024px) { - jmnode { - padding: 5px; - border-radius: 3px; - font-size: 1.2em; - } - - jmnode.root { - /* font-size: 21px; */ - font-size: 1.2em; - } -} - -/* ============================ - 工具列樣式 (Toolbar Styles) - ============================ */ -#jsmind-toolbar { - position: absolute; - z-index: 1000; -} - -/* 顏色選單 */ -.toolbar-color-palette { - position: absolute; - bottom: 30px; - background: #ccc; - border: 1px solid #ccc; - z-index: 1001; - min-width: 10em; -} - -.toolbar-color-palette-box { - width: 20px; - height: 20px; - margin: 2px; - display: inline-block; - cursor: pointer; -} - -/* ============================ - 遠程搜尋下拉選單 (Search Dropdown) - ============================ */ -.jsmind-suggestions { - position: absolute; - height: fit-content; - width: 200px; - background: white; - border: 1px solid #ccc; - box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); - z-index: 1000; -} - -.suggestion-item { - padding: 8px; - cursor: pointer; - border-bottom: 1px solid #eee; -} - -.suggestion-item:hover { - background: #f0f0f0; -} -.field-row{ - .column_entry_files{ - margin-top: 1em; - } - a{ - color: unset; - } - strong{ - display: none; - } - &:nth-child(2){ - a{ - font-weight: 500; - font-size: 1.5em; - } - - } - &:nth-child(4){ - display: none; - } +/* ============================ + 基礎布局 (Layout) + ============================ */ +.jsmind-inner { + position: relative; + overflow: auto; + width: 100%; + height: 100%; + outline: none; + user-select: none; /* 防止文字選取 */ +} + +.jsmind-inner canvas { + position: absolute; +} + +#jsmind_container { + position: relative; + height: 800px; + border: 1px solid #ccc; + background: #f4f4f4; +} + +/* ============================ + 層級管理 (Z-index) + ============================ */ +svg.jsmind, +canvas.jsmind { + position: absolute; + z-index: 1; +} + +jmnodes { + position: absolute; + z-index: 2; + background-color: rgba(0, 0, 0, 0); /* 透明背景,確保可點擊 */ +} + +jmnode { + position: absolute; + cursor: default; + max-width: 400px; +} + +jmexpander { + position: absolute; + width: 11px; + height: 11px; + display: block; + overflow: hidden; + line-height: 12px; + font-size: 10px; + text-align: center; + border-radius: 6px; + border-width: 1px; + border-style: solid; + cursor: pointer; +} + +/* ============================ + 文字溢出控制 (Overflow) + ============================ */ +.jmnode-overflow-wrap jmnodes { + min-width: 420px; +} + +.jmnode-overflow-hidden jmnode { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ============================ + 預設主題 (Default Theme) + ============================ */ +jmnode { + padding: 10px; + background-color: #fff; + color: #333; + border-radius: 5px; + box-shadow: 1px 1px 1px #666; + font: 1em/1.125 Verdana, Arial, Helvetica, sans-serif; +} + +jmnode:hover { + box-shadow: 2px 2px 8px #000; + filter: brightness(95%); + a{ + color: unset!important; + } +} + +jmnode.selected { + box-shadow: 2px 2px 8px #000; + filter: brightness(90%); +} + +jmnode.root { + font-size:1.2em; +/* 展開/收合按鈕 */ + border-color: gray; +} + +jmexpander:hover { + border-color: #000; +} + +/* ============================ + 響應式設計 (Responsive) + ============================ */ +@media screen and (max-device-width: 1024px) { + jmnode { + padding: 5px; + border-radius: 3px; + font-size: 1.2em; + } + + jmnode.root { + /* font-size: 21px; */ + font-size: 1.2em; + } +} + +/* ============================ + 工具列樣式 (Toolbar Styles) + ============================ */ +#jsmind-toolbar { + position: absolute; + z-index: 1000; +} + +/* 顏色選單 */ +.toolbar-color-palette { + position: absolute; + bottom: 30px; + background: #ccc; + border: 1px solid #ccc; + z-index: 1001; + min-width: 10em; +} + +.toolbar-color-palette-box { + width: 20px; + height: 20px; + margin: 2px; + display: inline-block; + cursor: pointer; +} + +/* ============================ + 遠程搜尋下拉選單 (Search Dropdown) + ============================ */ +.jsmind-suggestions { + position: absolute; + height: fit-content; + width: 200px; + background: white; + border: 1px solid #ccc; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + z-index: 1000; +} + +.suggestion-item { + padding: 8px; + cursor: pointer; + border-bottom: 1px solid #eee; +} + +.suggestion-item:hover { + background: #f0f0f0; +} +.field-row{ + .column_entry_files{ + margin-top: 1em; + } + a{ + color: unset; + } + strong{ + display: none; + } + &:nth-child(2){ + a{ + font-weight: 500; + font-size: 1.5em; + } + + } + &:nth-child(4){ + display: none; + } } \ No newline at end of file diff --git a/app/assets/stylesheets/universal_table/application.css b/app/assets/stylesheets/universal_table/application.css old mode 100644 new mode 100755 index a443db3..cfc4988 --- a/app/assets/stylesheets/universal_table/application.css +++ b/app/assets/stylesheets/universal_table/application.css @@ -1,15 +1,15 @@ -/* - * This is a manifest file that'll be compiled into application.css, which will include all the files - * listed below. - * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. - * - * You're free to add application-wide styles to this file and they'll appear at the bottom of the - * compiled file so the styles you add here take precedence over styles defined in any styles - * defined in the other CSS/SCSS files in this directory. It is generally better to create a new - * file per style scope. - * - *= require_tree . - *= require_self - */ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any styles + * defined in the other CSS/SCSS files in this directory. It is generally better to create a new + * file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/app/assets/stylesheets/universal_table/universal-table.scss b/app/assets/stylesheets/universal_table/universal-table.scss old mode 100644 new mode 100755 index 6c39be0..9a72e61 --- a/app/assets/stylesheets/universal_table/universal-table.scss +++ b/app/assets/stylesheets/universal_table/universal-table.scss @@ -1,471 +1,471 @@ -.main-forms .utable-heading-wrap { - margin-bottom: 8px; - border-radius: 0; - padding: 20px; - border-top-right-radius: 4px; - border-top-left-radius: 4px; - .control-group:last-child { - margin-bottom: 0; - } - .controls { - margin-left: 0; - } -} - -.utable-heading-header { - border-bottom: 1px solid #ddd; - margin-bottom: 20px; - padding-bottom: 10px; - h4 { - font-family: 'Chivo'; - line-height: 26px; - margin: 0; - } -} - -.utable-content { - .attributes { - padding: 20px; - &:nth-child(even) { - background-color: #e8e8e8; - } - &:nth-child(odd) { - background-color: #fff; - } - .draggable { - cursor: move; - } - .draggable i{ - cursor: move; - vertical-align: middle; - } - } -} -.attributes-checkbox { - margin-left: 8px; -} - -.main-forms > h3 { - margin: 5px 0 10px; - color: #333; - text-shadow: 0 1px 0 #ffffff; - font-family: 'Playfair Display SC', sans-serif; -} - -.main-forms fieldset { - background-color: #FFFFFF; - border: 1px solid #EDEDED; - margin-bottom: 20px; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; - .select-holder{ - margin-left: 20px; - display: inline-block; - span { - margin: 0 5px; - } - } -} - -.main-forms fieldset .input-area:after { - content: ""; - clear: both; - display: block; - height: 0; - visibility: hidden; -} - -.main-forms fieldset .input-area .nav-name { - float: left; - width: 100px; - padding-top: 5px; - text-align: right; - display: block; - margin-bottom: 5px; - font-size: 14px; - font-weight: normal; - line-height: 20px; -} - -.main-forms fieldset .input-area .controls textarea { - max-width: 500px; - max-height: 300px; - min-height: 86px; -} - -.main-forms fieldset .input-area .controls textarea.cke_source { - max-width: 100%; - max-height: 100%; -} - -.main-forms fieldset .input-area .controls hr { - margin: 5px 0 10px; -} - -.main-forms fieldset .input-area .controls h5 { - margin: 5px 0; -} - -.main-forms fieldset .input-area .controls .file-link { - margin-right: 10px; - display: inline-block; - width: 177px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.main-forms fieldset .input-area .controls .input-prepend { - margin-bottom: 5px; -} - -.main-forms fieldset .input-area .controls .input-prepend .btn-group { - padding: 4px 12px; -} - -.main-forms fieldset .input-area .controls .input-prepend .btn-group { - padding: 4px 12px; -} - -.main-forms fieldset .input-area .controls .input-prepend .btn-group .radio input[type="radio"], -.main-forms fieldset .input-area .controls .input-prepend .btn-group .checkbox input[type="checkbox"] { - margin: 5px 5px 0 0; -} - -.main-forms fieldset .input-area .controls .input-prepend .btn-group li { - text-align: left; - padding: 3px 10px; -} - -.main-forms fieldset .input-area .controls .input-prepend .btn-group li label { - padding-left: 5px; - display: block; -} - -.main-forms fieldset .input-area .controls .exist .input-prepend .btn-group:hover .dropdown-menu, -.main-forms fieldset .input-area .controls .add-target .input-prepend .btn-group:hover .dropdown-menu { - display: block; -} - -.main-forms fieldset .input-area .controls .exist .input-prepend, -.main-forms fieldset .input-area .controls .add-target .input-prepend { - margin-bottom: 10px; - display: inline-block; -} - -.main-forms fieldset .input-area .controls .exist .fileupload-new { - display: block; -} - -.main-forms fieldset .input-area .controls .exist .fileupload-new .input-prepend { - display: inline-block; -} - -.main-forms fieldset .input-area .controls .input-prepend a { - text-decoration: none; - color: #333333; -} - -.main-forms fieldset .input-area .controls .input-prepend .tab-content > .active { - display: inline-block; -} - -.main-forms fieldset .input-area .controls .add-btn { - margin: 3px 0; -} - -.main-forms fieldset .input-area .fileupload { - margin-right: 15px; - margin-bottom: 0; -} - -.main-forms fieldset .input-area .datetimepick { - margin-right: 5px; - margin-bottom: 5px; -} - -.main-forms fieldset .input-area .datetimepick .add-on { - line-height: 24px; - cursor: pointer; -} - -.main-forms fieldset .input-area .language-area .input-content .mceLayout { - width: 100%!important; -} - -.main-forms fieldset .input-area .module-nav, -.main-forms fieldset .input-area .language-nav { - margin: 0 0 20px; - padding: 0 0 15px 120px; - border-bottom: 1px solid #ddd; -} - -.main-forms fieldset .input-area .module-nav li, -.main-forms fieldset .input-area .language-nav li { - position: relative; -} - -.main-forms fieldset .input-area .module-nav li.active:before, -.main-forms fieldset .input-area .module-nav li.active:after, -.main-forms fieldset .input-area .language-nav li.active:before, -.main-forms fieldset .input-area .language-nav li.active:after { - display: block; - height: 0px; - width: 0px; - position: absolute; - bottom: -15px; - left: 50%; - margin-left: -5px; - content: ""; - border-style: solid; - border-width: 0 6px 6px 6px; - border-color: transparent transparent #EDEDED transparent; - z-index: 5 -} - -.main-forms fieldset .input-area .module-nav li.active:after { - display: none; -} - -.main-forms fieldset .input-area .language-nav li.active:after { - bottom: -16px; - margin-left: -4px; - border-width: 0 5px 5px 5px; - border-color: transparent transparent #FFFFFF transparent; -} - -.main-forms fieldset .input-area .module-nav { - margin-bottom: 0; - border-bottom: none; -} - -.main-forms fieldset .input-area .language-area, -.main-forms fieldset .input-area .module-area { - overflow: visible; -} - -.main-forms fieldset .input-area .module-area { - padding-top: 20px; - margin-bottom: 40px; - background-color: #EDEDED; - border-radius: 5px; - overflow: hidden; -} - -.main-forms fieldset .form-actions { - padding-left: 200px; - margin: 0px; - -webkit-border-radius: 0 0 4px 4px; - -moz-border-radius: 0 0 4px 4px; - border-radius: 0 0 4px 4px; -} - -.main-forms fieldset .input-area .nav-scroll { - margin-left: 120px; - width: 800px; - position: relative; - z-index: 1; - overflow: hidden; -} - -.main-forms fieldset .input-area .nav-scroll .scroller { - width: 1000px; - height: 100%; - float: left; - padding: 0; -} - -.main-forms fieldset .input-area .controls[data-toggle^="buttons-"] label { - position: relative; - margin: 0 0 5px; -} - -.main-forms fieldset .input-area .controls[data-toggle^="buttons-"] input[type="radio"], -.main-forms fieldset .input-area .controls[data-toggle^="buttons-"] input[type="checkbox"] { - margin-left: 0; - margin-top: 0; - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - display: block; - opacity: 0; -} - -.main-forms fieldset .input-area .question { - margin-top: 5px; -} - - -/* User Role Forms */ - -#attributes-area.clickHere { - min-height: 150px; - position: relative; -} - -#attributes-area.clickHere:before { - font-family: 'entypo'; - content: '\e0be'; - position: absolute; - font-size: 8em; - display: block; - bottom: 50px; - left: 175px; - color: #51a351; - opacity: .4; -} - -.main-forms .input-append .tab-content { - display: inline-block; - overflow: inherit; -} - -.main-forms .input-append .tab-content .active { - display: inline-block; - background-color: transparent; -} - -.main-forms .input-append .active { - border-color: #c5c5c5; - border-color: rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25); -} - -.main-forms .input-append > .btn-group > .btn:first-child { - margin-left: -1px; - -webkit-border-bottom-left-radius: 0px; - border-bottom-left-radius: 0px; - -webkit-border-top-left-radius: 0px; - border-top-left-radius: 0px; - -moz-border-radius-bottomleft: 0px; - -moz-border-radius-topleft: 0px; -} - -.main-forms .attributes { - padding-bottom: 20px; -} - -.main-forms .attributes .tab-content { - overflow: inherit; -} - -.main-forms .attributes.disabled label, -.main-forms .attributes.disabled h4 { - color: #e6e6e6; -} - -.main-forms .attributes-header { - border-bottom: 1px solid #ddd; - margin-bottom: 20px; - padding-bottom: 10px; -} - -.main-forms .attributes-header .btn { - margin-left: 5px; -} - -.main-forms .attributes-header h4 { - font-family: 'Chivo'; - line-height: 26px; - margin: 0; -} - -.main-forms .attributes-header h4 b { - padding: 0 1px; - border-style: dotted; - border-width: 0 2px; - border-color: #AAA; - margin-right: 5px; - cursor: move; -} - -.main-forms .attributes-header h4 i { - cursor: pointer; -} - -.main-forms .field-type { - background-color: #f5f5f5; - border-radius: 5px; - margin-bottom: 20px; - padding: 10px; -} - - -/* Responsive */ - -@media (max-width: 480px) { - .main-forms fieldset .input-area .nav-name { - float: none; - width: auto; - padding-top: 0; - text-align: left; - } - .main-forms fieldset .input-area .module-area { - padding: 20px; - } - .main-forms fieldset .input-area .module-nav, - .main-forms fieldset .input-area .language-nav { - padding: 0 0 15px 0px; - } - .main-forms fieldset .form-actions { - padding-right: 20px; - padding-left: 20px; - } - .main-forms fieldset .input-area .control-label { - width: auto; - } - .main-forms fieldset .input-area .controls { - margin-left: 0; - } - .main-forms fieldset .form-actions { - padding-left: 20px; - } -} - -// Bootstrap override -// fixing datepicker appearing behind modal - -.ut-table { - .image-preview { - width: 100px; - } - .image-expander { - position: relative; - display: inline-block; - &:hover { - .image-large { - opacity: 1; - } - } - } - .image-large { - border-radius: 2px; - -webkit-transition: .3s all ease-in-out; - transition: .3s all ease-in-out; - opacity: 0; - position: absolute; - left: calc(100% + 10px); - top: -10px; - width: 100%; - background-color: #fff; - padding: 10px; - box-shadow: 0 0 5px 0 rgba(0,0,0,.1); - } -} - -.ut-control-group-col { - float: left; - width: 350px; -} -.ut-control-group-col-right { - width: 190px; - .control-label { - width: auto; - margin-right: 10px; - } - .controls { - margin-left: 0; - } -} +.main-forms .utable-heading-wrap { + margin-bottom: 8px; + border-radius: 0; + padding: 20px; + border-top-right-radius: 4px; + border-top-left-radius: 4px; + .control-group:last-child { + margin-bottom: 0; + } + .controls { + margin-left: 0; + } +} + +.utable-heading-header { + border-bottom: 1px solid #ddd; + margin-bottom: 20px; + padding-bottom: 10px; + h4 { + font-family: 'Chivo'; + line-height: 26px; + margin: 0; + } +} + +.utable-content { + .attributes { + padding: 20px; + &:nth-child(even) { + background-color: #e8e8e8; + } + &:nth-child(odd) { + background-color: #fff; + } + .draggable { + cursor: move; + } + .draggable i{ + cursor: move; + vertical-align: middle; + } + } +} +.attributes-checkbox { + margin-left: 8px; +} + +.main-forms > h3 { + margin: 5px 0 10px; + color: #333; + text-shadow: 0 1px 0 #ffffff; + font-family: 'Playfair Display SC', sans-serif; +} + +.main-forms fieldset { + background-color: #FFFFFF; + border: 1px solid #EDEDED; + margin-bottom: 20px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + .select-holder{ + margin-left: 20px; + display: inline-block; + span { + margin: 0 5px; + } + } +} + +.main-forms fieldset .input-area:after { + content: ""; + clear: both; + display: block; + height: 0; + visibility: hidden; +} + +.main-forms fieldset .input-area .nav-name { + float: left; + width: 100px; + padding-top: 5px; + text-align: right; + display: block; + margin-bottom: 5px; + font-size: 14px; + font-weight: normal; + line-height: 20px; +} + +.main-forms fieldset .input-area .controls textarea { + max-width: 500px; + max-height: 300px; + min-height: 86px; +} + +.main-forms fieldset .input-area .controls textarea.cke_source { + max-width: 100%; + max-height: 100%; +} + +.main-forms fieldset .input-area .controls hr { + margin: 5px 0 10px; +} + +.main-forms fieldset .input-area .controls h5 { + margin: 5px 0; +} + +.main-forms fieldset .input-area .controls .file-link { + margin-right: 10px; + display: inline-block; + width: 177px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.main-forms fieldset .input-area .controls .input-prepend { + margin-bottom: 5px; +} + +.main-forms fieldset .input-area .controls .input-prepend .btn-group { + padding: 4px 12px; +} + +.main-forms fieldset .input-area .controls .input-prepend .btn-group { + padding: 4px 12px; +} + +.main-forms fieldset .input-area .controls .input-prepend .btn-group .radio input[type="radio"], +.main-forms fieldset .input-area .controls .input-prepend .btn-group .checkbox input[type="checkbox"] { + margin: 5px 5px 0 0; +} + +.main-forms fieldset .input-area .controls .input-prepend .btn-group li { + text-align: left; + padding: 3px 10px; +} + +.main-forms fieldset .input-area .controls .input-prepend .btn-group li label { + padding-left: 5px; + display: block; +} + +.main-forms fieldset .input-area .controls .exist .input-prepend .btn-group:hover .dropdown-menu, +.main-forms fieldset .input-area .controls .add-target .input-prepend .btn-group:hover .dropdown-menu { + display: block; +} + +.main-forms fieldset .input-area .controls .exist .input-prepend, +.main-forms fieldset .input-area .controls .add-target .input-prepend { + margin-bottom: 10px; + display: inline-block; +} + +.main-forms fieldset .input-area .controls .exist .fileupload-new { + display: block; +} + +.main-forms fieldset .input-area .controls .exist .fileupload-new .input-prepend { + display: inline-block; +} + +.main-forms fieldset .input-area .controls .input-prepend a { + text-decoration: none; + color: #333333; +} + +.main-forms fieldset .input-area .controls .input-prepend .tab-content > .active { + display: inline-block; +} + +.main-forms fieldset .input-area .controls .add-btn { + margin: 3px 0; +} + +.main-forms fieldset .input-area .fileupload { + margin-right: 15px; + margin-bottom: 0; +} + +.main-forms fieldset .input-area .datetimepick { + margin-right: 5px; + margin-bottom: 5px; +} + +.main-forms fieldset .input-area .datetimepick .add-on { + line-height: 24px; + cursor: pointer; +} + +.main-forms fieldset .input-area .language-area .input-content .mceLayout { + width: 100%!important; +} + +.main-forms fieldset .input-area .module-nav, +.main-forms fieldset .input-area .language-nav { + margin: 0 0 20px; + padding: 0 0 15px 120px; + border-bottom: 1px solid #ddd; +} + +.main-forms fieldset .input-area .module-nav li, +.main-forms fieldset .input-area .language-nav li { + position: relative; +} + +.main-forms fieldset .input-area .module-nav li.active:before, +.main-forms fieldset .input-area .module-nav li.active:after, +.main-forms fieldset .input-area .language-nav li.active:before, +.main-forms fieldset .input-area .language-nav li.active:after { + display: block; + height: 0px; + width: 0px; + position: absolute; + bottom: -15px; + left: 50%; + margin-left: -5px; + content: ""; + border-style: solid; + border-width: 0 6px 6px 6px; + border-color: transparent transparent #EDEDED transparent; + z-index: 5 +} + +.main-forms fieldset .input-area .module-nav li.active:after { + display: none; +} + +.main-forms fieldset .input-area .language-nav li.active:after { + bottom: -16px; + margin-left: -4px; + border-width: 0 5px 5px 5px; + border-color: transparent transparent #FFFFFF transparent; +} + +.main-forms fieldset .input-area .module-nav { + margin-bottom: 0; + border-bottom: none; +} + +.main-forms fieldset .input-area .language-area, +.main-forms fieldset .input-area .module-area { + overflow: visible; +} + +.main-forms fieldset .input-area .module-area { + padding-top: 20px; + margin-bottom: 40px; + background-color: #EDEDED; + border-radius: 5px; + overflow: hidden; +} + +.main-forms fieldset .form-actions { + padding-left: 200px; + margin: 0px; + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} + +.main-forms fieldset .input-area .nav-scroll { + margin-left: 120px; + width: 800px; + position: relative; + z-index: 1; + overflow: hidden; +} + +.main-forms fieldset .input-area .nav-scroll .scroller { + width: 1000px; + height: 100%; + float: left; + padding: 0; +} + +.main-forms fieldset .input-area .controls[data-toggle^="buttons-"] label { + position: relative; + margin: 0 0 5px; +} + +.main-forms fieldset .input-area .controls[data-toggle^="buttons-"] input[type="radio"], +.main-forms fieldset .input-area .controls[data-toggle^="buttons-"] input[type="checkbox"] { + margin-left: 0; + margin-top: 0; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + display: block; + opacity: 0; +} + +.main-forms fieldset .input-area .question { + margin-top: 5px; +} + + +/* User Role Forms */ + +#attributes-area.clickHere { + min-height: 150px; + position: relative; +} + +#attributes-area.clickHere:before { + font-family: 'entypo'; + content: '\e0be'; + position: absolute; + font-size: 8em; + display: block; + bottom: 50px; + left: 175px; + color: #51a351; + opacity: .4; +} + +.main-forms .input-append .tab-content { + display: inline-block; + overflow: inherit; +} + +.main-forms .input-append .tab-content .active { + display: inline-block; + background-color: transparent; +} + +.main-forms .input-append .active { + border-color: #c5c5c5; + border-color: rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25); +} + +.main-forms .input-append > .btn-group > .btn:first-child { + margin-left: -1px; + -webkit-border-bottom-left-radius: 0px; + border-bottom-left-radius: 0px; + -webkit-border-top-left-radius: 0px; + border-top-left-radius: 0px; + -moz-border-radius-bottomleft: 0px; + -moz-border-radius-topleft: 0px; +} + +.main-forms .attributes { + padding-bottom: 20px; +} + +.main-forms .attributes .tab-content { + overflow: inherit; +} + +.main-forms .attributes.disabled label, +.main-forms .attributes.disabled h4 { + color: #e6e6e6; +} + +.main-forms .attributes-header { + border-bottom: 1px solid #ddd; + margin-bottom: 20px; + padding-bottom: 10px; +} + +.main-forms .attributes-header .btn { + margin-left: 5px; +} + +.main-forms .attributes-header h4 { + font-family: 'Chivo'; + line-height: 26px; + margin: 0; +} + +.main-forms .attributes-header h4 b { + padding: 0 1px; + border-style: dotted; + border-width: 0 2px; + border-color: #AAA; + margin-right: 5px; + cursor: move; +} + +.main-forms .attributes-header h4 i { + cursor: pointer; +} + +.main-forms .field-type { + background-color: #f5f5f5; + border-radius: 5px; + margin-bottom: 20px; + padding: 10px; +} + + +/* Responsive */ + +@media (max-width: 480px) { + .main-forms fieldset .input-area .nav-name { + float: none; + width: auto; + padding-top: 0; + text-align: left; + } + .main-forms fieldset .input-area .module-area { + padding: 20px; + } + .main-forms fieldset .input-area .module-nav, + .main-forms fieldset .input-area .language-nav { + padding: 0 0 15px 0px; + } + .main-forms fieldset .form-actions { + padding-right: 20px; + padding-left: 20px; + } + .main-forms fieldset .input-area .control-label { + width: auto; + } + .main-forms fieldset .input-area .controls { + margin-left: 0; + } + .main-forms fieldset .form-actions { + padding-left: 20px; + } +} + +// Bootstrap override +// fixing datepicker appearing behind modal + +.ut-table { + .image-preview { + width: 100px; + } + .image-expander { + position: relative; + display: inline-block; + &:hover { + .image-large { + opacity: 1; + } + } + } + .image-large { + border-radius: 2px; + -webkit-transition: .3s all ease-in-out; + transition: .3s all ease-in-out; + opacity: 0; + position: absolute; + left: calc(100% + 10px); + top: -10px; + width: 100%; + background-color: #fff; + padding: 10px; + box-shadow: 0 0 5px 0 rgba(0,0,0,.1); + } +} + +.ut-control-group-col { + float: left; + width: 350px; +} +.ut-control-group-col-right { + width: 190px; + .control-label { + width: auto; + margin-right: 10px; + } + .controls { + margin-left: 0; + } +} diff --git a/app/controllers/admin/mind_maps_controller.rb b/app/controllers/admin/mind_maps_controller.rb old mode 100644 new mode 100755 index 24c4416..2a8bbbe --- a/app/controllers/admin/mind_maps_controller.rb +++ b/app/controllers/admin/mind_maps_controller.rb @@ -1,52 +1,52 @@ -class Admin::MindMapsController < OrbitAdminController - - def index - @table_fields = ["universal_table.mind_map","universal_table.created_time"] - @table = UTable.find(params[:id]) - @mind_maps = Kaminari.paginate_array(@table.mind_maps).page(params[:page]).per(10) - end - - def new - @table = UTable.find(params[:table]) - @mind_map = MindMap.new - end - - def edit - uid = params[:id].split("-").last - @mind_map = MindMap.where(:uid => uid).first - @table = @mind_map.u_table - end - - def create - mind_map = MindMap.new - mind_params = mind_map_params - mind_params[:mind_map_data] = JSON.parse(mind_params[:mind_map_data]) - mind_map.update_attributes(mind_map_params) - mind_map.save - redirect_to "/admin/universal_table/#{mind_map.u_table.id.to_s}/mind_maps" - end - - def update - mind_map = MindMap.find(params[:id]) - mind_params = mind_map_params - mind_params[:mind_map_data] = JSON.parse(mind_params[:mind_map_data]) - mind_map.update_attributes(mind_map_params) - mind_map.save - redirect_to "/admin/universal_table/#{mind_map.u_table.id.to_s}/mind_maps" - end - - def destroy - uid = params[:id].split("-").last - mind_map = MindMap.where(:uid => uid).first - table = mind_map.u_table - mind_map.destroy - redirect_to "/admin/universal_table/#{table.id.to_s}/mind_maps" - end - - private - - def mind_map_params - params.require(:mind_map).permit! - end - -end +class Admin::MindMapsController < OrbitAdminController + + def index + @table_fields = ["universal_table.mind_map","universal_table.created_time"] + @table = UTable.find(params[:id]) + @mind_maps = Kaminari.paginate_array(@table.mind_maps).page(params[:page]).per(10) + end + + def new + @table = UTable.find(params[:table]) + @mind_map = MindMap.new + end + + def edit + uid = params[:id].split("-").last + @mind_map = MindMap.where(:uid => uid).first + @table = @mind_map.u_table + end + + def create + mind_map = MindMap.new + mind_params = mind_map_params + mind_params[:mind_map_data] = JSON.parse(mind_params[:mind_map_data]) + mind_map.update_attributes(mind_map_params) + mind_map.save + redirect_to "/admin/universal_table/#{mind_map.u_table.id.to_s}/mind_maps" + end + + def update + mind_map = MindMap.find(params[:id]) + mind_params = mind_map_params + mind_params[:mind_map_data] = JSON.parse(mind_params[:mind_map_data]) + mind_map.update_attributes(mind_map_params) + mind_map.save + redirect_to "/admin/universal_table/#{mind_map.u_table.id.to_s}/mind_maps" + end + + def destroy + uid = params[:id].split("-").last + mind_map = MindMap.where(:uid => uid).first + table = mind_map.u_table + mind_map.destroy + redirect_to "/admin/universal_table/#{table.id.to_s}/mind_maps" + end + + private + + def mind_map_params + params.require(:mind_map).permit! + end + +end diff --git a/app/controllers/admin/universal_tables_controller.rb b/app/controllers/admin/universal_tables_controller.rb old mode 100644 new mode 100755 index 2517611..a35bcb7 --- a/app/controllers/admin/universal_tables_controller.rb +++ b/app/controllers/admin/universal_tables_controller.rb @@ -135,111 +135,131 @@ end render :json => {"success" => true, "title" => title}.to_json end - def import_data_from_excel - workbook = RubyXL::Parser.parse(params["import_data"].tempfile) - response = {} - current_locale = I18n.locale - table = UTable.find(params["universal_table_id"]) rescue nil - if !table.nil? - sheet = workbook[0] - if sheet.count <= 503 - columns = sheet[1].cells.collect.with_index{|c,i| - c.value.blank? ? table.table_columns.where(:title => sheet[0].cells[i].value.to_s.split("-").first.strip).first : table.table_columns.where(:key => c.value.to_s).first - } - languages = sheet[2].cells.collect{|c| - c.value.split("-").last rescue nil - } - sheet.each_with_index do |row, i| - next if i < 3 - te = TableEntry.new - te.u_table = table - skip = 0 - row.cells.each_with_index do |cell,index| - if skip > 1 - skip = skip - 1 - next - end - skip = 0 - val = cell.value rescue nil - tc = columns[index] - if !tc.nil? - ce = ColumnEntry.new - case tc.type - when "text" - v = {} - @site_in_use_locales.sort.each_with_index do |locale,x| - v[locale.to_s] = row.cells[index + x].value rescue nil - skip = skip + 1 - end - ce.text_translations = v - when "integer" - ce.number = (val.blank? ? nil : val) - when "image" - ce.remote_image_url = val - when "file" - if !val.nil? - val.split("\;").each do |remote_file| - file = ColumnEntryFile.new - file.remote_file_url = remote_file - filename = {} - file.choose_lang.reject(&:empty?).each do |lang| - filename[lang] = file.file.file.filename - end - file.file_title_translations = filename - # file.column_entry_id = ce.id - file.save - ce.column_entry_files << file - end - end - when "editor" - v = {} - @site_in_use_locales.sort.each_with_index do |locale,x| - v[locale.to_s] = row.cells[index + x].value rescue nil - skip = skip + 1 - end - ce.content_translations = v - when "date" - ce.date = val - when "period" - if tc.date_format == "yyyy" && !val.nil? - val = val.to_s + "/01/01" - end - skip = 2 - ce.period_from = val - val = row.cells[index + 1].value rescue nil - if tc.date_format == "yyyy" && !val.nil? - val = val.to_s + "/01/01" - end - ce.period_to = val - end - ce.table_column_id = tc.id - ce.save - te.column_entries << ce - else - if index == (columns.count - 2) - create_get_table_tags(te, val.split("\;")) - end - if index == (columns.count - 1) - te.related_entries = TableEntry.where(:uid.in => val.to_s.split("\;")).pluck(:id).join(",") - end - end - end - te.save - te.fix_have_data - end - response["success"] = true - response["count"] = table.table_entries.count - response["id"] = table.id.to_s - else - response["success"] = false - response["msg"] = "More than 500 entries. Please split the entries in different files." - end - else - response["success"] = false - response["msg"] = "Table not found." - end - render :json => response.to_json - end +def import_data_from_excel + site_in_use_locales = @site_in_use_locales.sort + workbook = RubyXL::Parser.parse(params["import_data"].tempfile) + response = {} + current_locale = I18n.locale + table = UTable.find(params["universal_table_id"]) rescue nil + if table.nil? + render json: { success: false, msg: "Table not found." }.to_json and return + end + + sheet = workbook[0] + if sheet.count > 503 + render json: { success: false, msg: "More than 500 entries. Please split the entries in different files." }.to_json and return + end + + # 前三列是欄位名、key、格式描述 + column_titles = sheet[0].cells.map { |c| c&.value.to_s.strip } + column_keys = sheet[1].cells.map { |c| c&.value.to_s.strip } + column_types = sheet[2].cells.map { |c| c&.value.to_s.strip } + + # 準備欄位對應 + columns = column_keys.uniq.map.with_index do |key, i| + tc = table.table_columns.where(key: key).first + [i, tc] + end.to_h + + sheet.each_with_index do |row, i| + next if i < 3 || row.cells.compact.map { |c| c.value.to_s.strip }.all?(&:blank?) + uid_val = row[0]&.value.to_s.strip rescue nil + te = uid_val.present? ? + TableEntry.where(uid: uid_val, u_table_id: table.id).first_or_initialize : + TableEntry.new + te.u_table = table + skip = 0 + + tc_idx = 0 + + row.cells.each_with_index do |cell, col_idx| + next if skip > 0 && (skip -= 1) >= 0 + + val = cell&.value + tc = columns[tc_idx] + tc_idx += 1 + next if tc.nil? + + ce = te.column_entries.where(table_column_id: tc.id).first + ce = ColumnEntry.new(table_column_id: tc.id) if ce.nil? + case tc.type + when "text", "editor" + v = {} + site_in_use_locales.each_with_index do |locale, offset| + v[locale.to_s] = row[col_idx + offset]&.value.to_s rescue "" + end + skip = site_in_use_locales.size - 1 + if tc.type == "text" + ce.text_translations = v + else + ce.content_translations = v + end + + when "integer" + ce.number = val.present? ? val.to_i : nil + + when "image" + ce.remote_image_url = val if val.present? + when "file" + file_urls = val.to_s.split(";").map(&:strip) + file_titles = row[col_idx + 1]&.value.to_s.split(";").map(&:strip) + skip = 1 + + ce.column_entry_files.destroy_all + ce.column_entry_files = [] + + file_urls.each_with_index do |remote_url, file_idx| + next if remote_url.blank? + file = ColumnEntryFile.new + file.remote_file_url = remote_url + + # 處理多語言標題 + titles = {} + site_in_use_locales.each do |locale| + titles[locale.to_s] = file_titles[file_idx] rescue file.file.file.filename + end + file.file_title_translations = titles + file.save! + ce.column_entry_files << file + end + when "date" + ce.date = val + + when "period" + skip = 1 + ce.period_from = val + ce.period_to = row[col_idx + 1]&.value rescue nil + end + + ce.save! + te.column_entries << ce + end + + # hashtags (倒數第2欄) + if row.cells.count >= 2 + tags_text = row.cells[-2]&.value.to_s rescue "" + create_get_table_tags(te, tags_text.split(";")) + end + + # related_entries (倒數第1欄) + if row.cells.count >= 1 + related_uids = row.cells[-1]&.value.to_s.split(";").map(&:strip) + related_ids = TableEntry.where(:uid.in => related_uids).pluck(:id) + te.related_entries = related_ids.join(",") + end + + te.save! + te.fix_have_data + te.uid = uid_val if uid_val.present? + te.save! + end + + render json: { + success: true, + count: table.table_entries.count, + id: table.id.to_s + }.to_json +end def new_entry uid = params[:universal_table_id].split("-").last @@ -402,4 +422,4 @@ end def is_uuid?(str) !!(str =~ /\A[\da-f]{24}\z/i || str =~ /\A[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}\z/i) end -end +end \ No newline at end of file diff --git a/app/controllers/universal_tables_controller.rb b/app/controllers/universal_tables_controller.rb old mode 100644 new mode 100755 index 8d4b323..52a5e88 --- a/app/controllers/universal_tables_controller.rb +++ b/app/controllers/universal_tables_controller.rb @@ -10,67 +10,114 @@ class UniversalTablesController < ApplicationController end end - def export_filtered - table = UTable.where(:category_id => params[:cat]).first rescue nil - page = Page.where(:page_id => params[:page_id]).first - if !table.nil? - host_url = Site.first.root_url - if host_url == "http://" - host_url = request.protocol + request.host_with_port - end - @rows = [] - @tablecolumns = table.table_columns.where(:display_in_index => true).asc(:order) - entries = get_entries(params, table, page, false) - entries.each do |te| - cols = [] - sort_value = "" - @tablecolumns.each do |column| - ce = te.column_entries.where(:table_column_id => column.id).first rescue nil - if !ce.nil? - text = "" - case ce.type - when "text" - text = ce.text - when "integer" - text = ce.number - when "editor" - text = ce.content - when "date" - text = format_date(ce.date, column.date_format) - when "period" - text = format_date(ce.period_from, column.date_format) + " ~ " + format_date(ce.period_to, column.date_format) - text = "" if text.starts_with?(" ~") - when "image" - text = host_url + ce.image.thumb.url - when "file" - file_links = [] - locale = I18n.locale.to_s - ce.column_entry_files.desc(:sort_number).each do |entry_file| - next unless entry_file.choose_lang_display(locale) - file_links << (host_url + entry_file.get_link) - end - text = file_links.join("\r\n") - end - cols << {"text" => text} - else - cols << {"text" => ""} - end - end - @rows << { - "columns" => cols - } - end - excel_name = table.title + ".xlsx" - excel_name = 'attachment; filename="' + excel_name + '"' +def export_filtered + table = UTable.where(:category_id => params[:cat]).first rescue nil + page = Page.where(:page_id => params[:page_id]).first + return unless table - end - respond_to do |format| - format.xlsx { - response.headers['Content-Disposition'] = excel_name - } - end - end + host_url = Site.first.root_url + host_url = request.protocol + request.host_with_port if host_url == "http://" + @rows = [] + @tablecolumns = [] + + # 處理 file 欄位雙欄輸出(連結與註解) + table.table_columns.where(display_in_index: true).asc(:order).each do |column| + if column.type == "file" + @tablecolumns << column + @tablecolumns << column.dup.tap { |c| c.define_singleton_method(:_file_title_column?) { true } } + else + @tablecolumns << column + end + end + + entries = get_entries(params, table, page, false) + + entries.each do |te| + cols = [] + + @tablecolumns.each do |column| + # 跳過副註解欄(由主 file 欄處理) + if column.respond_to?(:_file_title_column?) && column._file_title_column? + next + end + + ce = te.column_entries.where(table_column_id: column.id).first rescue nil + + if ce.present? + case column.type + when "text" + cols << { "text" => ce.text.to_s } + + when "integer" + cols << { "text" => ce.number.to_s } + + when "editor" + cols << { "text" => ce.content.to_s } + + when "date" + cols << { "text" => format_date(ce.date, column.date_format).to_s } + + when "period" + from = format_date(ce.period_from, column.date_format) + to = format_date(ce.period_to, column.date_format) + text = from.blank? && to.present? ? to : "#{from} ~ #{to}" + cols << { "text" => text.to_s } + + when "image" + text = ce.image&.thumb&.url ? (host_url + ce.image.thumb.url) : "" + cols << { "text" => text } + +when "file" + file_links = [] + file_titles = [] + locale = I18n.locale.to_s + + ce.column_entry_files.desc(:sort_number).each do |entry_file| + next unless entry_file.choose_lang_display(locale) + + file_links << (host_url + entry_file.get_link) + + title = if entry_file.respond_to?(:file_title_translations) && entry_file.file_title_translations.is_a?(Hash) + entry_file.file_title_translations[locale] + elsif entry_file.file_title.is_a?(Hash) + entry_file.file_title[locale] + else + entry_file.file_title + end + + title = entry_file.file.filename.to_s if title.blank? + file_titles << title + end + + cols << { "text" => file_links.join("\r\n") } + cols << { "text" => file_titles.join("\r\n") } + + else + cols << { "text" => "" } + end + else + if column.type == "file" + cols << { "text" => "" } + cols << { "text" => "" } + else + cols << { "text" => "" } + end + end + end + + @rows << { "columns" => cols } + end + + excel_name = "#{table.title}.xlsx" + excel_name = 'attachment; filename="' + excel_name + '"' + + respond_to do |format| + format.xlsx { + response.headers['Content-Disposition'] = excel_name + } + end +end def get_query(params) if params["column"].present? q = {params["column"] => params["q"]} diff --git a/app/helpers/admin/universal_tables_helper.rb b/app/helpers/admin/universal_tables_helper.rb old mode 100644 new mode 100755 index 366692e..e7ddc66 --- a/app/helpers/admin/universal_tables_helper.rb +++ b/app/helpers/admin/universal_tables_helper.rb @@ -1,43 +1,43 @@ -module Admin::UniversalTablesHelper - - def format_date(date, format, for_editing=false) - case format - when "yyyy/MM/dd hh:mm" - f = "%Y/%m/%d %H:%M" - when "yyyy/MM/dd" - f = "%Y/%m/%d" - when "yyyy/MM" - f = "%Y/%m" - when "yyyy" - f = "%Y" - f = "%Y/%m" if for_editing - end - d = date.strftime(f) rescue "" - return d - end - - def render_unique_texts(f,column,i) - select_values = column.column_entries.distinct(:text) - select = "" - "
Or " + select + "
" - end - - def render_unique_number(f,column,i) - select_values = column.column_entries.distinct(:number) - select = "" - "
Or " + select + "
" - end - -end +module Admin::UniversalTablesHelper + + def format_date(date, format, for_editing=false) + case format + when "yyyy/MM/dd hh:mm" + f = "%Y/%m/%d %H:%M" + when "yyyy/MM/dd" + f = "%Y/%m/%d" + when "yyyy/MM" + f = "%Y/%m" + when "yyyy" + f = "%Y" + f = "%Y/%m" if for_editing + end + d = date.strftime(f) rescue "" + return d + end + + def render_unique_texts(f,column,i) + select_values = column.column_entries.distinct(:text) + select = "" + "
Or " + select + "
" + end + + def render_unique_number(f,column,i) + select_values = column.column_entries.distinct(:number) + select = "" + "
Or " + select + "
" + end + +end diff --git a/app/models/column_entry.rb b/app/models/column_entry.rb old mode 100644 new mode 100755 index ed35731..aebdeb3 --- a/app/models/column_entry.rb +++ b/app/models/column_entry.rb @@ -1,102 +1,102 @@ -class ColumnEntry - include Mongoid::Document - include Mongoid::Timestamps - - include Admin::UniversalTablesHelper - include ActionView::Helpers::NumberHelper - - field :text, :localize => true - field :content, :localize => true - field :date, type: DateTime - field :period_from, type: DateTime - field :period_to, type: DateTime - field :number, type: Integer - - mount_uploader :image, ImageUploader - - has_many :column_entry_files, :autosave => true, :dependent => :destroy - accepts_nested_attributes_for :column_entry_files, :allow_destroy => true - after_save :save_column_entry_files - - belongs_to :table_entry, index: true - belongs_to :table_column, index: true - - I18n.available_locales.each do |locale| - index({"text.#{locale}" => 1}, { unique: false, background: true }) - index({"content.#{locale}" => 1}, { unique: false, background: true }) - end - - - def type - self.table_column.type - end - - def save_column_entry_files - return if @skip_callback - self.column_entry_files.each do |t| - if t.should_destroy - t.destroy - end - end - end - - def have_data(locale) - flag = nil - case self.type - when "text" - flag = self.text_translations[locale].present? - when "integer" - flag = true - when "editor" - flag = self.content_translations[locale].present? - when "date" - flag = self.date.present? - when "period" - flag = self.period_from.present? || self.period_to.present? - when "image" - flag = self.image.present? - when "file" - flag = false - self.column_entry_files.each do |entry_file| - next unless entry_file.choose_lang_display(locale) && entry_file.file.present? - flag = true - end - else - flag = true - end - flag - end - - def get_frontend_text(column) - text = "" - case self.type - when "text" - text = self.text - when "integer" - text = self.number - when "editor" - text = self.content - when "date" - text = format_date(self.date, column.date_format) - when "period" - text = format_date(self.period_from, column.date_format) + " ~ " + format_date(self.period_to, column.date_format) - text = "" if text.starts_with?(" ~") - when "image" - text = "" - when "file" - locale = I18n.locale.to_s - text = "
" - end - text - end -end +class ColumnEntry + include Mongoid::Document + include Mongoid::Timestamps + + include Admin::UniversalTablesHelper + include ActionView::Helpers::NumberHelper + + field :text, :localize => true + field :content, :localize => true + field :date, type: DateTime + field :period_from, type: DateTime + field :period_to, type: DateTime + field :number, type: Integer + + mount_uploader :image, ImageUploader + + has_many :column_entry_files, :autosave => true, :dependent => :destroy + accepts_nested_attributes_for :column_entry_files, :allow_destroy => true + after_save :save_column_entry_files + + belongs_to :table_entry, index: true + belongs_to :table_column, index: true + + I18n.available_locales.each do |locale| + index({"text.#{locale}" => 1}, { unique: false, background: true }) + index({"content.#{locale}" => 1}, { unique: false, background: true }) + end + + + def type + self.table_column.type + end + + def save_column_entry_files + return if @skip_callback + self.column_entry_files.each do |t| + if t.should_destroy + t.destroy + end + end + end + + def have_data(locale) + flag = nil + case self.type + when "text" + flag = self.text_translations[locale].present? + when "integer" + flag = true + when "editor" + flag = self.content_translations[locale].present? + when "date" + flag = self.date.present? + when "period" + flag = self.period_from.present? || self.period_to.present? + when "image" + flag = self.image.present? + when "file" + flag = false + self.column_entry_files.each do |entry_file| + next unless entry_file.choose_lang_display(locale) && entry_file.file.present? + flag = true + end + else + flag = true + end + flag + end + + def get_frontend_text(column) + text = "" + case self.type + when "text" + text = self.text + when "integer" + text = self.number + when "editor" + text = self.content + when "date" + text = format_date(self.date, column.date_format) + when "period" + text = format_date(self.period_from, column.date_format) + " ~ " + format_date(self.period_to, column.date_format) + text = "" if text.starts_with?(" ~") + when "image" + text = "" + when "file" + locale = I18n.locale.to_s + text = "
    " + self.column_entry_files.desc(:sort_number).each do |entry_file| + next unless entry_file.choose_lang_display(locale) + file_title = entry_file.get_file_title + if entry_file.file.content_type.start_with?('audio/') + text += "
    #{file_title}
    " + else + text += "
  • #{file_title}(#{number_to_human_size(entry_file.file.size)})#{entry_file.download_count}
  • " + end + end + text += "
" + end + text + end +end diff --git a/app/models/column_entry_file.rb b/app/models/column_entry_file.rb old mode 100644 new mode 100755 index 48073fa..f384d13 --- a/app/models/column_entry_file.rb +++ b/app/models/column_entry_file.rb @@ -5,7 +5,7 @@ class ColumnEntryFile mount_uploader :file, AssetUploader - field :file_title, localize: true + field :file_title, type: String, localize: true # field :description field :download_count, type: Integer, default: 0 field :choose_lang, :type => Array, :default => I18n.available_locales.map{|l| l.to_s} diff --git a/app/models/mind_map.rb b/app/models/mind_map.rb old mode 100644 new mode 100755 index 05de2d1..0d9b9c5 --- a/app/models/mind_map.rb +++ b/app/models/mind_map.rb @@ -1,11 +1,11 @@ -class MindMap - include Mongoid::Document - include Mongoid::Timestamps - include Slug - - field :title, as: :slug_title, localize: true - field :mind_map_data, type: Array, default: [] - - belongs_to :u_table - # has_many :mind_map_nodes, :dependent => :destroy -end +class MindMap + include Mongoid::Document + include Mongoid::Timestamps + include Slug + + field :title, as: :slug_title, localize: true + field :mind_map_data, type: Array, default: [] + + belongs_to :u_table + # has_many :mind_map_nodes, :dependent => :destroy +end diff --git a/app/models/table_column.rb b/app/models/table_column.rb old mode 100644 new mode 100755 index 81e720e..1c7709a --- a/app/models/table_column.rb +++ b/app/models/table_column.rb @@ -1,57 +1,57 @@ -class TableColumn - include Mongoid::Document - include Mongoid::Timestamps - - field :key - field :title, localize: true - field :display_in_index, type: Boolean, default: true - field :type - field :date_format, default: "YYYY/MM/DD" - field :is_link_to_show, type: Boolean, default: false - field :is_searchable, type: Boolean - field :order, type: Integer - field :make_categorizable, type: Boolean, default: false - field :default_ordered_field, type: Boolean, default: false - field :order_direction,type: String,default: 'desc' - belongs_to :u_table, index: true - - has_many :column_entries - - index({display_in_index: -1}, { unique: false, background: true }) - index({order: 1}, { unique: false, background: true }) - index({key: 1}, { unique: false, background: true }) - - def sort_hash(direction) - case self.type - when "text" - {text: direction} - when "integer" - {number: direction} - when "editor" - {content: direction} - when "image" - {image: direction} - when "date" - {date: direction} - when "period" - {period_from: direction,period_to: direction} - end - end - - def is_searchable - tmp = self[:is_searchable] - if tmp.nil? - case self.type - when "date", "period","image" - tmp = false - else - tmp = self.display_in_index - end - end - tmp - end - - def self.filter_searchable - self.any_of({is_searchable: true}, {is_searchable:nil, display_in_index: true, :type.nin=> ["date", "period","image"]}) - end +class TableColumn + include Mongoid::Document + include Mongoid::Timestamps + + field :key + field :title, localize: true + field :display_in_index, type: Boolean, default: true + field :type + field :date_format, default: "YYYY/MM/DD" + field :is_link_to_show, type: Boolean, default: false + field :is_searchable, type: Boolean + field :order, type: Integer + field :make_categorizable, type: Boolean, default: false + field :default_ordered_field, type: Boolean, default: false + field :order_direction,type: String,default: 'desc' + belongs_to :u_table, index: true + + has_many :column_entries + + index({display_in_index: -1}, { unique: false, background: true }) + index({order: 1}, { unique: false, background: true }) + index({key: 1}, { unique: false, background: true }) + + def sort_hash(direction) + case self.type + when "text" + {text: direction} + when "integer" + {number: direction} + when "editor" + {content: direction} + when "image" + {image: direction} + when "date" + {date: direction} + when "period" + {period_from: direction,period_to: direction} + end + end + + def is_searchable + tmp = self[:is_searchable] + if tmp.nil? + case self.type + when "date", "period","image" + tmp = false + else + tmp = self.display_in_index + end + end + tmp + end + + def self.filter_searchable + self.any_of({is_searchable: true}, {is_searchable:nil, display_in_index: true, :type.nin=> ["date", "period","image"]}) + end end \ No newline at end of file diff --git a/app/models/table_entry.rb b/app/models/table_entry.rb old mode 100644 new mode 100755 index 314a3ba..2591794 --- a/app/models/table_entry.rb +++ b/app/models/table_entry.rb @@ -1,170 +1,170 @@ -class TableEntry - include Mongoid::Document - include Mongoid::Timestamps - include OrbitModel::Status - include Slug - - attr_accessor :sort_value - field :have_data, type: Boolean, localize: true - field :sort_number, type: Integer - field :view_count, type: Integer, default: 0 - field :related_entries, type: String, default: "" - - has_many :column_entries, :dependent => :destroy - belongs_to :u_table, index: true - has_and_belongs_to_many :table_tags, inverse_of: :table_entries - - accepts_nested_attributes_for :column_entries, :allow_destroy => true - scope :can_display, ->{where(:is_hidden.ne=>true)} - - I18n.available_locales.each do |locale| - index({"have_data.#{locale}" => 1}, { unique: false, background: true }) - end - - before_save do - if self[:sort_number].nil? - other_record = self.class.where(:u_table_id=> self.u_table_id, :id.ne=> self.id).order_by(sort_number: :desc).first - sort_number_to_set = other_record ? other_record.sort_number : 0 - self.sort_number = sort_number_to_set.to_i + 1 - end - self.get_have_data - end - - def fix_have_data - have_data_translations = self.get_have_data - self.class.where(:id=> self.id).update_all(have_data_translations.map{|l, v| ["have_data.#{l}", v]}.to_h) - end - - def get_related_entries - tids = self.related_entries.split(',') - TableEntry.find(tids) - end - - def get_related_entries_uid - tids = self.related_entries.split(',') - TableEntry.where(:id.in => tids).pluck(:uid).join(", ") - end - - def get_have_data - searchable_field_ids = TableColumn.filter_searchable.where(u_table_id: self.u_table_id).pluck(:id) - searchable_column_entries = self.column_entries.where(:table_column_id.in=> searchable_field_ids).to_a - self.have_data_translations = I18n.available_locales.map do |locale| - flag = searchable_column_entries.detect{|ce| ce.have_data(locale)}.present? - [locale.to_s, flag] - end.to_h - end - - def tags_for_frontend - params = OrbitHelper.params - self.table_tags.map{|tt| - "#" + tt.title + "" - }.join(" ") - end - - def self.u_table - UTable.find(criteria.selector['u_table_id']) - end - - def self.get_sort_field(params: nil, table: nil) - field = nil - direction = nil - if table.nil? - table = self.u_table - end - if params - if !params[:sortcolumn].blank? - field = params[:sortcolumn] - direction = params[:sort] - else - field = params[:sort].blank? ? nil : params[:sort] - direction = params[:order].blank? ? 'desc' : params[:order] - end - if field.nil? - field, direction = table.default_ordered - else - field = field.to_s - if !(field=='created_at' || field == 'sort_number') - field = table.table_columns.where(key: field).first || table.table_columns.where(title: field).first - end - end - end - [table, field, direction] - end - - def self.sorted(entries: nil, params: nil, table: nil, field: nil, direction: nil, paginated: true) - if field.nil? || direction.nil? - table, field, direction = self.get_sort_field(params: params, table: table) - end - if entries.nil? - entries = table.table_entries - end - if (field=='created_at' || field == 'sort_number') - values = entries.order_by({field => direction}) - else - column_to_sort = field - if entries.selector.present? - column_entries = ColumnEntry.where(:table_column_id=>column_to_sort.id,:table_entry_id.in => entries.pluck(:id)).order_by(column_to_sort.sort_hash(direction)) - else - column_entries = ColumnEntry.where(:table_column_id=>column_to_sort.id).order_by(column_to_sort.sort_hash(direction)) - end - values = column_entries.map{|v| v.table_entry} - if paginated - values = Kaminari.paginate_array(values) - end - end - values - end - - def self.sorting(params: nil,table: nil,field: nil,direction: nil,page_num: nil,per: nil,column_entries: nil,paginated: true) - page_num = 1 if page_num.blank? - page_num = page_num.to_i - if field.nil? || direction.nil? - table, field, direction = self.get_sort_field(params: params, table: table) - end - - if (field=='created_at' || field == 'sort_number') - if column_entries.nil? - values = self.order_by({field => direction}) - else - values = column_entries.map{|v| v.table_entry}.compact - values = values.sort_by{|v| v.send(field)} - if direction == 'desc' - values = values.reverse - end - if paginated || !per.nil? - values_count = values.count - values_count = 1 if values_count==0 - values = Kaminari.paginate_array(values,limit: values_count) - end - end - if !per.nil? - values = values.page(page_num).per(per) - end - else - column_to_sort = field - if column_entries.nil? - if criteria.selector.keys != ['u_table_id'] - column_entries = ColumnEntry.where(:table_column_id=>column_to_sort.id,:table_entry_id.in => criteria.pluck(:id)).order_by(column_to_sort.sort_hash(direction)) - else - column_entries = ColumnEntry.where(:table_column_id=>column_to_sort.id).order_by(column_to_sort.sort_hash(direction)) - end - else - column_entries = ColumnEntry.where(:table_column_id=>column_to_sort.id,:table_entry_id.in => (column_entries.class==Kaminari::PaginatableArray ? column_entries.map(&:table_entry_id) : column_entries.pluck(:table_entry_id))).order_by(column_to_sort.sort_hash(direction)) - end - if !per.nil? - total_count = column_entries.count - column_entries = column_entries.page(page_num).per(per) - offset = page_num==0 ? 0 : (page_num-1)*per - end_offset = (total_count-offset-per) - end_offset = 0 if end_offset<0 - values = Kaminari.paginate_array([nil]*offset+column_entries.map{|v| v.table_entry}+[nil]*end_offset).page(page_num).per(per) - else - values = column_entries.map{|v| v.table_entry} - if paginated - values = Kaminari.paginate_array(values) - end - end - end - values - end -end +class TableEntry + include Mongoid::Document + include Mongoid::Timestamps + include OrbitModel::Status + include Slug + + attr_accessor :sort_value + field :have_data, type: Boolean, localize: true + field :sort_number, type: Integer + field :view_count, type: Integer, default: 0 + field :related_entries, type: String, default: "" + + has_many :column_entries, :dependent => :destroy + belongs_to :u_table, index: true + has_and_belongs_to_many :table_tags, inverse_of: :table_entries + + accepts_nested_attributes_for :column_entries, :allow_destroy => true + scope :can_display, ->{where(:is_hidden.ne=>true)} + + I18n.available_locales.each do |locale| + index({"have_data.#{locale}" => 1}, { unique: false, background: true }) + end + + before_save do + if self[:sort_number].nil? + other_record = self.class.where(:u_table_id=> self.u_table_id, :id.ne=> self.id).order_by(sort_number: :desc).first + sort_number_to_set = other_record ? other_record.sort_number : 0 + self.sort_number = sort_number_to_set.to_i + 1 + end + self.get_have_data + end + + def fix_have_data + have_data_translations = self.get_have_data + self.class.where(:id=> self.id).update_all(have_data_translations.map{|l, v| ["have_data.#{l}", v]}.to_h) + end + + def get_related_entries + tids = self.related_entries.split(',') + TableEntry.find(tids) + end + + def get_related_entries_uid + tids = self.related_entries.split(',') + TableEntry.where(:id.in => tids).pluck(:uid).join(", ") + end + + def get_have_data + searchable_field_ids = TableColumn.filter_searchable.where(u_table_id: self.u_table_id).pluck(:id) + searchable_column_entries = self.column_entries.where(:table_column_id.in=> searchable_field_ids).to_a + self.have_data_translations = I18n.available_locales.map do |locale| + flag = searchable_column_entries.detect{|ce| ce.have_data(locale)}.present? + [locale.to_s, flag] + end.to_h + end + + def tags_for_frontend + params = OrbitHelper.params + self.table_tags.map{|tt| + "#" + tt.title + "" + }.join(" ") + end + + def self.u_table + UTable.find(criteria.selector['u_table_id']) + end + + def self.get_sort_field(params: nil, table: nil) + field = nil + direction = nil + if table.nil? + table = self.u_table + end + if params + if !params[:sortcolumn].blank? + field = params[:sortcolumn] + direction = params[:sort] + else + field = params[:sort].blank? ? nil : params[:sort] + direction = params[:order].blank? ? 'desc' : params[:order] + end + if field.nil? + field, direction = table.default_ordered + else + field = field.to_s + if !(field=='created_at' || field == 'sort_number') + field = table.table_columns.where(key: field).first || table.table_columns.where(title: field).first + end + end + end + [table, field, direction] + end + + def self.sorted(entries: nil, params: nil, table: nil, field: nil, direction: nil, paginated: true) + if field.nil? || direction.nil? + table, field, direction = self.get_sort_field(params: params, table: table) + end + if entries.nil? + entries = table.table_entries + end + if (field=='created_at' || field == 'sort_number') + values = entries.order_by({field => direction}) + else + column_to_sort = field + if entries.selector.present? + column_entries = ColumnEntry.where(:table_column_id=>column_to_sort.id,:table_entry_id.in => entries.pluck(:id)).order_by(column_to_sort.sort_hash(direction)) + else + column_entries = ColumnEntry.where(:table_column_id=>column_to_sort.id).order_by(column_to_sort.sort_hash(direction)) + end + values = column_entries.map{|v| v.table_entry} + if paginated + values = Kaminari.paginate_array(values) + end + end + values + end + + def self.sorting(params: nil,table: nil,field: nil,direction: nil,page_num: nil,per: nil,column_entries: nil,paginated: true) + page_num = 1 if page_num.blank? + page_num = page_num.to_i + if field.nil? || direction.nil? + table, field, direction = self.get_sort_field(params: params, table: table) + end + + if (field=='created_at' || field == 'sort_number') + if column_entries.nil? + values = self.order_by({field => direction}) + else + values = column_entries.map{|v| v.table_entry}.compact + values = values.sort_by{|v| v.send(field)} + if direction == 'desc' + values = values.reverse + end + if paginated || !per.nil? + values_count = values.count + values_count = 1 if values_count==0 + values = Kaminari.paginate_array(values,limit: values_count) + end + end + if !per.nil? + values = values.page(page_num).per(per) + end + else + column_to_sort = field + if column_entries.nil? + if criteria.selector.keys != ['u_table_id'] + column_entries = ColumnEntry.where(:table_column_id=>column_to_sort.id,:table_entry_id.in => criteria.pluck(:id)).order_by(column_to_sort.sort_hash(direction)) + else + column_entries = ColumnEntry.where(:table_column_id=>column_to_sort.id).order_by(column_to_sort.sort_hash(direction)) + end + else + column_entries = ColumnEntry.where(:table_column_id=>column_to_sort.id,:table_entry_id.in => (column_entries.class==Kaminari::PaginatableArray ? column_entries.map(&:table_entry_id) : column_entries.pluck(:table_entry_id))).order_by(column_to_sort.sort_hash(direction)) + end + if !per.nil? + total_count = column_entries.count + column_entries = column_entries.page(page_num).per(per) + offset = page_num==0 ? 0 : (page_num-1)*per + end_offset = (total_count-offset-per) + end_offset = 0 if end_offset<0 + values = Kaminari.paginate_array([nil]*offset+column_entries.map{|v| v.table_entry}+[nil]*end_offset).page(page_num).per(per) + else + values = column_entries.map{|v| v.table_entry} + if paginated + values = Kaminari.paginate_array(values) + end + end + end + values + end +end diff --git a/app/models/table_tag.rb b/app/models/table_tag.rb old mode 100644 new mode 100755 index 7e6f187..33becd8 --- a/app/models/table_tag.rb +++ b/app/models/table_tag.rb @@ -1,8 +1,8 @@ -class TableTag - include Mongoid::Document - include Mongoid::Timestamps - - field :title, type: String - field :u_table_id - has_and_belongs_to_many :table_entries, inverse_of: :table_tags -end +class TableTag + include Mongoid::Document + include Mongoid::Timestamps + + field :title, type: String + field :u_table_id + has_and_belongs_to_many :table_entries, inverse_of: :table_tags +end diff --git a/app/models/u_table.rb b/app/models/u_table.rb old mode 100644 new mode 100755 index d170eb0..082263a --- a/app/models/u_table.rb +++ b/app/models/u_table.rb @@ -1,42 +1,42 @@ -class UTable - include Mongoid::Document - include Mongoid::Timestamps - include OrbitCategory::Categorizable - include Slug - - field :title, as: :slug_title, localize: true - - field :ordered_with_sort_number, type: Boolean, default: false - field :sort_number_order_direction, type: String, default: 'desc' - - field :ordered_with_created_at, type: Boolean, default: true - field :created_at_order_direction, type: String, default: 'desc' - - has_many :table_columns, :dependent => :destroy - has_many :table_entries, :dependent => :destroy - has_many :mind_maps, :dependent => :destroy - - accepts_nested_attributes_for :table_columns, :allow_destroy => true - - FIELD_TYPES = ["text", "integer", "editor", "image", "date", "period", "file"] - DATE_FORMATS = ["yyyy/MM/dd hh:mm", "yyyy/MM/dd","yyyy/MM", "yyyy"] - AUDIO_EXTENSIONS = %w[.mp3 .wav .ogg .m4a .aac .flac] - def default_ordered - if self.ordered_with_created_at - sort_column = 'created_at' - direction = self.created_at_order_direction - elsif self.ordered_with_sort_number - sort_column = 'sort_number' - direction = self.sort_number_order_direction - else - sort_column = self.table_columns.where(default_ordered_field: true).first - if sort_column - direction = sort_column.order_direction - else - sort_column = 'created_at' - direction = self.created_at_order_direction - end - end - [sort_column,direction] - end -end +class UTable + include Mongoid::Document + include Mongoid::Timestamps + include OrbitCategory::Categorizable + include Slug + + field :title, as: :slug_title, localize: true + + field :ordered_with_sort_number, type: Boolean, default: false + field :sort_number_order_direction, type: String, default: 'desc' + + field :ordered_with_created_at, type: Boolean, default: true + field :created_at_order_direction, type: String, default: 'desc' + + has_many :table_columns, :dependent => :destroy + has_many :table_entries, :dependent => :destroy + has_many :mind_maps, :dependent => :destroy + + accepts_nested_attributes_for :table_columns, :allow_destroy => true + + FIELD_TYPES = ["text", "integer", "editor", "image", "date", "period", "file"] + DATE_FORMATS = ["yyyy/MM/dd hh:mm", "yyyy/MM/dd","yyyy/MM", "yyyy"] + AUDIO_EXTENSIONS = %w[.mp3 .wav .ogg .m4a .aac .flac] + def default_ordered + if self.ordered_with_created_at + sort_column = 'created_at' + direction = self.created_at_order_direction + elsif self.ordered_with_sort_number + sort_column = 'sort_number' + direction = self.sort_number_order_direction + else + sort_column = self.table_columns.where(default_ordered_field: true).first + if sort_column + direction = sort_column.order_direction + else + sort_column = 'created_at' + direction = self.created_at_order_direction + end + end + [sort_column,direction] + end +end diff --git a/app/views/admin/mind_maps/_form.html.erb b/app/views/admin/mind_maps/_form.html.erb old mode 100644 new mode 100755 index c75195f..591bbc7 --- a/app/views/admin/mind_maps/_form.html.erb +++ b/app/views/admin/mind_maps/_form.html.erb @@ -1,45 +1,45 @@ -
-
-

<%= t("universal_table.table_name") %> - <%= @table.title %>

-
-
-
-
-
- <% @site_in_use_locales.each do |locale| %> - <% active = (locale == @site_in_use_locales.first ? "active in" : "") %> -
- <%= f.fields_for :title_translations do |f| %> - <%= f.text_field locale, :placeholder => "Title", :value => @mind_map.title_translations[locale] %> - <% end %> -
- <% end %> -
-
- <% @site_in_use_locales.each do |locale| %> - <% active = (locale == @site_in_use_locales.first ? "active" : "") %> - <%= link_to t(locale).to_s,"#mind_map_#{locale.to_s}",:class=>"btn #{active}",:data=>{:toggle=>"tab"}%> - <% end %> -
-
-
-
-
-
-
-

<%= t("universal_table.mind_map") %>

-
-
-
- -
-
-
-
-
-
- <%= f.hidden_field :mind_map_data, id: "mind_map_data_field", value: "[]" %> - <%= f.hidden_field :u_table_id, value: @table.id %> - "> -
+
+
+

<%= t("universal_table.table_name") %> - <%= @table.title %>

+
+
+
+
+
+ <% @site_in_use_locales.each do |locale| %> + <% active = (locale == @site_in_use_locales.first ? "active in" : "") %> +
+ <%= f.fields_for :title_translations do |f| %> + <%= f.text_field locale, :placeholder => "Title", :value => @mind_map.title_translations[locale] %> + <% end %> +
+ <% end %> +
+
+ <% @site_in_use_locales.each do |locale| %> + <% active = (locale == @site_in_use_locales.first ? "active" : "") %> + <%= link_to t(locale).to_s,"#mind_map_#{locale.to_s}",:class=>"btn #{active}",:data=>{:toggle=>"tab"}%> + <% end %> +
+
+
+
+
+
+
+

<%= t("universal_table.mind_map") %>

+
+
+
+ +
+
+
+
+
+
+ <%= f.hidden_field :mind_map_data, id: "mind_map_data_field", value: "[]" %> + <%= f.hidden_field :u_table_id, value: @table.id %> + "> +
\ No newline at end of file diff --git a/app/views/admin/mind_maps/_index.html.erb b/app/views/admin/mind_maps/_index.html.erb old mode 100644 new mode 100755 index a75afcf..ebdc132 --- a/app/views/admin/mind_maps/_index.html.erb +++ b/app/views/admin/mind_maps/_index.html.erb @@ -1,33 +1,33 @@ - - - - <% @table_fields.each do |f| %> - <%= thead(f) %> - <% end %> - - - - <% @mind_maps.each do |mindmap| %> - - - - - <% end %> - -
- <%= mindmap.title %> - - - <%= mindmap.created_at.strftime("%Y-%m-%d") %> -
-<%= - content_tag :div, class: "bottomnav clearfix" do - content_tag(:div, paginate(@mind_maps), class: "pagination pagination-centered") + - content_tag(:div, link_to(t(:new_),new_admin_mind_map_path(:table => @table.id.to_s), :class=>"btn btn-primary"), class: "pull-right") - end + + + + <% @table_fields.each do |f| %> + <%= thead(f) %> + <% end %> + + + + <% @mind_maps.each do |mindmap| %> + + + + + <% end %> + +
+ <%= mindmap.title %> + + + <%= mindmap.created_at.strftime("%Y-%m-%d") %> +
+<%= + content_tag :div, class: "bottomnav clearfix" do + content_tag(:div, paginate(@mind_maps), class: "pagination pagination-centered") + + content_tag(:div, link_to(t(:new_),new_admin_mind_map_path(:table => @table.id.to_s), :class=>"btn btn-primary"), class: "pull-right") + end %> \ No newline at end of file diff --git a/app/views/admin/mind_maps/edit.html.erb b/app/views/admin/mind_maps/edit.html.erb old mode 100644 new mode 100755 index c7b6826..4d6136a --- a/app/views/admin/mind_maps/edit.html.erb +++ b/app/views/admin/mind_maps/edit.html.erb @@ -1,93 +1,93 @@ -<% content_for :page_specific_css do %> - <%= stylesheet_link_tag "universal_table/universal-table" %> - <%= stylesheet_link_tag "mind_map/mindmap" %> -<% end %> -<%= form_for @mind_map, url: admin_mind_map_path(@mind_map.id), html: {class: "form-horizontal main-forms", id: "mind_map_form"} do |f| %> - <%= render :partial => "form", locals: {f: f} %> -<% end %> - +<% content_for :page_specific_css do %> + <%= stylesheet_link_tag "universal_table/universal-table" %> + <%= stylesheet_link_tag "mind_map/mindmap" %> +<% end %> +<%= form_for @mind_map, url: admin_mind_map_path(@mind_map.id), html: {class: "form-horizontal main-forms", id: "mind_map_form"} do |f| %> + <%= render :partial => "form", locals: {f: f} %> +<% end %> + diff --git a/app/views/admin/mind_maps/index.html.erb b/app/views/admin/mind_maps/index.html.erb old mode 100644 new mode 100755 index b2787eb..7e5ec78 --- a/app/views/admin/mind_maps/index.html.erb +++ b/app/views/admin/mind_maps/index.html.erb @@ -1,6 +1,6 @@ -<% content_for :page_specific_javascript do %> - <%= javascript_include_tag "lib/jquery.form" %> -<% end %> -
- <%= render 'index'%> +<% content_for :page_specific_javascript do %> + <%= javascript_include_tag "lib/jquery.form" %> +<% end %> +
+ <%= render 'index'%>
\ No newline at end of file diff --git a/app/views/admin/mind_maps/new.html.erb b/app/views/admin/mind_maps/new.html.erb old mode 100644 new mode 100755 index 8f6f0f7..d36c76e --- a/app/views/admin/mind_maps/new.html.erb +++ b/app/views/admin/mind_maps/new.html.erb @@ -1,89 +1,89 @@ -<% content_for :page_specific_css do %> - <%= stylesheet_link_tag "universal_table/universal-table" %> - <%= stylesheet_link_tag "mind_map/mindmap" %> -<% end %> -<%= form_for @mind_map, url: admin_mind_maps_path, html: {class: "form-horizontal main-forms", id: "mind_map_form"} do |f| %> - <%= render :partial => "form", locals: {f: f} %> -<% end %> - +<% content_for :page_specific_css do %> + <%= stylesheet_link_tag "universal_table/universal-table" %> + <%= stylesheet_link_tag "mind_map/mindmap" %> +<% end %> +<%= form_for @mind_map, url: admin_mind_maps_path, html: {class: "form-horizontal main-forms", id: "mind_map_form"} do |f| %> + <%= render :partial => "form", locals: {f: f} %> +<% end %> + diff --git a/app/views/admin/universal_tables/_column.html.erb b/app/views/admin/universal_tables/_column.html.erb old mode 100644 new mode 100755 index 9befaa4..c39454b --- a/app/views/admin/universal_tables/_column.html.erb +++ b/app/views/admin/universal_tables/_column.html.erb @@ -1,102 +1,102 @@ -<% if !defined?(i) %> -
-<% end %> -
- Delete - <% if defined?(i) %> - <%= f.hidden_field :_destroy, :value => "false", :class => "attribute_field_to_delete" %> -

<%= column.title %>

- <% else %> -

ColumnXX

- <% end %> -
-
-
- -
- <%= f.text_field :key, :autocomplete => "off", :'data-type' => 'key' %> -
-
-
- -
-
-
- <% @site_in_use_locales.each do |locale| %> - <% active = (locale == @site_in_use_locales.first ? "active in" : "") %> - <% id = (defined?(i) ? "table_column_#{i}_title_translations_#{locale.to_s}" : "table_column_XXX_title_translations_#{locale.to_s}") %> -
- <%= f.fields_for :title_translations do |f| %> - <%= f.text_field locale, :value => column.title_translations[locale] %> - <% end %> -
- <% end %> -
-
- <% @site_in_use_locales.each do |locale| %> - <% active = (locale == @site_in_use_locales.first ? "active" : "") %> - <% id = (defined?(i) ? "table_column_#{i}_title_translations_#{locale.to_s}" : "table_column_XXX_title_translations_#{locale.to_s}") %> - <%= link_to t(locale).to_s,"##{id}",:class=>"btn #{active}",:data=>{:toggle=>"tab"}%> - <% end %> -
-
-
-
- -
- -
- - -
-
-
- -
-
- <%= f.check_box :default_ordered_field, class: 'default_ordered_field' %> -
-
- <%= f.select :order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %> -
-
-
-
- -
- <% select_values = UTable::FIELD_TYPES.collect{|ft| [ft.capitalize,ft]} %> - <%= f.select :type, select_values, {}, {class: "type-selector"} %> - "> - - - - - <% select_values = UTable::DATE_FORMATS.collect{|ft| [ft.upcase,ft]} %> - -
-
- <% if defined?(i) %> - <% if column.order.nil? %> - <%= f.hidden_field :order, :value => i, :class => "order-hidden-field" %> - <% else %> - <%= f.hidden_field :order, :class => "order-hidden-field" %> - <% end %> - <% else %> - <%= f.hidden_field :order, :value=> "XXX", :class => "order-hidden-field" %> - <% end %> -
-<% if !defined?(i) %> -
+<% if !defined?(i) %> +
+<% end %> +
+ Delete + <% if defined?(i) %> + <%= f.hidden_field :_destroy, :value => "false", :class => "attribute_field_to_delete" %> +

<%= column.title %>

+ <% else %> +

ColumnXX

+ <% end %> +
+
+
+ +
+ <%= f.text_field :key, :autocomplete => "off", :'data-type' => 'key' %> +
+
+
+ +
+
+
+ <% @site_in_use_locales.each do |locale| %> + <% active = (locale == @site_in_use_locales.first ? "active in" : "") %> + <% id = (defined?(i) ? "table_column_#{i}_title_translations_#{locale.to_s}" : "table_column_XXX_title_translations_#{locale.to_s}") %> +
+ <%= f.fields_for :title_translations do |f| %> + <%= f.text_field locale, :value => column.title_translations[locale] %> + <% end %> +
+ <% end %> +
+
+ <% @site_in_use_locales.each do |locale| %> + <% active = (locale == @site_in_use_locales.first ? "active" : "") %> + <% id = (defined?(i) ? "table_column_#{i}_title_translations_#{locale.to_s}" : "table_column_XXX_title_translations_#{locale.to_s}") %> + <%= link_to t(locale).to_s,"##{id}",:class=>"btn #{active}",:data=>{:toggle=>"tab"}%> + <% end %> +
+
+
+
+ +
+ +
+ + +
+
+
+ +
+
+ <%= f.check_box :default_ordered_field, class: 'default_ordered_field' %> +
+
+ <%= f.select :order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %> +
+
+
+
+ +
+ <% select_values = UTable::FIELD_TYPES.collect{|ft| [ft.capitalize,ft]} %> + <%= f.select :type, select_values, {}, {class: "type-selector"} %> + "> + + + + + <% select_values = UTable::DATE_FORMATS.collect{|ft| [ft.upcase,ft]} %> + +
+
+ <% if defined?(i) %> + <% if column.order.nil? %> + <%= f.hidden_field :order, :value => i, :class => "order-hidden-field" %> + <% else %> + <%= f.hidden_field :order, :class => "order-hidden-field" %> + <% end %> + <% else %> + <%= f.hidden_field :order, :value=> "XXX", :class => "order-hidden-field" %> + <% end %> +
+<% if !defined?(i) %> +
<% end %> \ No newline at end of file diff --git a/app/views/admin/universal_tables/_date_field.html.erb b/app/views/admin/universal_tables/_date_field.html.erb old mode 100644 new mode 100755 index 28cd4ec..5713e64 --- a/app/views/admin/universal_tables/_date_field.html.erb +++ b/app/views/admin/universal_tables/_date_field.html.erb @@ -1,18 +1,18 @@ -
- <%= f.label :date, column.title, :class => "control-label" %> -
-
-
- <% v = !date_field.new_record? ? format_date(date_field.date, column.date_format, true) : "" %> - <%= f.text_field :date, :value => v, :placeholder => column.date_format.upcase, :data => {:format => (column.date_format == "yyyy" ? "yyyy/MM" : column.date_format)} %> - - -
-
-
- <% if !date_field.new_record? %> - <%= f.hidden_field :id %> - <% else %> - <%= f.hidden_field :table_column_id, :value => column.id %> - <% end %> -
+
+ <%= f.label :date, column.title, :class => "control-label" %> +
+
+
+ <% v = !date_field.new_record? ? format_date(date_field.date, column.date_format, true) : "" %> + <%= f.text_field :date, :value => v, :placeholder => column.date_format.upcase, :data => {:format => (column.date_format == "yyyy" ? "yyyy/MM" : column.date_format)} %> + + +
+
+
+ <% if !date_field.new_record? %> + <%= f.hidden_field :id %> + <% else %> + <%= f.hidden_field :table_column_id, :value => column.id %> + <% end %> +
diff --git a/app/views/admin/universal_tables/_edit_sort.html.erb b/app/views/admin/universal_tables/_edit_sort.html.erb old mode 100644 new mode 100755 index 1e0e7f6..81be618 --- a/app/views/admin/universal_tables/_edit_sort.html.erb +++ b/app/views/admin/universal_tables/_edit_sort.html.erb @@ -1,79 +1,79 @@ -
- - - - <% @table_fields.each do |field| %> - <% - field_text = field.to_s.include?('.') ? t(field.to_s) : field.to_s - sort = field.to_s.split('.')[-1] - active = params[:sort].eql? sort - order = active ? (["asc", "desc"]-[params[:order]]).first : "asc" - arrow = (order.eql? "desc") ? "" : "" - klass = field.eql?(:title) ? "span5" : "span2" - th_data = "#{field_text} #{active ? arrow : ""}" - %> - - <% end %> - - - - <% can_edit = can_edit_or_delete?(@entries.first.u_table) if !(@entries.first.nil?) %> - <% @entries.each do |entry| %> - - - <% @columns.each_with_index do |column, index| %> - <% ce = entry.column_entries.where(:table_column_id => column.id).first rescue nil %> - <% if !ce.nil? %> - - <% else %> - - <% end %> - <% end %> - - - <% end %> - -
<%= th_data.html_safe %>
- <%= number_field_tag nil,entry.sort_number,class: 'sort_number',step: 1 %> - - <% case ce.type %> - <% when "text" %> - <%= ce.text %> - <% when "integer" %> - <%= ce.number %> - <% when "editor" %> - <%= ce.content.html_safe rescue "" %> - <% when "image" %> -
- <% if !ce.image.nil? %> - - <% end %> -
- <% when "date" %> - <%= format_date(ce.date, column.date_format) %> - <% when "period" %> - <% if !ce.period_from.nil? %> - <%= format_date(ce.period_from, column.date_format) %> ~ <%= format_date(ce.period_to, column.date_format) %> - <% end %> - <% end %> - <% if index == 0 && can_edit %> - - <% end %> -
-   - - <%= entry.created_at %> -
-
- - - - - - - +
+ + + + <% @table_fields.each do |field| %> + <% + field_text = field.to_s.include?('.') ? t(field.to_s) : field.to_s + sort = field.to_s.split('.')[-1] + active = params[:sort].eql? sort + order = active ? (["asc", "desc"]-[params[:order]]).first : "asc" + arrow = (order.eql? "desc") ? "" : "" + klass = field.eql?(:title) ? "span5" : "span2" + th_data = "#{field_text} #{active ? arrow : ""}" + %> + + <% end %> + + + + <% can_edit = can_edit_or_delete?(@entries.first.u_table) if !(@entries.first.nil?) %> + <% @entries.each do |entry| %> + + + <% @columns.each_with_index do |column, index| %> + <% ce = entry.column_entries.where(:table_column_id => column.id).first rescue nil %> + <% if !ce.nil? %> + + <% else %> + + <% end %> + <% end %> + + + <% end %> + +
<%= th_data.html_safe %>
+ <%= number_field_tag nil,entry.sort_number,class: 'sort_number',step: 1 %> + + <% case ce.type %> + <% when "text" %> + <%= ce.text %> + <% when "integer" %> + <%= ce.number %> + <% when "editor" %> + <%= ce.content.html_safe rescue "" %> + <% when "image" %> +
+ <% if !ce.image.nil? %> + + <% end %> +
+ <% when "date" %> + <%= format_date(ce.date, column.date_format) %> + <% when "period" %> + <% if !ce.period_from.nil? %> + <%= format_date(ce.period_from, column.date_format) %> ~ <%= format_date(ce.period_to, column.date_format) %> + <% end %> + <% end %> + <% if index == 0 && can_edit %> + + <% end %> +
+   + + <%= entry.created_at %> +
+
+ + + + + + + diff --git a/app/views/admin/universal_tables/_editor_field.html.erb b/app/views/admin/universal_tables/_editor_field.html.erb old mode 100644 new mode 100755 index 86605a9..4ae13d7 --- a/app/views/admin/universal_tables/_editor_field.html.erb +++ b/app/views/admin/universal_tables/_editor_field.html.erb @@ -1,31 +1,31 @@ - - -
-<% @site_in_use_locales.each_with_index do |locale| %> - <% id = "table_entry_column_entries_#{i}_content_translations_#{locale.to_s}" %> - <% active = (locale == @site_in_use_locales.first ? "active in" : "") %> -
-
- <%= f.label :content, column.title, :class => "control-label" %> -
- <%= f.fields_for :content_translations do |f| %> - <%= f.text_area locale, :value => editor_field.content_translations[locale.to_s], :class => "ckeditor" %> - <% end %> -
-
-
-<% end %> -<% if !editor_field.new_record? %> - <%= f.hidden_field :id %> -<% else %> - <%= f.hidden_field :table_column_id, :value => column.id %> - <% end %> -
+ + +
+<% @site_in_use_locales.each_with_index do |locale| %> + <% id = "table_entry_column_entries_#{i}_content_translations_#{locale.to_s}" %> + <% active = (locale == @site_in_use_locales.first ? "active in" : "") %> +
+
+ <%= f.label :content, column.title, :class => "control-label" %> +
+ <%= f.fields_for :content_translations do |f| %> + <%= f.text_area locale, :value => editor_field.content_translations[locale.to_s], :class => "ckeditor" %> + <% end %> +
+
+
+<% end %> +<% if !editor_field.new_record? %> + <%= f.hidden_field :id %> +<% else %> + <%= f.hidden_field :table_column_id, :value => column.id %> + <% end %> +
diff --git a/app/views/admin/universal_tables/_entry_form.html.erb b/app/views/admin/universal_tables/_entry_form.html.erb old mode 100644 new mode 100755 index 7e06baa..59375b4 --- a/app/views/admin/universal_tables/_entry_form.html.erb +++ b/app/views/admin/universal_tables/_entry_form.html.erb @@ -1,111 +1,111 @@ - <% content_for :page_specific_css do %> - <%= stylesheet_link_tag "universal_table/universal-table" %> - <%= stylesheet_link_tag "lib/main-forms" %> - <%= stylesheet_link_tag "lib/fileupload" %> - <%= stylesheet_link_tag "lib/main-list" %> - <%= stylesheet_link_tag "select2/select2" %> - <%= javascript_include_tag "select2/select2.min" %> -<% end %> - <% content_for :page_specific_javascript do %> - <%= javascript_include_tag "lib/bootstrap-fileupload" %> - <%= javascript_include_tag "lib/bootstrap-datetimepicker" %> - <%= javascript_include_tag "lib/datetimepicker/datetimepicker.js" %> -<% end %> - - -
-
- -
- -
-
- <% - tbData = @entry.get_related_entries.map{|tb| {id: tb.id.to_s, text: tb.column_entries.first.text}} - %> -
- -
- <%= f.text_field :related_entries, :class => "select2", data: { value: tbData } %> -
-
- <% @columns.each_with_index do |column, index| %> - <% if @entry.new_record? %> - <% object = f.object.send(:column_entries).build rescue nil %> - <% else %> - <% - ce = @entry.column_entries.where(:table_column_id => column.id).first rescue nil - if ce.nil? - object = f.object.send(:column_entries).build rescue nil - else - object = ce - end - %> - <% end %> - <%= f.fields_for :column_entries, object, :child_index => index do |f| %> - <%= render :partial => "#{column.type}_field", :object => object, :locals => {:f => f, :column => column, :i => index} %> - <% end %> - <% end %> -
- - - \ No newline at end of file diff --git a/app/views/admin/universal_tables/_entry_summary.html.erb b/app/views/admin/universal_tables/_entry_summary.html.erb index bd062d5..62bf4b8 100755 --- a/app/views/admin/universal_tables/_entry_summary.html.erb +++ b/app/views/admin/universal_tables/_entry_summary.html.erb @@ -1,35 +1,35 @@ -
- <%= entry.column_entries.first.try(:text).to_s %> - - <% entry.column_entries.each do |ce| %> - <% if ce.type == "file" %> -
    - <% ce.column_entry_files.desc(:sort_number).each do |file| %> - <% next unless file.choose_lang_display(I18n.locale.to_s) %> - <% file_title = file.get_file_title %> - <% size = number_to_human_size(file.file.size) %> - <% link = file.get_link %> - <% if file.file.content_type.start_with?('audio/') %> -
    - <%= file_title %> - - - -
    - <% else %> -
  • - - <%= file_title %> - - (<%= size %>) - - "> - <%= file.download_count %> - -
  • - <% end %> - <% end %> -
- <% end %> - <% end %> -
+
+ <%= entry.column_entries.first.try(:text).to_s %> + + <% entry.column_entries.each do |ce| %> + <% if ce.type == "file" %> +
    + <% ce.column_entry_files.desc(:sort_number).each do |file| %> + <% next unless file.choose_lang_display(I18n.locale.to_s) %> + <% file_title = file.get_file_title %> + <% size = number_to_human_size(file.file.size) %> + <% link = file.get_link %> + <% if file.file.content_type.start_with?('audio/') %> +
    + <%= file_title %> + + + +
    + <% else %> +
  • + + <%= file_title %> + + (<%= size %>) + + "> + <%= file.download_count %> + +
  • + <% end %> + <% end %> +
+ <% end %> + <% end %> +
diff --git a/app/views/admin/universal_tables/_file_field.html.erb b/app/views/admin/universal_tables/_file_field.html.erb old mode 100644 new mode 100755 index 77446d7..cd4e0c0 --- a/app/views/admin/universal_tables/_file_field.html.erb +++ b/app/views/admin/universal_tables/_file_field.html.erb @@ -1,289 +1,289 @@ -<% # encoding: utf-8 %> -<% content_for :page_specific_css do %> - -<% end %> -<% content_for :page_specific_javascript do %> - <%= javascript_include_tag "lib/file-type" %> - <%= javascript_include_tag "lib/module-area" %> - <%= javascript_include_tag "lib/jquery-ui-sortable.min" %> -<% end %> - -
- <%= f.label :text, column.title, :class => "control-label" %> -
-

- <%= hidden_field_tag 'column_entry_file_field_count', file_field.column_entry_files.count %> - <%= t(:add) %> -

-
- -
- - <% if file_field && !file_field.column_entry_files.blank? %> -
- <% file_field.column_entry_files.desc(:sort_number).each_with_index do |column_entry_file, i| %> - <%= f.fields_for :column_entry_files, column_entry_file do |f| %> - <%= render :partial => 'form_file', :locals => {:f => f, :i => i,:form_file => column_entry_file} %> - <% end %> - <% end %> -
-
- <% end %> -
-
-
- <%=t("universal_table.drag_file_to_here")%> -
-
-
- -
- <% if !file_field.new_record? %> - <%= f.hidden_field :id %> - <% else %> - <%= f.hidden_field :table_column_id, :value => column.id %> - <% end %> -
- -<% content_for :page_specific_javascript do %> - +<% # encoding: utf-8 %> +<% content_for :page_specific_css do %> + +<% end %> +<% content_for :page_specific_javascript do %> + <%= javascript_include_tag "lib/file-type" %> + <%= javascript_include_tag "lib/module-area" %> + <%= javascript_include_tag "lib/jquery-ui-sortable.min" %> +<% end %> + +
+ <%= f.label :text, column.title, :class => "control-label" %> +
+

+ <%= hidden_field_tag 'column_entry_file_field_count', file_field.column_entry_files.count %> + <%= t(:add) %> +

+
+ +
+ + <% if file_field && !file_field.column_entry_files.blank? %> +
+ <% file_field.column_entry_files.desc(:sort_number).each_with_index do |column_entry_file, i| %> + <%= f.fields_for :column_entry_files, column_entry_file do |f| %> + <%= render :partial => 'form_file', :locals => {:f => f, :i => i,:form_file => column_entry_file} %> + <% end %> + <% end %> +
+
+ <% end %> +
+
+
+ <%=t("universal_table.drag_file_to_here")%> +
+
+
+ +
+ <% if !file_field.new_record? %> + <%= f.hidden_field :id %> + <% else %> + <%= f.hidden_field :table_column_id, :value => column.id %> + <% end %> +
+ +<% content_for :page_specific_javascript do %> + <% end %> \ No newline at end of file diff --git a/app/views/admin/universal_tables/_form_file.html.erb b/app/views/admin/universal_tables/_form_file.html.erb old mode 100644 new mode 100755 index 3ef82de..3d90722 --- a/app/views/admin/universal_tables/_form_file.html.erb +++ b/app/views/admin/universal_tables/_form_file.html.erb @@ -1,65 +1,65 @@ -<% if form_file.new_record? %> -
-<% else %> -
- - <% if form_file.file.blank? %> - <%= t(:no_file) %> - <% else %> - <%= link_to content_tag(:i) + form_file.file_identifier, form_file.file.url, {:class => 'file-link file-type', :target => '_blank', :title => form_file.file_identifier} %> - <% end %> -<% end %> -
- - - - <% @site_in_use_locales.each_with_index do |locale, i| %> - <%= locale %>"> - <%= f.fields_for :file_title_translations do |f| %> - <%= f.text_field locale, :class => "input-medium", placeholder: t('file.name'), :value => (form_file.file_title_translations[locale] rescue nil) %> - <% end %> - - <% end %> - - - - - - <%= hidden_field_tag "#{f.object_name}[choose_lang][]", '' %> - - - <% if form_file.new_record? %> - - - <%= f.hidden_field :sort_number, :value => "new_column_entry_file_sort_order_XXX", :class => "input-mini" %> - - <% else %> - - <%= f.hidden_field :id %> - <%= f.hidden_field :sort_number , :class => "file-sort-number-field" %> - - <%= f.hidden_field :_destroy, :value => nil, :class => 'should_destroy' %> - - Downloaded <%= form_file.download_count %> time<%= form_file.download_count > 1 ? "s" : "" %>. - <% end %> -
-
- +<% if form_file.new_record? %> +
+<% else %> +
+ + <% if form_file.file.blank? %> + <%= t(:no_file) %> + <% else %> + <%= link_to content_tag(:i) + form_file.file_identifier, form_file.file.url, {:class => 'file-link file-type', :target => '_blank', :title => form_file.file_identifier} %> + <% end %> +<% end %> +
+ + + + <% @site_in_use_locales.each_with_index do |locale, i| %> + <%= locale %>"> + <%= f.fields_for :file_title_translations do |f| %> + <%= f.text_field locale, :class => "input-medium", placeholder: t('file.name'), :value => (form_file.file_title_translations[locale] rescue nil) %> + <% end %> + + <% end %> + + + + + + <%= hidden_field_tag "#{f.object_name}[choose_lang][]", '' %> + + + <% if form_file.new_record? %> + + + <%= f.hidden_field :sort_number, :value => "new_column_entry_file_sort_order_XXX", :class => "input-mini" %> + + <% else %> + + <%= f.hidden_field :id %> + <%= f.hidden_field :sort_number , :class => "file-sort-number-field" %> + + <%= f.hidden_field :_destroy, :value => nil, :class => 'should_destroy' %> + + Downloaded <%= form_file.download_count %> time<%= form_file.download_count > 1 ? "s" : "" %>. + <% end %> +
+
+ diff --git a/app/views/admin/universal_tables/_image_field.html.erb b/app/views/admin/universal_tables/_image_field.html.erb old mode 100644 new mode 100755 index a624466..c2624b2 --- a/app/views/admin/universal_tables/_image_field.html.erb +++ b/app/views/admin/universal_tables/_image_field.html.erb @@ -1,31 +1,31 @@ -
- <%= f.label :image, column.title, :class => "control-label" %> -
-
-
- <% if image_field.image.file %> - <%= image_tag image_field.image %> - <% else %> - - <% end %> -
-
- - <%= t(:select_image) %> - <%= t(:change) %> - <%= f.file_field :image %> - - <%= t(:cancel) %> -
- -
-
-
- <% if !image_field.new_record? %> - <%= f.hidden_field :id %> - <% else %> - <%= f.hidden_field :table_column_id, :value => column.id %> - <% end %> +
+ <%= f.label :image, column.title, :class => "control-label" %> +
+
+
+ <% if image_field.image.file %> + <%= image_tag image_field.image %> + <% else %> + + <% end %> +
+
+ + <%= t(:select_image) %> + <%= t(:change) %> + <%= f.file_field :image %> + + <%= t(:cancel) %> +
+ +
+
+
+ <% if !image_field.new_record? %> + <%= f.hidden_field :id %> + <% else %> + <%= f.hidden_field :table_column_id, :value => column.id %> + <% end %>
\ No newline at end of file diff --git a/app/views/admin/universal_tables/_index.html.erb b/app/views/admin/universal_tables/_index.html.erb old mode 100644 new mode 100755 index 535efe3..dfdf656 --- a/app/views/admin/universal_tables/_index.html.erb +++ b/app/views/admin/universal_tables/_index.html.erb @@ -1,49 +1,49 @@ - - - - <% @table_fields.each do |f| %> - <%= thead(f) %> - <% end %> - - - - <% @tables.each do |table| %> - <% can_edit = can_edit_or_delete?(table) %> - - - - - - - <% end %> - +
- <%= table.title %> -
- -
-
- <%= table.created_at.strftime("%Y-%m-%d") %> - - <%= table.table_entries.count %> - - <% if can_edit %> -
- <%= hidden_field_tag :authenticity_token, form_authenticity_token %> - - - - "><%= t("universal_table.export_structure") %> -
- <% end %> -
+ + + <% @table_fields.each do |f| %> + <%= thead(f) %> + <% end %> + + + + <% @tables.each do |table| %> + <% can_edit = can_edit_or_delete?(table) %> + + + + + + + <% end %> +
+ <%= table.title %> +
+ +
+
+ <%= table.created_at.strftime("%Y-%m-%d") %> + + <%= table.table_entries.count %> + + <% if can_edit %> +
+ <%= hidden_field_tag :authenticity_token, form_authenticity_token %> + + + + "><%= t("universal_table.export_structure") %> +
+ <% end %> +
\ No newline at end of file diff --git a/app/views/admin/universal_tables/_integer_field.html.erb b/app/views/admin/universal_tables/_integer_field.html.erb old mode 100644 new mode 100755 index 843f3a1..a608049 --- a/app/views/admin/universal_tables/_integer_field.html.erb +++ b/app/views/admin/universal_tables/_integer_field.html.erb @@ -1,25 +1,25 @@ -
- <%= f.label :number, column.title, :class => "control-label" %> -
-
- <%= f.number_field :number, :value => integer_field.number %> -
- <% if column.make_categorizable %> - <%= render_unique_number(f,column,i).html_safe %> - - <% end %> - -
- <% if !integer_field.new_record? %> - <%= f.hidden_field :id %> - <% else %> - <%= f.hidden_field :table_column_id, :value => column.id %> - <% end %> +
+ <%= f.label :number, column.title, :class => "control-label" %> +
+
+ <%= f.number_field :number, :value => integer_field.number %> +
+ <% if column.make_categorizable %> + <%= render_unique_number(f,column,i).html_safe %> + + <% end %> + +
+ <% if !integer_field.new_record? %> + <%= f.hidden_field :id %> + <% else %> + <%= f.hidden_field :table_column_id, :value => column.id %> + <% end %>
\ No newline at end of file diff --git a/app/views/admin/universal_tables/_period_field.html.erb b/app/views/admin/universal_tables/_period_field.html.erb old mode 100644 new mode 100755 index 0febe1f..49e549f --- a/app/views/admin/universal_tables/_period_field.html.erb +++ b/app/views/admin/universal_tables/_period_field.html.erb @@ -1,34 +1,34 @@ -
-
- <%= f.label :period_from, column.title, :class => "control-label" %> -
-
-
- <% v = !period_field.new_record? ? format_date(period_field.period_from, column.date_format, true) : "" %> - <%= f.text_field :period_from, :value => v, :placeholder => column.date_format.upcase, :data => {:format => (column.date_format == "yyyy" ? "yyyy/MM" : column.date_format)}, :class => "input-small" %> - - -
-
-
-
-
- <%= f.label :period_to, "~", :class => "control-label" %> -
-
-
- <% v = !period_field.new_record? ? format_date(period_field.period_to, column.date_format, true) : "" %> - <%= f.text_field :period_to,:value => v, :placeholder => column.date_format.upcase, :data => {:format => (column.date_format == "yyyy" ? "yyyy/MM" : column.date_format)}, :class => "input-small" %> - - -
-
-
-
- <% if !period_field.new_record? %> - <%= f.hidden_field :id %> - <% else %> - <%= f.hidden_field :table_column_id, :value => column.id %> - <% end %> - +
+
+ <%= f.label :period_from, column.title, :class => "control-label" %> +
+
+
+ <% v = !period_field.new_record? ? format_date(period_field.period_from, column.date_format, true) : "" %> + <%= f.text_field :period_from, :value => v, :placeholder => column.date_format.upcase, :data => {:format => (column.date_format == "yyyy" ? "yyyy/MM" : column.date_format)}, :class => "input-small" %> + + +
+
+
+
+
+ <%= f.label :period_to, "~", :class => "control-label" %> +
+
+
+ <% v = !period_field.new_record? ? format_date(period_field.period_to, column.date_format, true) : "" %> + <%= f.text_field :period_to,:value => v, :placeholder => column.date_format.upcase, :data => {:format => (column.date_format == "yyyy" ? "yyyy/MM" : column.date_format)}, :class => "input-small" %> + + +
+
+
+
+ <% if !period_field.new_record? %> + <%= f.hidden_field :id %> + <% else %> + <%= f.hidden_field :table_column_id, :value => column.id %> + <% end %> +
\ No newline at end of file diff --git a/app/views/admin/universal_tables/_table_form.html.erb b/app/views/admin/universal_tables/_table_form.html.erb old mode 100644 new mode 100755 index c87da20..8df1c69 --- a/app/views/admin/universal_tables/_table_form.html.erb +++ b/app/views/admin/universal_tables/_table_form.html.erb @@ -1,194 +1,194 @@ -<% content_for :page_specific_css do %> - <%= stylesheet_link_tag "universal_table/universal-table" %> -<% end %> -<% content_for :page_specific_javascript do %> - <%= javascript_include_tag "universal_table/jquery-ui.min" %> -<% end %> - -
-
-

<%= t("universal_table.table_name") %>

-
-
-
-
-
- <% @site_in_use_locales.each do |locale| %> - <% active = (locale == @site_in_use_locales.first ? "active in" : "") %> -
- <%= f.fields_for :title_translations do |f| %> - <%= f.text_field locale, :placeholder => "Title", :value => @table.title_translations[locale] %> - <% end %> -
- <% end %> -
- -
- <% @site_in_use_locales.each do |locale| %> - <% active = (locale == @site_in_use_locales.first ? "active" : "") %> - <%= link_to t(locale).to_s,"#table_name_#{locale.to_s}",:class=>"btn #{active}",:data=>{:toggle=>"tab"}%> - <% end %> -
-
-
-
-
- -
-
-

<%= t("universal_table.default_ordered_field") %>

-
-
- -
-
- <%= f.check_box :ordered_with_created_at, class: 'default_ordered_field ordered_with_created_at' %> -
-
- <%= f.select :created_at_order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %> -
-
-
-
- -
-
- <%= f.check_box :ordered_with_sort_number, class: 'default_ordered_field' %> -
-
- <%= f.select :sort_number_order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %> -
-
-
-
- -
-
- <% @table.table_columns.asc(:order).each_with_index do |table_column, index| %> -
- <%= f.fields_for :table_columns, table_column, :child_index => index.to_s do |f| %> - <%= render :partial => "column", :object => table_column, :locals => {:f => f, :i => index} %> - <%= f.hidden_field :id %> - <% end %> -
- <% end %> -
-
- - "> -
-
- - - - - - - - +<% content_for :page_specific_css do %> + <%= stylesheet_link_tag "universal_table/universal-table" %> +<% end %> +<% content_for :page_specific_javascript do %> + <%= javascript_include_tag "universal_table/jquery-ui.min" %> +<% end %> + +
+
+

<%= t("universal_table.table_name") %>

+
+
+
+
+
+ <% @site_in_use_locales.each do |locale| %> + <% active = (locale == @site_in_use_locales.first ? "active in" : "") %> +
+ <%= f.fields_for :title_translations do |f| %> + <%= f.text_field locale, :placeholder => "Title", :value => @table.title_translations[locale] %> + <% end %> +
+ <% end %> +
+ +
+ <% @site_in_use_locales.each do |locale| %> + <% active = (locale == @site_in_use_locales.first ? "active" : "") %> + <%= link_to t(locale).to_s,"#table_name_#{locale.to_s}",:class=>"btn #{active}",:data=>{:toggle=>"tab"}%> + <% end %> +
+
+
+
+
+ +
+
+

<%= t("universal_table.default_ordered_field") %>

+
+
+ +
+
+ <%= f.check_box :ordered_with_created_at, class: 'default_ordered_field ordered_with_created_at' %> +
+
+ <%= f.select :created_at_order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %> +
+
+
+
+ +
+
+ <%= f.check_box :ordered_with_sort_number, class: 'default_ordered_field' %> +
+
+ <%= f.select :sort_number_order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %> +
+
+
+
+ +
+
+ <% @table.table_columns.asc(:order).each_with_index do |table_column, index| %> +
+ <%= f.fields_for :table_columns, table_column, :child_index => index.to_s do |f| %> + <%= render :partial => "column", :object => table_column, :locals => {:f => f, :i => index} %> + <%= f.hidden_field :id %> + <% end %> +
+ <% end %> +
+
+ + "> +
+
+ + + + + + + + diff --git a/app/views/admin/universal_tables/_text_field.html.erb b/app/views/admin/universal_tables/_text_field.html.erb old mode 100644 new mode 100755 index f2fbdf6..0049987 --- a/app/views/admin/universal_tables/_text_field.html.erb +++ b/app/views/admin/universal_tables/_text_field.html.erb @@ -1,46 +1,46 @@ -
- <%= f.label :text, column.title, :class => "control-label" %> -
-
-
- <% @site_in_use_locales.each do |locale| %> - <% active = (locale == @site_in_use_locales.first ? "active in" : "") %> - <% id = "table_entry_column_entries_#{i}_text_translations_#{locale.to_s}" %> -
- <%= f.fields_for :text_translations do |f| %> - <%= f.text_field locale, :value => text_field.text_translations[locale.to_s], :for => locale.to_s %> - <% end %> -
- <% end %> -
-
- <% @site_in_use_locales.each do |locale| %> - <% active = (locale == @site_in_use_locales.first ? "active" : "") %> - <% id = "table_entry_column_entries_#{i}_text_translations_#{locale.to_s}" %> - <%= link_to t(locale).to_s,"##{id}",:class=>"btn #{active}",:data=>{:toggle=>"tab"}%> - <% end %> -
-
- <% if column.make_categorizable %> - <%= render_unique_texts(f,column,i).html_safe %> - - <% end %> - -
- <% if !text_field.new_record? %> - <%= f.hidden_field :id %> - <% else %> - <%= f.hidden_field :table_column_id, :value => column.id %> - <% end %> +
+ <%= f.label :text, column.title, :class => "control-label" %> +
+
+
+ <% @site_in_use_locales.each do |locale| %> + <% active = (locale == @site_in_use_locales.first ? "active in" : "") %> + <% id = "table_entry_column_entries_#{i}_text_translations_#{locale.to_s}" %> +
+ <%= f.fields_for :text_translations do |f| %> + <%= f.text_field locale, :value => text_field.text_translations[locale.to_s], :for => locale.to_s %> + <% end %> +
+ <% end %> +
+
+ <% @site_in_use_locales.each do |locale| %> + <% active = (locale == @site_in_use_locales.first ? "active" : "") %> + <% id = "table_entry_column_entries_#{i}_text_translations_#{locale.to_s}" %> + <%= link_to t(locale).to_s,"##{id}",:class=>"btn #{active}",:data=>{:toggle=>"tab"}%> + <% end %> +
+
+ <% if column.make_categorizable %> + <%= render_unique_texts(f,column,i).html_safe %> + + <% end %> + +
+ <% if !text_field.new_record? %> + <%= f.hidden_field :id %> + <% else %> + <%= f.hidden_field :table_column_id, :value => column.id %> + <% end %>
\ No newline at end of file diff --git a/app/views/admin/universal_tables/edit.html.erb b/app/views/admin/universal_tables/edit.html.erb old mode 100644 new mode 100755 index 584e23e..de75766 --- a/app/views/admin/universal_tables/edit.html.erb +++ b/app/views/admin/universal_tables/edit.html.erb @@ -1,5 +1,5 @@ -<%= form_for @table, url: admin_universal_table_path(@table), html: {class: "form-horizontal main-forms"} do |f| %> - - <%= render :partial => "table_form", locals: {f: f} %> - +<%= form_for @table, url: admin_universal_table_path(@table), html: {class: "form-horizontal main-forms"} do |f| %> + + <%= render :partial => "table_form", locals: {f: f} %> + <% end %> \ No newline at end of file diff --git a/app/views/admin/universal_tables/edit_entry.html.erb b/app/views/admin/universal_tables/edit_entry.html.erb old mode 100644 new mode 100755 index 76b866f..4c5ddbd --- a/app/views/admin/universal_tables/edit_entry.html.erb +++ b/app/views/admin/universal_tables/edit_entry.html.erb @@ -1,7 +1,7 @@ -<%= form_for @entry, url: "/admin/universal_tables/update_entry", html: {class: "form-horizontal main-forms"} do |f| %> -
- <%= f.hidden_field_tag 'id', f.object.id %> - <%= f.hidden_field_tag 'page', params[:page] %> - <%= render :partial => "entry_form", :locals => {:f => f} %> -
+<%= form_for @entry, url: "/admin/universal_tables/update_entry", html: {class: "form-horizontal main-forms"} do |f| %> +
+ <%= f.hidden_field_tag 'id', f.object.id %> + <%= f.hidden_field_tag 'page', params[:page] %> + <%= render :partial => "entry_form", :locals => {:f => f} %> +
<% end %> \ No newline at end of file diff --git a/app/views/admin/universal_tables/edit_sort.html.erb b/app/views/admin/universal_tables/edit_sort.html.erb old mode 100644 new mode 100755 index b52da64..505133f --- a/app/views/admin/universal_tables/edit_sort.html.erb +++ b/app/views/admin/universal_tables/edit_sort.html.erb @@ -1,109 +1,109 @@ -<% content_for :page_specific_css do %> - <%= stylesheet_link_tag "universal_table/universal-table" %> -<% end %> -
- -
-<%= render partial: 'edit_sort' %> - - - - - - - - +<% content_for :page_specific_css do %> + <%= stylesheet_link_tag "universal_table/universal-table" %> +<% end %> +
+ +
+<%= render partial: 'edit_sort' %> + + + + + + + + diff --git a/app/views/admin/universal_tables/export_structure.xlsx.axlsx b/app/views/admin/universal_tables/export_structure.xlsx.axlsx old mode 100644 new mode 100755 index f8b2de0..4f6c0e4 --- a/app/views/admin/universal_tables/export_structure.xlsx.axlsx +++ b/app/views/admin/universal_tables/export_structure.xlsx.axlsx @@ -3,63 +3,71 @@ wb = xlsx_package.workbook wb.add_worksheet(name: "Structure") do |sheet| - heading = sheet.styles.add_style(:b => true, :locked => true) - type = sheet.styles.add_style(:i => true) + heading = sheet.styles.add_style(b: true, locked: true) + type = sheet.styles.add_style(i: true) - row = [] - row1 = [] - row2 = [] + row = ['UID'] + row1 = ['uid'] + row2 = ['Use to update existing entries. Leave blank to create new.'] - @table.table_columns.asc(:order).each do |column| - case column.type - when "text" - @site_in_use_locales.sort.each do |locale| - row << column.title + " - " + t(locale.to_s) - row1 << column.key - row2 << column.type + "-#{locale}" - end - when "integer" - row << column.title - row1 << column.key - row2 << column.type - when "editor" - @site_in_use_locales.sort.each do |locale| - row << column.title + " - " + t(locale.to_s) - row1 << column.key - row2 << column.type + "-#{locale}" - end - when "image" - row << column.title - row1 << column.key - row2 << "Public URL" - when "date" - row << column.title - row1 << column.key - row2 << column.type + " : " + column.date_format.upcase - when "period" - row << column.title + "-From" - row1 << column.key - row2 << column.type + " : " + column.date_format.upcase + "-period_from" - row << column.title + "-To" - row1 << column.key - row2 << column.type + " : " + column.date_format.upcase + "-period_to" - when "file" - row << column.title - row1 << column.key - row2 << "Separate the files by ;" - end - end + @table.table_columns.asc(:order).each do |column| + case column.type + when "text", "editor" + @site_in_use_locales.sort.each do |locale| + row << "#{column.title} - #{t(locale.to_s)}" + row1 << column.key + row2 << "#{column.type}-#{locale}" + end - row << t("universal_table.hashtags") - row1 << "table_tags" - row2 << "Separate tags by ;" + when "integer" + row << column.title + row1 << column.key + row2 << "integer" - row << t("universal_table.related_entries") - row1 << "related_entries" - row2 << "Separate UIDs with ;" + when "image" + row << column.title + row1 << column.key + row2 << "Public URL" - sheet.add_row row, :style => heading - sheet.add_row row1 - sheet.add_row row2, :style => type + when "date" + row << column.title + row1 << column.key + row2 << "date : #{column.date_format.upcase}" -end \ No newline at end of file + when "period" + row << "#{column.title}-From" + row1 << column.key + row2 << "period : #{column.date_format.upcase}-period_from" + + row << "#{column.title}-To" + row1 << column.key + row2 << "period : #{column.date_format.upcase}-period_to" + + when "file" + # 多語系 file_title 欄位 + @site_in_use_locales.sort.each do |locale| + row << "#{column.title} - #{t(locale.to_s)}" + row1 << column.key + row2 << "Separate the files by" + end + + # URL 欄位 + row << "#{column.title} (註解)" + row1 << column.key + row2 << "file_title - #{locale} ;" + end # <-- 正確結束 case 區塊 + end + + # 加入 hashtags 與 related_entries 欄位 + row << t("universal_table.hashtags") + row1 << "table_tags" + row2 << "Separate tags by ;" + + row << t("universal_table.related_entries") + row1 << "related_entries" + row2 << "Separate UIDs with ;" + + sheet.add_row row, style: heading + sheet.add_row row1 + sheet.add_row row2, style: type +end diff --git a/app/views/admin/universal_tables/index.html.erb b/app/views/admin/universal_tables/index.html.erb old mode 100644 new mode 100755 index 7faaa0e..6a3a52d --- a/app/views/admin/universal_tables/index.html.erb +++ b/app/views/admin/universal_tables/index.html.erb @@ -1,96 +1,96 @@ -<% content_for :page_specific_javascript do %> - <%= javascript_include_tag "lib/jquery.form" %> -<% end %> -
- <%= render 'index'%> -
- - - - + + + \ No newline at end of file diff --git a/app/views/admin/universal_tables/new.html.erb b/app/views/admin/universal_tables/new.html.erb old mode 100644 new mode 100755 index 8d94d3f..b8953b7 --- a/app/views/admin/universal_tables/new.html.erb +++ b/app/views/admin/universal_tables/new.html.erb @@ -1,5 +1,5 @@ -<%= form_for @table, url: admin_universal_tables_path, html: {class: "form-horizontal main-forms"} do |f| %> - - <%= render :partial => "table_form", locals: {f: f} %> - +<%= form_for @table, url: admin_universal_tables_path, html: {class: "form-horizontal main-forms"} do |f| %> + + <%= render :partial => "table_form", locals: {f: f} %> + <% end %> \ No newline at end of file diff --git a/app/views/admin/universal_tables/new_entry.html.erb b/app/views/admin/universal_tables/new_entry.html.erb old mode 100644 new mode 100755 index 02eafa9..289cb39 --- a/app/views/admin/universal_tables/new_entry.html.erb +++ b/app/views/admin/universal_tables/new_entry.html.erb @@ -1,6 +1,6 @@ -<%= form_for @entry, url: "/admin/universal_tables/add_entry", html: {class: "form-horizontal main-forms"} do |f| %> -
- <%= f.hidden_field :u_table_id, :value => @table.id %> - <%= render :partial => "entry_form", :locals => {:f => f} %> -
+<%= form_for @entry, url: "/admin/universal_tables/add_entry", html: {class: "form-horizontal main-forms"} do |f| %> +
+ <%= f.hidden_field :u_table_id, :value => @table.id %> + <%= render :partial => "entry_form", :locals => {:f => f} %> +
<% end %> \ No newline at end of file diff --git a/app/views/admin/universal_tables/show.html.erb b/app/views/admin/universal_tables/show.html.erb old mode 100644 new mode 100755 index 09e05b8..58af71f --- a/app/views/admin/universal_tables/show.html.erb +++ b/app/views/admin/universal_tables/show.html.erb @@ -1,209 +1,209 @@ -<% content_for :page_specific_css do %> - <%= stylesheet_link_tag "universal_table/universal-table" %> -<% end %> - - - -
- - - - - <% @table_fields.each do |field| %> - <% - sort = field.to_s.include?('.') ? field.to_s.split('.')[1] : field.to_s - active = params[:sort].eql? sort - order = active ? (["asc", "desc"]-[params[:order]]).first : "asc" - arrow = (order.eql? "desc") ? "" : "" - klass = field.eql?(:title) ? "span5" : "span2" - th_data = "#{field} #{active ? arrow : ""}" - %> - - <% end %> - - - - <% can_edit = can_edit_or_delete?(@table) %> - <% @entries.each do |entry| %> - - - <% @columns.each_with_index do |column, index| %> - <% ce = entry.column_entries.where(:table_column_id => column.id).first rescue nil %> - - <% end %> - - <% end %> - -
- <%= t(:status) %> -
- <%= t(:hide) %> - <%= t(:show) %> -
-
<%= th_data.html_safe %>
- <% if !ce.nil? %> - <% case ce.type %> - <% when "text" %> - <%= ce.text %> - <% when "integer" %> - <%= ce.number %> - <% when "editor" %> - <%= ce.content.html_safe rescue "" %> - <% when "image" %> -
- <% if !ce.image.nil? %> - - <% end %> -
- <% when "date" %> - <%= format_date(ce.date, column.date_format) %> - <% when "period" %> - <% if !ce.period_from.nil? %> - <%= format_date(ce.period_from, column.date_format) %> ~ <%= format_date(ce.period_to, column.date_format) %> - <% end %> - <% when "file" %> - <% locale = I18n.locale.to_s %> -
    - <% ce.column_entry_files.desc(:sort_number).each do |entry_file| %> - <% next unless entry_file.choose_lang_display(locale) %> - <% if entry_file.file.content_type.start_with?('audio/') %> - <%= entry_file.get_file_title %> - - <% else %> -
  1. <%= link_to entry_file.get_file_title, entry_file.file.url, target: "_blank" %>
  2. - <% end %> - <% end %> -
- <% end %> - <% else %> -   - <% end %> - <% if index == 0 && can_edit %> - - <% end %> -
-
- - -
- <%= content_tag :div, paginate(@entries), class: "pagination pagination-centered" %> -
- Add Entry -
-
- - \ No newline at end of file diff --git a/app/views/admin/universal_tables/update_sort.html.erb b/app/views/admin/universal_tables/update_sort.html.erb old mode 100644 new mode 100755 diff --git a/app/views/universal_tables/download_file.html.erb b/app/views/universal_tables/download_file.html.erb old mode 100644 new mode 100755 index f6e4d90..486871f --- a/app/views/universal_tables/download_file.html.erb +++ b/app/views/universal_tables/download_file.html.erb @@ -1,147 +1,147 @@ -<% if @ext == 'pdf' %> - <%= render partial: 'archives/viewer' %> -<% else %> - - - - <%=@filename%> - <%= stylesheet_link_tag "archive/download_file.css" %> - - -

<%=@filename%>

- <% if @ext != "png" && @ext != "jpg" && @ext != "bmp" %> - - - <%=@filename%> - - <% else %> - <%=@filename%> - - <% end %> - - +<% if @ext == 'pdf' %> + <%= render partial: 'archives/viewer' %> +<% else %> + + + + <%=@filename%> + <%= stylesheet_link_tag "archive/download_file.css" %> + + +

<%=@filename%>

+ <% if @ext != "png" && @ext != "jpg" && @ext != "bmp" %> + + + <%=@filename%> + + <% else %> + <%=@filename%> + + <% end %> + + <% end %> \ No newline at end of file diff --git a/app/views/universal_tables/export_filtered.xlsx.axlsx b/app/views/universal_tables/export_filtered.xlsx.axlsx old mode 100644 new mode 100755 index 2e29d66..773d3c3 --- a/app/views/universal_tables/export_filtered.xlsx.axlsx +++ b/app/views/universal_tables/export_filtered.xlsx.axlsx @@ -1,20 +1,20 @@ -# encoding: utf-8 - - -wb = xlsx_package.workbook - -wb.add_worksheet(name: "Table") do |sheet| - heading = sheet.styles.add_style(:b => true, :locked => true) - headings = @tablecolumns.collect{|tc| tc.title} - sheet.add_row headings, :style => heading - - wrap = sheet.styles.add_style alignment: {wrap_text: true} - - @rows.each do |r| - row = [] - r["columns"].each do |col| - row << col["text"] - end - sheet.add_row row, style: wrap - end +# encoding: utf-8 + + +wb = xlsx_package.workbook + +wb.add_worksheet(name: "Table") do |sheet| + heading = sheet.styles.add_style(:b => true, :locked => true) + headings = @tablecolumns.collect{|tc| tc.title} + sheet.add_row headings, :style => heading + + wrap = sheet.styles.add_style alignment: {wrap_text: true} + + @rows.each do |r| + row = [] + r["columns"].each do |col| + row << col["text"] + end + sheet.add_row row, style: wrap + end end \ No newline at end of file diff --git a/app/views/universal_tables/index.html.erb b/app/views/universal_tables/index.html.erb old mode 100644 new mode 100755 index 67d24b0..3e8d78d --- a/app/views/universal_tables/index.html.erb +++ b/app/views/universal_tables/index.html.erb @@ -1,31 +1,31 @@ -<%= render_view %> - \ No newline at end of file diff --git a/app/views/universal_tables/mind_map.html.erb b/app/views/universal_tables/mind_map.html.erb old mode 100644 new mode 100755 index 7f5bb8a..a5e6589 --- a/app/views/universal_tables/mind_map.html.erb +++ b/app/views/universal_tables/mind_map.html.erb @@ -1,64 +1,64 @@ -<% - data = action_data - OrbitHelper.render_css_in_head(["mind_map/mindmap"]) -%> - -

<%= data["title"] %>

-
- \ No newline at end of file diff --git a/app/views/universal_tables/redirect_to_file.html.erb b/app/views/universal_tables/redirect_to_file.html.erb old mode 100644 new mode 100755 index 0968d5c..4d62d18 --- a/app/views/universal_tables/redirect_to_file.html.erb +++ b/app/views/universal_tables/redirect_to_file.html.erb @@ -1,17 +1,17 @@ - - - <%=@filename%> - - - -
-

<%=@filename%>

- <% download_text = t('download') + " " + @filename %> -

<%=download_text%>

-

- <%=t('close')%> -

-
- - + + + <%=@filename%> + + + +
+

<%=@filename%>

+ <% download_text = t('download') + " " + @filename %> +

<%=download_text%>

+

+ <%=t('close')%> +

+
+ + \ No newline at end of file diff --git a/app/views/universal_tables/show.html.erb b/app/views/universal_tables/show.html.erb old mode 100644 new mode 100755 index 67d24b0..3e8d78d --- a/app/views/universal_tables/show.html.erb +++ b/app/views/universal_tables/show.html.erb @@ -1,31 +1,31 @@ -<%= render_view %> - \ No newline at end of file diff --git a/app/views/utable_export/export.xlsx.axlsx b/app/views/utable_export/export.xlsx.axlsx old mode 100644 new mode 100755 index 03f32b8..ffa0f77 --- a/app/views/utable_export/export.xlsx.axlsx +++ b/app/views/utable_export/export.xlsx.axlsx @@ -3,127 +3,132 @@ wb = xlsx_package.workbook wb.add_worksheet(name: "Structure") do |sheet| - heading = sheet.styles.add_style(:b => true, :locked => true) - type = sheet.styles.add_style(:i => true) - wrap = sheet.styles.add_style alignment: {wrap_text: true} + heading = sheet.styles.add_style(b: true, locked: true) + type = sheet.styles.add_style(i: true) + wrap = sheet.styles.add_style alignment: { wrap_text: true } - row = [] - row1 = [] - row2 = [] + row = [] + row1 = [] + row2 = [] - row << "UID" - row1 << "uid" - row2 << "uid" + row << "UID" + row1 << "uid" + row2 << "uid" - table.table_columns.asc(:order).each do |column| - case column.type - when "text" - site_in_use_locales.sort.each do |locale| - row << column.title + " - " + t(locale.to_s) - row1 << column.key - row2 << column.type + "-#{locale}" - end - when "integer" - row << column.title - row1 << column.key - row2 << column.type - when "editor" - site_in_use_locales.sort.each do |locale| - row << column.title + " - " + t(locale.to_s) - row1 << column.key - row2 << column.type + "-#{locale}" - end - when "image" - row << column.title - row1 << column.key - row2 << "Public URL" - when "date" - row << column.title - row1 << column.key - row2 << column.type + " : " + column.date_format.upcase - when "period" - row << column.title + "-From ~ To" - row1 << column.key - row2 << column.type + " : " + column.date_format.upcase + "-period_from ~ period_to" - when "file" - row << column.title - row1 << column.key - row2 << "Separate the files by ;" - end - end + table.table_columns.asc(:order).each do |column| + case column.type + when "text", "editor" + site_in_use_locales.sort.each do |locale| + row << "#{column.title} - #{t(locale.to_s)}" + row1 << column.key + row2 << "#{column.type}-#{locale}" + end + when "integer" + row << column.title + row1 << column.key + row2 << column.type + when "image" + row << column.title + row1 << column.key + row2 << "Public URL" + when "date" + row << column.title + row1 << column.key + row2 << "#{column.type} : #{column.date_format.upcase}" + when "period" + row << "#{column.title} - From ~ To" + row1 << column.key + row2 << "#{column.type} : #{column.date_format.upcase}-period_from ~ period_to" + when "file" + row << "#{column.title} (Link)" + row1 << column.key + row2 << "Separate the files by ;" - row << t("universal_table.hashtags") - row1 << "table_tags" - row2 << "Separate tags by ;" + row << "#{column.title} 註解" + row1 << column.key + row2 << "file_title-#{locale}" + end + end - row << t("universal_table.related_entries") - row1 << "related_entries" - row2 << "Separate UIDs with ;" + row << t("universal_table.hashtags") + row1 << "table_tags" + row2 << "Separate tags by ;" - sheet.add_row row, :style => heading - sheet.add_row row1 - sheet.add_row row2, :style => type + row << t("universal_table.related_entries") + row1 << "related_entries" + row2 << "Separate UIDs with ;" - table.table_entries.asc(:created_at).each do |entry| - row = [] - row << entry.uid - table.table_columns.asc(:order).each do |col| - column = entry.column_entries.where(:table_column_id => col.id).first - case col.type - when "text" - site_in_use_locales.sort.each do |locale| - row << (column.text_translations[locale.to_s] rescue "") - end - when "integer" - row << column.number - when "editor" - site_in_use_locales.sort.each do |locale| - row << (column.content_translations[locale.to_s] rescue "") - end - when "image" - if !column.image.url.nil? - row << url + column.image.url - else - row << "" - end - when "date" - case col.date_format - when "yyyy/MM/dd hh:mm" - row << (column.date.strftime("%Y/%m/%d %H:%M") rescue "") - when "yyyy/MM/dd" - row << (column.date.strftime("%Y/%m/%d") rescue "") - when "yyyy/MM" - row << (column.date.strftime("%Y/%m/") rescue "") - when "yyyy" - row << (column.date.strftime("%Y") rescue "") - end - when "period" - case col.date_format - when "yyyy/MM/dd hh:mm" - row << (column.period_from.strftime("%Y/%m/%d %H:%M")rescue "") + " ~ " + (column.period_to.strftime("%Y/%m/%d %H:%M") rescue "") - when "yyyy/MM/dd" - row << (column.period_from.strftime("%Y/%m/%d")rescue "") + " ~ " + (column.period_to.strftime("%Y/%m/%d") rescue "") - when "yyyy/MM" - row << (column.period_from.strftime("%Y/%m")rescue "") + " ~ " + (column.period_to.strftime("%Y/%m") rescue "") - when "yyyy" - row << (column.period_from.strftime("%Y")rescue "") + " ~ " + (column.period_to.strftime("%Y") rescue "") - end - when "file" - file_links = [] - locale = I18n.locale.to_s - if !column.nil? - column.column_entry_files.desc(:sort_number).each do |entry_file| - next unless entry_file.choose_lang_display(locale) - file_links << (url + entry_file.get_link) - end - end - row << file_links.join(";") - end - end - row << entry.table_tags.pluck("title").map { |t| "#{t}" }.join("; ") - row << entry.get_related_entries_uid - sheet.add_row row, style: wrap - end + sheet.add_row row, style: heading + sheet.add_row row1 + sheet.add_row row2, style: type + table.table_entries.asc(:created_at).each do |entry| + row = [] + row << entry.uid -end \ No newline at end of file + table.table_columns.asc(:order).each do |col| + column = entry.column_entries.where(table_column_id: col.id).first + + case col.type + when "text" + site_in_use_locales.sort.each do |locale| + row << (column.text_translations[locale.to_s] rescue "") + end + when "integer" + row << column.number + when "editor" + site_in_use_locales.sort.each do |locale| + row << (column.content_translations[locale.to_s] rescue "") + end + when "image" + row << (column&.image&.url.present? ? (url + column.image.url) : "") + when "date" + format_str = case col.date_format + when "yyyy/MM/dd hh:mm" then "%Y/%m/%d %H:%M" + when "yyyy/MM/dd" then "%Y/%m/%d" + when "yyyy/MM" then "%Y/%m" + when "yyyy" then "%Y" + end + row << (column.date.strftime(format_str) rescue "") + when "period" + format_str = case col.date_format + when "yyyy/MM/dd hh:mm" then "%Y/%m/%d %H:%M" + when "yyyy/MM/dd" then "%Y/%m/%d" + when "yyyy/MM" then "%Y/%m" + when "yyyy" then "%Y" + end + from = (column.period_from.strftime(format_str) rescue "") + to = (column.period_to.strftime(format_str) rescue "") + row << "#{from} ~ #{to}" +when "file" + file_links = [] + file_titles = [] + locale = "zh_tw" + if column + column.column_entry_files.desc(:sort_number).each do |entry_file| + next unless entry_file.choose_lang_display(locale) + file_links << (url + entry_file.get_link) + + title = if entry_file.respond_to?(:file_title_translations) && entry_file.file_title_translations.is_a?(Hash) + entry_file.file_title_translations[locale] + elsif entry_file.file_title.is_a?(Hash) + entry_file.file_title[locale] + else + entry_file.file_title + end + + title = entry_file.file.filename.to_s if title.blank? + file_titles << title + end + end + row << file_links.join(";") + row << file_titles.join(";") + end + end + + row << entry.table_tags.pluck("title").join("; ") + row << entry.get_related_entries_uid + + sheet.add_row row, style: wrap + end +end diff --git a/bin/rails b/bin/rails index 5b76e68..d62a547 100755 --- a/bin/rails +++ b/bin/rails @@ -1,12 +1,12 @@ -#!/usr/bin/env ruby -# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. - -ENGINE_ROOT = File.expand_path('../..', __FILE__) -ENGINE_PATH = File.expand_path('../../lib/universal_table/engine', __FILE__) - -# Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) - -require 'rails/all' -require 'rails/engine/commands' +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. + +ENGINE_ROOT = File.expand_path('../..', __FILE__) +ENGINE_PATH = File.expand_path('../../lib/universal_table/engine', __FILE__) + +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) + +require 'rails/all' +require 'rails/engine/commands' diff --git a/config/locales/en.yml b/config/locales/en.yml old mode 100644 new mode 100755 index 6e76e1d..3ddd40f --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,36 +1,36 @@ -en: - universal_table: - universal_table: Universal Table - all_tables: All Tables - new_table: Create New Table - table_name: Table Name - created_time: Created Time - total_no_of_entries: No of entries - export_structure: Download table structure - import_from_excel: Import From Excel - total_number_of_entries: "Total number of enteries found : %{total_number}" - export_xls: Export XLSX - add_column: Add Column - default_ordered_field: Default Ordered Field - asc: asc - desc: desc - sort_number: Sort Number - created_at: Created Time - edit_sort: Edit Sorting - manual_update_sort: Manually Update Sorting - drag_file_to_here: Drag file to here - show_lang: Language - downloaded_times: Downloaded Times - mind_map: Mind Map - add_node: Add Node - delete_node: Delete Node - stroke_color: Line Color - bg_color: Background Color - text_color: Text Color - disable_editing: Disable editing - enable_editing: Enable editing - save_mind_map: Save mind map - hashtags: Hashtags - related_entries: Related entries - search_entries: Search entries +en: + universal_table: + universal_table: Universal Table + all_tables: All Tables + new_table: Create New Table + table_name: Table Name + created_time: Created Time + total_no_of_entries: No of entries + export_structure: Download table structure + import_from_excel: Import From Excel + total_number_of_entries: "Total number of enteries found : %{total_number}" + export_xls: Export XLSX + add_column: Add Column + default_ordered_field: Default Ordered Field + asc: asc + desc: desc + sort_number: Sort Number + created_at: Created Time + edit_sort: Edit Sorting + manual_update_sort: Manually Update Sorting + drag_file_to_here: Drag file to here + show_lang: Language + downloaded_times: Downloaded Times + mind_map: Mind Map + add_node: Add Node + delete_node: Delete Node + stroke_color: Line Color + bg_color: Background Color + text_color: Text Color + disable_editing: Disable editing + enable_editing: Enable editing + save_mind_map: Save mind map + hashtags: Hashtags + related_entries: Related entries + search_entries: Search entries status: Status \ No newline at end of file diff --git a/config/locales/zh_tw.yml b/config/locales/zh_tw.yml old mode 100644 new mode 100755 index 30f6f6a..03ec981 --- a/config/locales/zh_tw.yml +++ b/config/locales/zh_tw.yml @@ -1,36 +1,36 @@ -zh_tw: - universal_table: - universal_table: 萬用表格 - all_tables: 所有表格 - new_table: 新增表格 - table_name: 表格名稱 - created_time: 建立時間 - total_no_of_entries: 條目編號 - export_structure: 表格下載 - import_from_excel: 自Excel檔匯入 - total_number_of_entries: "搜尋結果數量: %{total_number}" - export_xls: 匯出XLSX - add_column: 新增欄位 - default_ordered_field: 預設排序欄位 - asc: 升序 - desc: 降序 - sort_number: 排序數 - created_at: 創建時間 - edit_sort: 編輯排序 - manual_update_sort: 手動更新排序 - drag_file_to_here: 拖移檔案到此 - show_lang: 呈現語系 - downloaded_times: 下載次數 - mind_map: Mind Map - add_node: 新增 - delete_node: 刪除 - stroke_color: 線條顏色 - bg_color: 背景顏色 - text_color: 文字顏色 - disable_editing: Disable editing - enable_editing: Enable editing - save_mind_map: Save mind map - hashtags: Hashtags - related_entries: Related entries - search_entries: Search entries +zh_tw: + universal_table: + universal_table: 萬用表格 + all_tables: 所有表格 + new_table: 新增表格 + table_name: 表格名稱 + created_time: 建立時間 + total_no_of_entries: 條目編號 + export_structure: 表格下載 + import_from_excel: 自Excel檔匯入 + total_number_of_entries: "搜尋結果數量: %{total_number}" + export_xls: 匯出XLSX + add_column: 新增欄位 + default_ordered_field: 預設排序欄位 + asc: 升序 + desc: 降序 + sort_number: 排序數 + created_at: 創建時間 + edit_sort: 編輯排序 + manual_update_sort: 手動更新排序 + drag_file_to_here: 拖移檔案到此 + show_lang: 呈現語系 + downloaded_times: 下載次數 + mind_map: Mind Map + add_node: 新增 + delete_node: 刪除 + stroke_color: 線條顏色 + bg_color: 背景顏色 + text_color: 文字顏色 + disable_editing: Disable editing + enable_editing: Enable editing + save_mind_map: Save mind map + hashtags: Hashtags + related_entries: Related entries + search_entries: Search entries status: Status \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb old mode 100644 new mode 100755 index 79eca8f..004f01b --- a/config/routes.rb +++ b/config/routes.rb @@ -1,45 +1,45 @@ -Rails.application.routes.draw do - - if ENV['worker_num']=='0' && File.basename($0) != 'rake' && !Rails.const_defined?('Console') - Thread.new do - stored_flag = "utf1" - need_update = Site.pluck(:tmp_flags).flatten.compact.exclude?(stored_flag) - if need_update - TableEntry.all.to_a.each do |te| - te.fix_have_data - end - Site.update_all("$push"=>{"tmp_flags"=> stored_flag}) - end - end - end - - locales = Site.first.in_use_locales rescue I18n.available_locales - - scope "(:locale)", locale: Regexp.new(locales.join("|")) do - namespace :admin do - post "/universal_tables/add_entry", to: 'universal_tables#add_entry' - post "/universal_tables/toggle_entries", to: 'universal_tables#toggle_entries' - get "/universal_tables/get_entries", to: 'universal_tables#get_entries' - get "/universal_tables/get_mindmaps", to: 'universal_tables#get_mindmaps' - patch "/universal_tables/update_entry", to: 'universal_tables#update_entry' - post "/universal_tables/import_data_from_excel", to: 'universal_tables#import_data_from_excel' - get "universal_tables/checkforthread", to: "universal_tables#checkforthread" - get "/universal_table/:id/mind_maps", to: "mind_maps#index" - resources :universal_tables do - get "new_entry" - delete "delete_entry" - get "edit_entry" - get "edit_sort" - post "update_sort", to: 'universal_tables#update_sort' - get "export_structure" - member do - get "export_data" - end - end - resources :mind_maps - end - get "/xhr/universal_table/export", to: 'universal_tables#export_filtered' - get "/xhr/universal_table/download", to: "universal_tables#download_file" - end - -end +Rails.application.routes.draw do + + if ENV['worker_num']=='0' && File.basename($0) != 'rake' && !Rails.const_defined?('Console') + Thread.new do + stored_flag = "utf1" + need_update = Site.pluck(:tmp_flags).flatten.compact.exclude?(stored_flag) + if need_update + TableEntry.all.to_a.each do |te| + te.fix_have_data + end + Site.update_all("$push"=>{"tmp_flags"=> stored_flag}) + end + end + end + + locales = Site.first.in_use_locales rescue I18n.available_locales + + scope "(:locale)", locale: Regexp.new(locales.join("|")) do + namespace :admin do + post "/universal_tables/add_entry", to: 'universal_tables#add_entry' + post "/universal_tables/toggle_entries", to: 'universal_tables#toggle_entries' + get "/universal_tables/get_entries", to: 'universal_tables#get_entries' + get "/universal_tables/get_mindmaps", to: 'universal_tables#get_mindmaps' + patch "/universal_tables/update_entry", to: 'universal_tables#update_entry' + post "/universal_tables/import_data_from_excel", to: 'universal_tables#import_data_from_excel' + get "universal_tables/checkforthread", to: "universal_tables#checkforthread" + get "/universal_table/:id/mind_maps", to: "mind_maps#index" + resources :universal_tables do + get "new_entry" + delete "delete_entry" + get "edit_entry" + get "edit_sort" + post "update_sort", to: 'universal_tables#update_sort' + get "export_structure" + member do + get "export_data" + end + end + resources :mind_maps + end + get "/xhr/universal_table/export", to: 'universal_tables#export_filtered' + get "/xhr/universal_table/download", to: "universal_tables#download_file" + end + +end diff --git a/lib/tasks/universal_table_tasks.rake b/lib/tasks/universal_table_tasks.rake old mode 100644 new mode 100755 index 7eb7454..3fd4fb8 --- a/lib/tasks/universal_table_tasks.rake +++ b/lib/tasks/universal_table_tasks.rake @@ -1,26 +1,26 @@ -# desc "Explaining what the task does" -# task :universal_table do -# # Task goes here -# end -namespace :universal_table_tasks do - task :prepare_download,[:utable_id, :url] => :environment do |task,args| - id = args.utable_id - I18n.locale = :zh_tw - table = UTable.find(id) - ac = ActionController::Base.new() - host_url = Site.first.root_url - if host_url == "http://" - host_url = "http://#{args.url}" - end - xlsx = ac.render_to_string handlers: [:axlsx], formats: [:xlsx], template: "utable_export/export", locals: {table: table, site_in_use_locales: Site.first.in_use_locales, url: host_url} - dirname = "public/uploads/utable_export/#{id}" - FileUtils.mkdir_p(dirname) unless File.exist?(dirname) - f = "#{dirname}/#{table.title.gsub(/[ "'*@#$%^&()+=;:.,?>|\\\/<~_!:,、。!?;「」〈〉【】/]/,'')}.xlsx" - if File.exist?(f) - File.delete(f) - end - file = File.open(f, "w") - xlsx.force_encoding("utf-8") - file.write(xlsx) - end -end +# desc "Explaining what the task does" +# task :universal_table do +# # Task goes here +# end +namespace :universal_table_tasks do + task :prepare_download,[:utable_id, :url] => :environment do |task,args| + id = args.utable_id + I18n.locale = :zh_tw + table = UTable.find(id) + ac = ActionController::Base.new() + host_url = Site.first.root_url + if host_url == "http://" + host_url = "http://#{args.url}" + end + xlsx = ac.render_to_string handlers: [:axlsx], formats: [:xlsx], template: "utable_export/export", locals: {table: table, site_in_use_locales: Site.first.in_use_locales, url: host_url} + dirname = "public/uploads/utable_export/#{id}" + FileUtils.mkdir_p(dirname) unless File.exist?(dirname) + f = "#{dirname}/#{table.title.gsub(/[ "'*@#$%^&()+=;:.,?>|\\\/<~_!:,、。!?;「」〈〉【】/]/,'')}.xlsx" + if File.exist?(f) + File.delete(f) + end + file = File.open(f, "w") + xlsx.force_encoding("utf-8") + file.write(xlsx) + end +end diff --git a/lib/universal_table.rb b/lib/universal_table.rb old mode 100644 new mode 100755 index 35c2599..fade82d --- a/lib/universal_table.rb +++ b/lib/universal_table.rb @@ -1,4 +1,4 @@ -require "universal_table/engine" - -module UniversalTable -end +require "universal_table/engine" + +module UniversalTable +end diff --git a/lib/universal_table/engine.rb b/lib/universal_table/engine.rb old mode 100644 new mode 100755 index 7e65ed3..e1b9b02 --- a/lib/universal_table/engine.rb +++ b/lib/universal_table/engine.rb @@ -1,65 +1,65 @@ -module UniversalTable - class Engine < ::Rails::Engine - initializer "universal_table" do - if ENV['worker_num']=='0' && File.basename($0) != 'rake' && !Rails.const_defined?('Console') - require File.expand_path('../../../app/models/table_entry', __FILE__) - require File.expand_path('../../../app/models/u_table', __FILE__) - if defined?(TableEntry) && defined?(UTable) - if TableEntry.where(sort_number: nil).count>0 - UTable.all.pluck(:id).each do |u_table_id| - table_entries = TableEntry.where(u_table_id: u_table_id).order_by(id: 1) - table_entry_ids = table_entries.pluck(:id) - table_entry_ids.each_with_index do |id,i| - TableEntry.where(id: id).update(sort_number: i) - end - end - end - end - end - OrbitApp.registration "UniversalTable", :type => "ModuleApp" do - module_label "universal_table.universal_table" - base_url File.expand_path File.dirname(__FILE__) - widget_methods ["widget","tag_cloud"] - widget_settings [{"data_count"=>30}] - # taggable "Bulletin" - categorizable - authorizable - frontend_enabled - data_count 1..30 - - side_bar do - head_label_i18n 'universal_table.universal_table', icon_class: "icons-untitled" - available_for "users" - active_for_controllers (['admin/universal_tables']) - head_link_path "admin_universal_tables_path" - - context_link 'universal_table.all_tables', - :link_path=>"admin_universal_tables_path" , - :priority=>1, - :active_for_action=>{'admin/universal_table'=>'index'}, - :available_for => 'users' - context_link 'universal_table.new_table', - :link_path=>"new_admin_universal_table_path" , - :priority=>2, - :active_for_action=>{'admin/universal_tables'=>'new'}, - :available_for => 'sub_managers' - context_link 'categories', - :link_path=>"admin_module_app_categories_path" , - :link_arg=>"{:module_app_id=>ModuleApp.find_by(:key=>'universal_table').id}", - :priority=>3, - :active_for_action=>{'admin/universal_tables'=>'categories'}, - :active_for_category => 'UniversalTable', - :available_for => 'managers' - # context_link 'tags', - # :link_path=>"admin_module_app_tags_path" , - # :link_arg=>"{:module_app_id=>ModuleApp.find_by(:key=>'universal_table').id}", - # :priority=>4, - # :active_for_action=>{'admin/universal_table'=>'tags'}, - # :active_for_tag => 'Announcement', - # :available_for => 'managers' - end - - end - end - end -end +module UniversalTable + class Engine < ::Rails::Engine + initializer "universal_table" do + if ENV['worker_num']=='0' && File.basename($0) != 'rake' && !Rails.const_defined?('Console') + require File.expand_path('../../../app/models/table_entry', __FILE__) + require File.expand_path('../../../app/models/u_table', __FILE__) + if defined?(TableEntry) && defined?(UTable) + if TableEntry.where(sort_number: nil).count>0 + UTable.all.pluck(:id).each do |u_table_id| + table_entries = TableEntry.where(u_table_id: u_table_id).order_by(id: 1) + table_entry_ids = table_entries.pluck(:id) + table_entry_ids.each_with_index do |id,i| + TableEntry.where(id: id).update(sort_number: i) + end + end + end + end + end + OrbitApp.registration "UniversalTable", :type => "ModuleApp" do + module_label "universal_table.universal_table" + base_url File.expand_path File.dirname(__FILE__) + widget_methods ["widget","tag_cloud"] + widget_settings [{"data_count"=>30}] + # taggable "Bulletin" + categorizable + authorizable + frontend_enabled + data_count 1..30 + + side_bar do + head_label_i18n 'universal_table.universal_table', icon_class: "icons-untitled" + available_for "users" + active_for_controllers (['admin/universal_tables']) + head_link_path "admin_universal_tables_path" + + context_link 'universal_table.all_tables', + :link_path=>"admin_universal_tables_path" , + :priority=>1, + :active_for_action=>{'admin/universal_table'=>'index'}, + :available_for => 'users' + context_link 'universal_table.new_table', + :link_path=>"new_admin_universal_table_path" , + :priority=>2, + :active_for_action=>{'admin/universal_tables'=>'new'}, + :available_for => 'sub_managers' + context_link 'categories', + :link_path=>"admin_module_app_categories_path" , + :link_arg=>"{:module_app_id=>ModuleApp.find_by(:key=>'universal_table').id}", + :priority=>3, + :active_for_action=>{'admin/universal_tables'=>'categories'}, + :active_for_category => 'UniversalTable', + :available_for => 'managers' + # context_link 'tags', + # :link_path=>"admin_module_app_tags_path" , + # :link_arg=>"{:module_app_id=>ModuleApp.find_by(:key=>'universal_table').id}", + # :priority=>4, + # :active_for_action=>{'admin/universal_table'=>'tags'}, + # :active_for_tag => 'Announcement', + # :available_for => 'managers' + end + + end + end + end +end diff --git a/lib/universal_table/version.rb b/lib/universal_table/version.rb old mode 100644 new mode 100755 index 8dfda7d..17fc54f --- a/lib/universal_table/version.rb +++ b/lib/universal_table/version.rb @@ -1,3 +1,3 @@ -module UniversalTable - VERSION = "0.0.1" -end +module UniversalTable + VERSION = "0.0.1" +end diff --git a/modules/universal_table/_tag_cloud.html.erb b/modules/universal_table/_tag_cloud.html.erb old mode 100644 new mode 100755 diff --git a/modules/universal_table/index.html.erb b/modules/universal_table/index.html.erb old mode 100644 new mode 100755 index 6d3d100..c188ce6 --- a/modules/universal_table/index.html.erb +++ b/modules/universal_table/index.html.erb @@ -1,111 +1,111 @@ - -
- - - - - - - - - - - - -
-

{{table-name}}

- Reset -
- -
{{title}}
- -
{{text}}
-
-
{{total_entries}}
-
{{export_button}}
- {{pagination_goes_here}} - - +
+ + + + + + + + + + + + +
+

{{table-name}}

+ Reset +
+ +
{{title}}
+ +
{{text}}
+
+
{{total_entries}}
+
{{export_button}}
+ {{pagination_goes_here}} + + \ No newline at end of file diff --git a/modules/universal_table/index2.html.erb b/modules/universal_table/index2.html.erb old mode 100644 new mode 100755 index ae6d4b4..b058544 --- a/modules/universal_table/index2.html.erb +++ b/modules/universal_table/index2.html.erb @@ -1,99 +1,99 @@ - -
- - - - - - - - - - - - -
-

{{table-name}}

- Reset -
- -
{{title}}
- -
{{text}}
-
-
{{total_entries}}
-
{{export_button}}
- {{pagination_goes_here}} - - \ No newline at end of file diff --git a/modules/universal_table/index3.html.erb b/modules/universal_table/index3.html.erb old mode 100644 new mode 100755 index 609bf0e..ea5f72c --- a/modules/universal_table/index3.html.erb +++ b/modules/universal_table/index3.html.erb @@ -1,118 +1,118 @@ - -
- -
-
-
查詢
- Reset -
-
- - - - - - - - - - - - -
-

{{table-name}}

-
- -
{{title}}
- -
{{text}}
- -
{{total_entries}}
-
{{export_button}}
- {{pagination_goes_here}} + +
+ +
+
+
查詢
+ Reset +
+
+ + + + + + + + + + + + +
+

{{table-name}}

+
+ +
{{title}}
+ +
{{text}}
+ +
{{total_entries}}
+
{{export_button}}
+ {{pagination_goes_here}} diff --git a/modules/universal_table/info.json b/modules/universal_table/info.json old mode 100644 new mode 100755 index 77a172e..34244b1 --- a/modules/universal_table/info.json +++ b/modules/universal_table/info.json @@ -1,37 +1,37 @@ -{ - "frontend": [ - { - "filename" : "index", - "name" : { - "zh_tw" : "1. 單純表格列表", - "en" : "1. Pure index table" - }, - "thumbnail" : "thumb.png" - }, - { - "filename" : "index2", - "name" : { - "zh_tw" : "2. 含序號表格列表", - "en" : "2. Index Table with serial number" - }, - "thumbnail" : "thumb.png" - }, - { - "filename" : "index3", - "name" : { - "zh_tw" : "3. 含序號表格列表 + 多欄位搜尋", - "en" : "3. Index Table with serial number + Multiple Field Search" - }, - "thumbnail" : "thumb.png", - "default": true - }, - { - "filename" : "mindmap", - "name" : { - "zh_tw" : "6. Mind Maps", - "en" : "6. Mind Maps" - }, - "thumbnail" : "thumb.png" - } - ] -} +{ + "frontend": [ + { + "filename" : "index", + "name" : { + "zh_tw" : "1. 單純表格列表", + "en" : "1. Pure index table" + }, + "thumbnail" : "thumb.png" + }, + { + "filename" : "index2", + "name" : { + "zh_tw" : "2. 含序號表格列表", + "en" : "2. Index Table with serial number" + }, + "thumbnail" : "thumb.png" + }, + { + "filename" : "index3", + "name" : { + "zh_tw" : "3. 含序號表格列表 + 多欄位搜尋", + "en" : "3. Index Table with serial number + Multiple Field Search" + }, + "thumbnail" : "thumb.png", + "default": true + }, + { + "filename" : "mindmap", + "name" : { + "zh_tw" : "6. Mind Maps", + "en" : "6. Mind Maps" + }, + "thumbnail" : "thumb.png" + } + ] +} diff --git a/modules/universal_table/mindmap.html.erb b/modules/universal_table/mindmap.html.erb old mode 100644 new mode 100755 index 4f5bd17..684a20f --- a/modules/universal_table/mindmap.html.erb +++ b/modules/universal_table/mindmap.html.erb @@ -1,107 +1,107 @@ - -
- - - - - - - -
-

{{table-name}}

-
{{title}}
-
-{{pagination_goes_here}} - + +
+ + + + + + + +
+

{{table-name}}

+
{{title}}
+
+{{pagination_goes_here}} + diff --git a/modules/universal_table/show.html.erb b/modules/universal_table/show.html.erb old mode 100644 new mode 100755 index 211c087..63b828a --- a/modules/universal_table/show.html.erb +++ b/modules/universal_table/show.html.erb @@ -1,33 +1,33 @@ - - - - - - - - -
{{title}}{{text}}
-
- {{view_count_head}}: - {{view_count}} -
- -
- - - {{text}} - - -
+ + + + + + + + +
{{title}}{{text}}
+
+ {{view_count_head}}: + {{view_count}} +
+ +
+ + + {{text}} + + +
diff --git a/modules/universal_table/thumbs/thumb.png b/modules/universal_table/thumbs/thumb.png old mode 100644 new mode 100755 diff --git a/test/dummy/README.rdoc b/test/dummy/README.rdoc old mode 100644 new mode 100755 index dd4e97e..4bc01f8 --- a/test/dummy/README.rdoc +++ b/test/dummy/README.rdoc @@ -1,28 +1,28 @@ -== README - -This README would normally document whatever steps are necessary to get the -application up and running. - -Things you may want to cover: - -* Ruby version - -* System dependencies - -* Configuration - -* Database creation - -* Database initialization - -* How to run the test suite - -* Services (job queues, cache servers, search engines, etc.) - -* Deployment instructions - -* ... - - -Please feel free to use a different markup language if you do not plan to run -rake doc:app. +== README + +This README would normally document whatever steps are necessary to get the +application up and running. + +Things you may want to cover: + +* Ruby version + +* System dependencies + +* Configuration + +* Database creation + +* Database initialization + +* How to run the test suite + +* Services (job queues, cache servers, search engines, etc.) + +* Deployment instructions + +* ... + + +Please feel free to use a different markup language if you do not plan to run +rake doc:app. diff --git a/test/dummy/Rakefile b/test/dummy/Rakefile old mode 100644 new mode 100755 index ba6b733..6ea7d63 --- a/test/dummy/Rakefile +++ b/test/dummy/Rakefile @@ -1,6 +1,6 @@ -# Add your own tasks in files placed in lib/tasks ending in .rake, -# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. - -require File.expand_path('../config/application', __FILE__) - -Rails.application.load_tasks +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require File.expand_path('../config/application', __FILE__) + +Rails.application.load_tasks diff --git a/test/dummy/app/assets/images/.keep b/test/dummy/app/assets/images/.keep old mode 100644 new mode 100755 diff --git a/test/dummy/app/assets/javascripts/application.js b/test/dummy/app/assets/javascripts/application.js old mode 100644 new mode 100755 index a1873dd..5aacd2a --- a/test/dummy/app/assets/javascripts/application.js +++ b/test/dummy/app/assets/javascripts/application.js @@ -1,13 +1,13 @@ -// This is a manifest file that'll be compiled into application.js, which will include all the files -// listed below. -// -// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. -// -// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// compiled file. -// -// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details -// about supported directives. -// -//= require_tree . +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require_tree . diff --git a/test/dummy/app/assets/stylesheets/application.css b/test/dummy/app/assets/stylesheets/application.css old mode 100644 new mode 100755 index a443db3..cfc4988 --- a/test/dummy/app/assets/stylesheets/application.css +++ b/test/dummy/app/assets/stylesheets/application.css @@ -1,15 +1,15 @@ -/* - * This is a manifest file that'll be compiled into application.css, which will include all the files - * listed below. - * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. - * - * You're free to add application-wide styles to this file and they'll appear at the bottom of the - * compiled file so the styles you add here take precedence over styles defined in any styles - * defined in the other CSS/SCSS files in this directory. It is generally better to create a new - * file per style scope. - * - *= require_tree . - *= require_self - */ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any styles + * defined in the other CSS/SCSS files in this directory. It is generally better to create a new + * file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/test/dummy/app/controllers/application_controller.rb b/test/dummy/app/controllers/application_controller.rb old mode 100644 new mode 100755 index d83690e..3b371ba --- a/test/dummy/app/controllers/application_controller.rb +++ b/test/dummy/app/controllers/application_controller.rb @@ -1,5 +1,5 @@ -class ApplicationController < ActionController::Base - # Prevent CSRF attacks by raising an exception. - # For APIs, you may want to use :null_session instead. - protect_from_forgery with: :exception -end +class ApplicationController < ActionController::Base + # Prevent CSRF attacks by raising an exception. + # For APIs, you may want to use :null_session instead. + protect_from_forgery with: :exception +end diff --git a/test/dummy/app/controllers/concerns/.keep b/test/dummy/app/controllers/concerns/.keep old mode 100644 new mode 100755 diff --git a/test/dummy/app/helpers/application_helper.rb b/test/dummy/app/helpers/application_helper.rb old mode 100644 new mode 100755 index de6be79..bf7774a --- a/test/dummy/app/helpers/application_helper.rb +++ b/test/dummy/app/helpers/application_helper.rb @@ -1,2 +1,2 @@ -module ApplicationHelper -end +module ApplicationHelper +end diff --git a/test/dummy/app/mailers/.keep b/test/dummy/app/mailers/.keep old mode 100644 new mode 100755 diff --git a/test/dummy/app/models/.keep b/test/dummy/app/models/.keep old mode 100644 new mode 100755 diff --git a/test/dummy/app/models/concerns/.keep b/test/dummy/app/models/concerns/.keep old mode 100644 new mode 100755 diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb old mode 100644 new mode 100755 index 593a778..47cf1a7 --- a/test/dummy/app/views/layouts/application.html.erb +++ b/test/dummy/app/views/layouts/application.html.erb @@ -1,14 +1,14 @@ - - - - Dummy - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> - <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> - <%= csrf_meta_tags %> - - - -<%= yield %> - - - + + + + Dummy + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> + <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> + <%= csrf_meta_tags %> + + + +<%= yield %> + + + diff --git a/test/dummy/bin/bundle b/test/dummy/bin/bundle index 66e9889..9c6dfa0 100755 --- a/test/dummy/bin/bundle +++ b/test/dummy/bin/bundle @@ -1,3 +1,3 @@ -#!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -load Gem.bin_path('bundler', 'bundle') +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +load Gem.bin_path('bundler', 'bundle') diff --git a/test/dummy/bin/rails b/test/dummy/bin/rails index 728cd85..7b28f23 100755 --- a/test/dummy/bin/rails +++ b/test/dummy/bin/rails @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby -APP_PATH = File.expand_path('../../config/application', __FILE__) -require_relative '../config/boot' -require 'rails/commands' +#!/usr/bin/env ruby +APP_PATH = File.expand_path('../../config/application', __FILE__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/test/dummy/bin/rake b/test/dummy/bin/rake index 1724048..8704afd 100755 --- a/test/dummy/bin/rake +++ b/test/dummy/bin/rake @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby -require_relative '../config/boot' -require 'rake' -Rake.application.run +#!/usr/bin/env ruby +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/test/dummy/config.ru b/test/dummy/config.ru old mode 100644 new mode 100755 index 5bc2a61..085f31c --- a/test/dummy/config.ru +++ b/test/dummy/config.ru @@ -1,4 +1,4 @@ -# This file is used by Rack-based servers to start the application. - -require ::File.expand_path('../config/environment', __FILE__) -run Rails.application +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run Rails.application diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb old mode 100644 new mode 100755 index ac4c348..df96cb6 --- a/test/dummy/config/application.rb +++ b/test/dummy/config/application.rb @@ -1,23 +1,23 @@ -require File.expand_path('../boot', __FILE__) - -require 'rails/all' - -Bundler.require(*Rails.groups) -require "universal_table" - -module Dummy - class Application < Rails::Application - # Settings in config/environments/* take precedence over those specified here. - # Application configuration should go into files in config/initializers - # -- all .rb files in that directory are automatically loaded. - - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. - # config.time_zone = 'Central Time (US & Canada)' - - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. - # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] - # config.i18n.default_locale = :de - end -end - +require File.expand_path('../boot', __FILE__) + +require 'rails/all' + +Bundler.require(*Rails.groups) +require "universal_table" + +module Dummy + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + end +end + diff --git a/test/dummy/config/boot.rb b/test/dummy/config/boot.rb old mode 100644 new mode 100755 index 6266cfc..72c2a82 --- a/test/dummy/config/boot.rb +++ b/test/dummy/config/boot.rb @@ -1,5 +1,5 @@ -# Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) - -require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) -$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) + +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) diff --git a/test/dummy/config/database.yml b/test/dummy/config/database.yml old mode 100644 new mode 100755 index 1c1a37c..3608801 --- a/test/dummy/config/database.yml +++ b/test/dummy/config/database.yml @@ -1,25 +1,25 @@ -# SQLite version 3.x -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem 'sqlite3' -# -default: &default - adapter: sqlite3 - pool: 5 - timeout: 5000 - -development: - <<: *default - database: db/development.sqlite3 - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - <<: *default - database: db/test.sqlite3 - -production: - <<: *default - database: db/production.sqlite3 +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: sqlite3 + pool: 5 + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/test/dummy/config/environment.rb b/test/dummy/config/environment.rb old mode 100644 new mode 100755 index ee8d90d..a925c89 --- a/test/dummy/config/environment.rb +++ b/test/dummy/config/environment.rb @@ -1,5 +1,5 @@ -# Load the Rails application. -require File.expand_path('../application', __FILE__) - -# Initialize the Rails application. -Rails.application.initialize! +# Load the Rails application. +require File.expand_path('../application', __FILE__) + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/test/dummy/config/environments/development.rb b/test/dummy/config/environments/development.rb old mode 100644 new mode 100755 index ddf0e90..4c0c279 --- a/test/dummy/config/environments/development.rb +++ b/test/dummy/config/environments/development.rb @@ -1,37 +1,37 @@ -Rails.application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # In the development environment your application's code is reloaded on - # every request. This slows down response time but is perfect for development - # since you don't have to restart the web server when you make code changes. - config.cache_classes = false - - # Do not eager load code on boot. - config.eager_load = false - - # Show full error reports and disable caching. - config.consider_all_requests_local = true - config.action_controller.perform_caching = false - - # Don't care if the mailer can't send. - config.action_mailer.raise_delivery_errors = false - - # Print deprecation notices to the Rails logger. - config.active_support.deprecation = :log - - # Raise an error on page load if there are pending migrations. - config.active_record.migration_error = :page_load - - # Debug mode disables concatenation and preprocessing of assets. - # This option may cause significant delays in view rendering with a large - # number of complex assets. - config.assets.debug = true - - # Adds additional error checking when serving assets at runtime. - # Checks for improperly declared sprockets dependencies. - # Raises helpful error messages. - config.assets.raise_runtime_errors = true - - # Raises error for missing translations - # config.action_view.raise_on_missing_translations = true -end +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Adds additional error checking when serving assets at runtime. + # Checks for improperly declared sprockets dependencies. + # Raises helpful error messages. + config.assets.raise_runtime_errors = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/test/dummy/config/environments/production.rb b/test/dummy/config/environments/production.rb old mode 100644 new mode 100755 index b93a877..2c91392 --- a/test/dummy/config/environments/production.rb +++ b/test/dummy/config/environments/production.rb @@ -1,78 +1,78 @@ -Rails.application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # Code is not reloaded between requests. - config.cache_classes = true - - # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both threaded web servers - # and those relying on copy on write to perform better. - # Rake tasks automatically ignore this option for performance. - config.eager_load = true - - # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false - config.action_controller.perform_caching = true - - # Enable Rack::Cache to put a simple HTTP cache in front of your application - # Add `rack-cache` to your Gemfile before enabling this. - # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. - # config.action_dispatch.rack_cache = true - - # Disable Rails's static asset server (Apache or nginx will already do this). - config.serve_static_assets = false - - # Compress JavaScripts and CSS. - config.assets.js_compressor = :uglifier - # config.assets.css_compressor = :sass - - # Do not fallback to assets pipeline if a precompiled asset is missed. - config.assets.compile = false - - # Generate digests for assets URLs. - config.assets.digest = true - - # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb - - # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache - # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx - - # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - # config.force_ssl = true - - # Set to :debug to see everything in the log. - config.log_level = :info - - # Prepend all log lines with the following tags. - # config.log_tags = [ :subdomain, :uuid ] - - # Use a different logger for distributed setups. - # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - - # Use a different cache store in production. - # config.cache_store = :mem_cache_store - - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = "http://assets.example.com" - - # Ignore bad email addresses and do not raise email delivery errors. - # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false - - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation cannot be found). - config.i18n.fallbacks = true - - # Send deprecation notices to registered listeners. - config.active_support.deprecation = :notify - - # Disable automatic flushing of the log to improve performance. - # config.autoflush_log = false - - # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new - - # Do not dump schema after migrations. - config.active_record.dump_schema_after_migration = false -end +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. + # config.action_dispatch.rack_cache = true + + # Disable Rails's static asset server (Apache or nginx will already do this). + config.serve_static_assets = false + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Generate digests for assets URLs. + config.assets.digest = true + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Set to :debug to see everything in the log. + config.log_level = :info + + # Prepend all log lines with the following tags. + # config.log_tags = [ :subdomain, :uuid ] + + # Use a different logger for distributed setups. + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = "http://assets.example.com" + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Disable automatic flushing of the log to improve performance. + # config.autoflush_log = false + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/test/dummy/config/environments/test.rb b/test/dummy/config/environments/test.rb old mode 100644 new mode 100755 index 053f5b6..63d9b10 --- a/test/dummy/config/environments/test.rb +++ b/test/dummy/config/environments/test.rb @@ -1,39 +1,39 @@ -Rails.application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # The test environment is used exclusively to run your application's - # test suite. You never need to work with it otherwise. Remember that - # your test database is "scratch space" for the test suite and is wiped - # and recreated between test runs. Don't rely on the data there! - config.cache_classes = true - - # Do not eager load code on boot. This avoids loading your whole application - # just for the purpose of running a single test. If you are using a tool that - # preloads Rails for running tests, you may have to set it to true. - config.eager_load = false - - # Configure static asset server for tests with Cache-Control for performance. - config.serve_static_assets = true - config.static_cache_control = 'public, max-age=3600' - - # Show full error reports and disable caching. - config.consider_all_requests_local = true - config.action_controller.perform_caching = false - - # Raise exceptions instead of rendering exception templates. - config.action_dispatch.show_exceptions = false - - # Disable request forgery protection in test environment. - config.action_controller.allow_forgery_protection = false - - # Tell Action Mailer not to deliver emails to the real world. - # The :test delivery method accumulates sent emails in the - # ActionMailer::Base.deliveries array. - config.action_mailer.delivery_method = :test - - # Print deprecation notices to the stderr. - config.active_support.deprecation = :stderr - - # Raises error for missing translations - # config.action_view.raise_on_missing_translations = true -end +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure static asset server for tests with Cache-Control for performance. + config.serve_static_assets = true + config.static_cache_control = 'public, max-age=3600' + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/test/dummy/config/initializers/assets.rb b/test/dummy/config/initializers/assets.rb old mode 100644 new mode 100755 index d2f4ec3..531a9d4 --- a/test/dummy/config/initializers/assets.rb +++ b/test/dummy/config/initializers/assets.rb @@ -1,8 +1,8 @@ -# Be sure to restart your server when you modify this file. - -# Version of your assets, change this if you want to expire all your assets. -Rails.application.config.assets.version = '1.0' - -# Precompile additional assets. -# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. -# Rails.application.config.assets.precompile += %w( search.js ) +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. +# Rails.application.config.assets.precompile += %w( search.js ) diff --git a/test/dummy/config/initializers/backtrace_silencers.rb b/test/dummy/config/initializers/backtrace_silencers.rb old mode 100644 new mode 100755 index 59385cd..803738d --- a/test/dummy/config/initializers/backtrace_silencers.rb +++ b/test/dummy/config/initializers/backtrace_silencers.rb @@ -1,7 +1,7 @@ -# Be sure to restart your server when you modify this file. - -# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. -# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } - -# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. -# Rails.backtrace_cleaner.remove_silencers! +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/test/dummy/config/initializers/cookies_serializer.rb b/test/dummy/config/initializers/cookies_serializer.rb old mode 100644 new mode 100755 index 7a06a89..c944d3a --- a/test/dummy/config/initializers/cookies_serializer.rb +++ b/test/dummy/config/initializers/cookies_serializer.rb @@ -1,3 +1,3 @@ -# Be sure to restart your server when you modify this file. - +# Be sure to restart your server when you modify this file. + Rails.application.config.action_dispatch.cookies_serializer = :json \ No newline at end of file diff --git a/test/dummy/config/initializers/filter_parameter_logging.rb b/test/dummy/config/initializers/filter_parameter_logging.rb old mode 100644 new mode 100755 index 4a994e1..180af8a --- a/test/dummy/config/initializers/filter_parameter_logging.rb +++ b/test/dummy/config/initializers/filter_parameter_logging.rb @@ -1,4 +1,4 @@ -# Be sure to restart your server when you modify this file. - -# Configure sensitive parameters which will be filtered from the log file. -Rails.application.config.filter_parameters += [:password] +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/test/dummy/config/initializers/inflections.rb b/test/dummy/config/initializers/inflections.rb old mode 100644 new mode 100755 index ac033bf..d173fb9 --- a/test/dummy/config/initializers/inflections.rb +++ b/test/dummy/config/initializers/inflections.rb @@ -1,16 +1,16 @@ -# Be sure to restart your server when you modify this file. - -# Add new inflection rules using the following format. Inflections -# are locale specific, and you may define rules for as many different -# locales as you wish. All of these examples are active by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.plural /^(ox)$/i, '\1en' -# inflect.singular /^(ox)en/i, '\1' -# inflect.irregular 'person', 'people' -# inflect.uncountable %w( fish sheep ) -# end - -# These inflection rules are supported but not enabled by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.acronym 'RESTful' -# end +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/test/dummy/config/initializers/mime_types.rb b/test/dummy/config/initializers/mime_types.rb old mode 100644 new mode 100755 index dc18996..b9979aa --- a/test/dummy/config/initializers/mime_types.rb +++ b/test/dummy/config/initializers/mime_types.rb @@ -1,4 +1,4 @@ -# Be sure to restart your server when you modify this file. - -# Add new mime types for use in respond_to blocks: -# Mime::Type.register "text/richtext", :rtf +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/test/dummy/config/initializers/session_store.rb b/test/dummy/config/initializers/session_store.rb old mode 100644 new mode 100755 index e766b67..0315b1b --- a/test/dummy/config/initializers/session_store.rb +++ b/test/dummy/config/initializers/session_store.rb @@ -1,3 +1,3 @@ -# Be sure to restart your server when you modify this file. - -Rails.application.config.session_store :cookie_store, key: '_dummy_session' +# Be sure to restart your server when you modify this file. + +Rails.application.config.session_store :cookie_store, key: '_dummy_session' diff --git a/test/dummy/config/initializers/wrap_parameters.rb b/test/dummy/config/initializers/wrap_parameters.rb old mode 100644 new mode 100755 index 33725e9..466d360 --- a/test/dummy/config/initializers/wrap_parameters.rb +++ b/test/dummy/config/initializers/wrap_parameters.rb @@ -1,14 +1,14 @@ -# Be sure to restart your server when you modify this file. - -# This file contains settings for ActionController::ParamsWrapper which -# is enabled by default. - -# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. -ActiveSupport.on_load(:action_controller) do - wrap_parameters format: [:json] if respond_to?(:wrap_parameters) -end - -# To enable root element in JSON for ActiveRecord objects. -# ActiveSupport.on_load(:active_record) do -# self.include_root_in_json = true -# end +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] if respond_to?(:wrap_parameters) +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/test/dummy/config/locales/en.yml b/test/dummy/config/locales/en.yml old mode 100644 new mode 100755 index 0653957..374ca54 --- a/test/dummy/config/locales/en.yml +++ b/test/dummy/config/locales/en.yml @@ -1,23 +1,23 @@ -# Files in the config/locales directory are used for internationalization -# and are automatically loaded by Rails. If you want to use locales other -# than English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t 'hello' -# -# In views, this is aliased to just `t`: -# -# <%= t('hello') %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# To learn more, please read the Rails Internationalization guide -# available at http://guides.rubyonrails.org/i18n.html. - -en: - hello: "Hello world" +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb old mode 100644 new mode 100755 index 590f846..a63fcee --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -1,4 +1,4 @@ -Rails.application.routes.draw do - - mount UniversalTable::Engine => "/universal_table" -end +Rails.application.routes.draw do + + mount UniversalTable::Engine => "/universal_table" +end diff --git a/test/dummy/config/secrets.yml b/test/dummy/config/secrets.yml old mode 100644 new mode 100755 index b76fc7f..4e1774f --- a/test/dummy/config/secrets.yml +++ b/test/dummy/config/secrets.yml @@ -1,22 +1,22 @@ -# Be sure to restart your server when you modify this file. - -# Your secret key is used for verifying the integrity of signed cookies. -# If you change this key, all old signed cookies will become invalid! - -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -# You can use `rake secret` to generate a secure secret key. - -# Make sure the secrets in this file are kept private -# if you're sharing your code publicly. - -development: - secret_key_base: 2e776a135036adaf5d26161c92c238abaa3e6180e2adac8b7dd14cf37de7dec63dd62ccb6f9bbe900f6ea9de23cce0c7ef073f2d181906b9fca907671b331051 - -test: - secret_key_base: e545f79b01755572ddcc29be62e6ca5d806459b2e0d60da27e25542b7700b1f335ec4142514c521f4207aab84dddabe9906b358bed001ba89c5a1f82f2853098 - -# Do not keep production secrets in the repository, -# instead read values from the environment. -production: - secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rake secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +development: + secret_key_base: 2e776a135036adaf5d26161c92c238abaa3e6180e2adac8b7dd14cf37de7dec63dd62ccb6f9bbe900f6ea9de23cce0c7ef073f2d181906b9fca907671b331051 + +test: + secret_key_base: e545f79b01755572ddcc29be62e6ca5d806459b2e0d60da27e25542b7700b1f335ec4142514c521f4207aab84dddabe9906b358bed001ba89c5a1f82f2853098 + +# Do not keep production secrets in the repository, +# instead read values from the environment. +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/test/dummy/lib/assets/.keep b/test/dummy/lib/assets/.keep old mode 100644 new mode 100755 diff --git a/test/dummy/log/.keep b/test/dummy/log/.keep old mode 100644 new mode 100755 diff --git a/test/dummy/public/404.html b/test/dummy/public/404.html old mode 100644 new mode 100755 index b612547..00431b8 --- a/test/dummy/public/404.html +++ b/test/dummy/public/404.html @@ -1,67 +1,67 @@ - - - - The page you were looking for doesn't exist (404) - - - - - - -
-
-

The page you were looking for doesn't exist.

-

You may have mistyped the address or the page may have moved.

-
-

If you are the application owner check the logs for more information.

-
- - + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/test/dummy/public/422.html b/test/dummy/public/422.html old mode 100644 new mode 100755 index a21f82b..f313786 --- a/test/dummy/public/422.html +++ b/test/dummy/public/422.html @@ -1,67 +1,67 @@ - - - - The change you wanted was rejected (422) - - - - - - -
-
-

The change you wanted was rejected.

-

Maybe you tried to change something you didn't have access to.

-
-

If you are the application owner check the logs for more information.

-
- - + + + + The change you wanted was rejected (422) + + + + + + +
+
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/test/dummy/public/500.html b/test/dummy/public/500.html old mode 100644 new mode 100755 index 061abc5..860aefd --- a/test/dummy/public/500.html +++ b/test/dummy/public/500.html @@ -1,66 +1,66 @@ - - - - We're sorry, but something went wrong (500) - - - - - - -
-
-

We're sorry, but something went wrong.

-
-

If you are the application owner check the logs for more information.

-
- - + + + + We're sorry, but something went wrong (500) + + + + + + +
+
+

We're sorry, but something went wrong.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/test/dummy/public/favicon.ico b/test/dummy/public/favicon.ico old mode 100644 new mode 100755 diff --git a/test/integration/navigation_test.rb b/test/integration/navigation_test.rb old mode 100644 new mode 100755 index 97a94c9..f0cab94 --- a/test/integration/navigation_test.rb +++ b/test/integration/navigation_test.rb @@ -1,10 +1,10 @@ -require 'test_helper' - -class NavigationTest < ActionDispatch::IntegrationTest - fixtures :all - - # test "the truth" do - # assert true - # end -end - +require 'test_helper' + +class NavigationTest < ActionDispatch::IntegrationTest + fixtures :all + + # test "the truth" do + # assert true + # end +end + diff --git a/test/test_helper.rb b/test/test_helper.rb old mode 100644 new mode 100755 index a553d9a..e798795 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,19 +1,19 @@ -# Configure Rails Environment -ENV["RAILS_ENV"] = "test" - -require File.expand_path("../../test/dummy/config/environment.rb", __FILE__) -ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../test/dummy/db/migrate", __FILE__)] -ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__) -require "rails/test_help" - -# Filter out Minitest backtrace while allowing backtrace from other libraries -# to be shown. -Minitest.backtrace_filter = Minitest::BacktraceFilter.new - -# Load support files -Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } - -# Load fixtures from the engine -if ActiveSupport::TestCase.method_defined?(:fixture_path=) - ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) -end +# Configure Rails Environment +ENV["RAILS_ENV"] = "test" + +require File.expand_path("../../test/dummy/config/environment.rb", __FILE__) +ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../test/dummy/db/migrate", __FILE__)] +ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__) +require "rails/test_help" + +# Filter out Minitest backtrace while allowing backtrace from other libraries +# to be shown. +Minitest.backtrace_filter = Minitest::BacktraceFilter.new + +# Load support files +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } + +# Load fixtures from the engine +if ActiveSupport::TestCase.method_defined?(:fixture_path=) + ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) +end diff --git a/test/universal_table_test.rb b/test/universal_table_test.rb old mode 100644 new mode 100755 index ef7ee6e..b75ba76 --- a/test/universal_table_test.rb +++ b/test/universal_table_test.rb @@ -1,7 +1,7 @@ -require 'test_helper' - -class UniversalTableTest < ActiveSupport::TestCase - test "truth" do - assert_kind_of Module, UniversalTable - end -end +require 'test_helper' + +class UniversalTableTest < ActiveSupport::TestCase + test "truth" do + assert_kind_of Module, UniversalTable + end +end diff --git a/universal_table.gemspec b/universal_table.gemspec old mode 100644 new mode 100755 index 96bc5ca..3899d70 --- a/universal_table.gemspec +++ b/universal_table.gemspec @@ -1,33 +1,33 @@ -$:.push File.expand_path("../lib", __FILE__) - -# Maintain your gem's version: -require "universal_table/version" -bundle_update_flag = ARGV[0]=='update' || ARGV[0]=='install' -if bundle_update_flag - env_pwd = ENV['PWD'] - app_path = File.expand_path(__dir__) - template_path = env_pwd + '/app/templates' - all_template = Dir.glob(template_path+'/*/') - all_template.each do |folder| - if !folder.include?('mobile') - moudle_path = "#{folder}modules/universal_table/" - if Dir.exist?(File.dirname(moudle_path)) - Bundler.with_clean_env{system ('cp -r '+ app_path + '/modules/ ' + folder)} - end - end - end -end -# Describe your gem and declare its dependencies: -Gem::Specification.new do |s| - s.name = "universal_table" - s.version = UniversalTable::VERSION - s.authors = ["Ruling Digital"] - s.email = ["orbit@rulingcom.com"] - s.homepage = "http://www.rulingcom.com" - s.summary = "Table module for Orbit 4.5." - s.description = "Table module for Orbit 4.5." - s.license = "MIT" - - s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"] - s.test_files = Dir["test/**/*"] -end +$:.push File.expand_path("../lib", __FILE__) + +# Maintain your gem's version: +require "universal_table/version" +bundle_update_flag = ARGV[0]=='update' || ARGV[0]=='install' +if bundle_update_flag + env_pwd = ENV['PWD'] + app_path = File.expand_path(__dir__) + template_path = env_pwd + '/app/templates' + all_template = Dir.glob(template_path+'/*/') + all_template.each do |folder| + if !folder.include?('mobile') + moudle_path = "#{folder}modules/universal_table/" + if Dir.exist?(File.dirname(moudle_path)) + Bundler.with_clean_env{system ('cp -r '+ app_path + '/modules/ ' + folder)} + end + end + end +end +# Describe your gem and declare its dependencies: +Gem::Specification.new do |s| + s.name = "universal_table" + s.version = UniversalTable::VERSION + s.authors = ["Ruling Digital"] + s.email = ["orbit@rulingcom.com"] + s.homepage = "http://www.rulingcom.com" + s.summary = "Table module for Orbit 4.5." + s.description = "Table module for Orbit 4.5." + s.license = "MIT" + + s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"] + s.test_files = Dir["test/**/*"] +end