id覆蓋檔案匯入
This commit is contained in:
parent
b1d9692b82
commit
e9375e6449
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
= UniversalTable
|
||||
|
||||
= UniversalTable
|
||||
|
||||
This project rocks and uses MIT-LICENSE.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
376
app/assets/javascripts/mind_map/jsmind/jsmind.shortcut_provider.js
Normal file → Executable file
376
app/assets/javascripts/mind_map/jsmind/jsmind.shortcut_provider.js
Normal file → Executable file
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
932
app/assets/javascripts/mind_map/jsmind/plugins/jsmind.draggable-node.js
Normal file → Executable file
932
app/assets/javascripts/mind_map/jsmind/plugins/jsmind.draggable-node.js
Normal file → Executable file
|
|
@ -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)
|
||||
|
|
|
|||
316
app/assets/javascripts/mind_map/jsmind/plugins/jsmind.screenshot.js
Normal file → Executable file
316
app/assets/javascripts/mind_map/jsmind/plugins/jsmind.screenshot.js
Normal file → Executable file
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
? `<a href="${node.link}" target="_blank">${node.text}</a>`
|
||||
: 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
|
||||
? `<a href="${node.link}" target="_blank">${node.text}</a>`
|
||||
: 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')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
? `<a href="${f.url}" target="_blank">${f.text}</a>`
|
||||
: f.text;
|
||||
return `<div class="field-row"><strong>${f.title}:</strong> ${txt}</div>`;
|
||||
}).join("");
|
||||
|
||||
return `
|
||||
<div class="suggestion-item" data-link="${item.link}">
|
||||
${fieldHtml}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
|
||||
|
||||
.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
|
||||
? `<a href="${f.url}" target="_blank">${f.text}</a>`
|
||||
: f.text;
|
||||
return `<div class="field-row"><strong>${f.title}:</strong> ${txt}</div>`;
|
||||
}).join("");
|
||||
|
||||
return `
|
||||
<div class="suggestion-item" data-link="${item.link}">
|
||||
${fieldHtml}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
|
||||
|
||||
.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');
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 .
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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"]}
|
||||
|
|
|
|||
|
|
@ -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 = "<select id='#{column.key}_#{i}'>"
|
||||
s = {"en" => "", "zh_tw" => ""}
|
||||
select = select + "<option class='muted' value='#{s.to_json.html_safe}'>---------------Select---------------</option>"
|
||||
select_values.each do |sv|
|
||||
select = select + "<option value='#{sv.to_json.html_safe}'>#{sv[I18n.locale]}</option>"
|
||||
end
|
||||
select = select + "</select>"
|
||||
"<div class='select-holder'> <span>Or</span> " + select + "</div>"
|
||||
end
|
||||
|
||||
def render_unique_number(f,column,i)
|
||||
select_values = column.column_entries.distinct(:number)
|
||||
select = "<select id='#{column.key}_#{i}'>"
|
||||
s = ""
|
||||
select = select + "<option class='muted' value='#{s}'>---------------Select---------------</option>"
|
||||
select_values.each do |sv|
|
||||
select = select + "<option value='#{sv}'>#{sv}</option>"
|
||||
end
|
||||
select = select + "</select>"
|
||||
"<div class='select-holder'> <span>Or</span> " + select + "</div>"
|
||||
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 = "<select id='#{column.key}_#{i}'>"
|
||||
s = {"en" => "", "zh_tw" => ""}
|
||||
select = select + "<option class='muted' value='#{s.to_json.html_safe}'>---------------Select---------------</option>"
|
||||
select_values.each do |sv|
|
||||
select = select + "<option value='#{sv.to_json.html_safe}'>#{sv[I18n.locale]}</option>"
|
||||
end
|
||||
select = select + "</select>"
|
||||
"<div class='select-holder'> <span>Or</span> " + select + "</div>"
|
||||
end
|
||||
|
||||
def render_unique_number(f,column,i)
|
||||
select_values = column.column_entries.distinct(:number)
|
||||
select = "<select id='#{column.key}_#{i}'>"
|
||||
s = ""
|
||||
select = select + "<option class='muted' value='#{s}'>---------------Select---------------</option>"
|
||||
select_values.each do |sv|
|
||||
select = select + "<option value='#{sv}'>#{sv}</option>"
|
||||
end
|
||||
select = select + "</select>"
|
||||
"<div class='select-holder'> <span>Or</span> " + select + "</div>"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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 = "<img src='#{self.image.thumb.url}' class='image-preview' />"
|
||||
when "file"
|
||||
locale = I18n.locale.to_s
|
||||
text = "<ul class=\"column_entry_files\">"
|
||||
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 += "<div class=\"voice-player\"><span class=\"voice-title\">#{file_title}</span><a class=\"voice-player\" data-content=\"#{entry_file.file.url}\" href="" title=\"#{file_title}\"><i class=\"fa fa-play\" aria-hidden=\"true\"></i></a></div>"
|
||||
else
|
||||
text += "<li class=\"column_entry_file\"><a class=\"column_entry_file_link\" href=\"#{entry_file.get_link}\" title=\"#{file_title}\" target=\"_blank\">#{file_title}</a><span class=\"file_size\">(#{number_to_human_size(entry_file.file.size)})</span><span class=\"view_count\"><i class=\"fa fa-eye\" title=\"#{I18n.t("universal_table.downloaded_times")}\"></i><span class=\"view-count\">#{entry_file.download_count}</span></span></li>"
|
||||
end
|
||||
end
|
||||
text += "</ul>"
|
||||
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 = "<img src='#{self.image.thumb.url}' class='image-preview' />"
|
||||
when "file"
|
||||
locale = I18n.locale.to_s
|
||||
text = "<ul class=\"column_entry_files\">"
|
||||
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 += "<div class=\"voice-player\"><span class=\"voice-title\">#{file_title}</span><a class=\"voice-player\" data-content=\"#{entry_file.file.url}\" href="" title=\"#{file_title}\"><i class=\"fa fa-play\" aria-hidden=\"true\"></i></a></div>"
|
||||
else
|
||||
text += "<li class=\"column_entry_file\"><a class=\"column_entry_file_link\" href=\"#{entry_file.get_link}\" title=\"#{file_title}\" target=\"_blank\">#{file_title}</a><span class=\"file_size\">(#{number_to_human_size(entry_file.file.size)})</span><span class=\"view_count\"><i class=\"fa fa-eye\" title=\"#{I18n.t("universal_table.downloaded_times")}\"></i><span class=\"view-count\">#{entry_file.download_count}</span></span></li>"
|
||||
end
|
||||
end
|
||||
text += "</ul>"
|
||||
end
|
||||
text
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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|
|
||||
"<a class='tag' href='/#{params[:locale]}#{params[:url]}?tag=#{tt.title}'>#" + tt.title + "</a>"
|
||||
}.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|
|
||||
"<a class='tag' href='/#{params[:locale]}#{params[:url]}?tag=#{tt.title}'>#" + tt.title + "</a>"
|
||||
}.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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,45 +1,45 @@
|
|||
<fieldset class="utable-heading-wrap">
|
||||
<div class="utable-heading-header">
|
||||
<h4><%= t("universal_table.table_name") %> - <%= @table.title %></h4>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<div class="tab-content">
|
||||
<% @site_in_use_locales.each do |locale| %>
|
||||
<% active = (locale == @site_in_use_locales.first ? "active in" : "") %>
|
||||
<div class="tab-pane fade <%= active %>" id="mind_map_<%= locale.to_s %>">
|
||||
<%= f.fields_for :title_translations do |f| %>
|
||||
<%= f.text_field locale, :placeholder => "Title", :value => @mind_map.title_translations[locale] %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<% @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 %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="utable-heading-wrap">
|
||||
<div class="utable-heading-header">
|
||||
<h4><%= t("universal_table.mind_map") %></h4>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<button id="toggle_editable"><%= t("universal_table.disable_editing") %></button>
|
||||
</div>
|
||||
<div id="jsmind_container"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="utable-content">
|
||||
<div class="form-actions">
|
||||
<%= f.hidden_field :mind_map_data, id: "mind_map_data_field", value: "[]" %>
|
||||
<%= f.hidden_field :u_table_id, value: @table.id %>
|
||||
<input class="btn btn-primary pull-right" name="commit" type="submit" value="<%= t("save") %>">
|
||||
</div>
|
||||
<fieldset class="utable-heading-wrap">
|
||||
<div class="utable-heading-header">
|
||||
<h4><%= t("universal_table.table_name") %> - <%= @table.title %></h4>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<div class="tab-content">
|
||||
<% @site_in_use_locales.each do |locale| %>
|
||||
<% active = (locale == @site_in_use_locales.first ? "active in" : "") %>
|
||||
<div class="tab-pane fade <%= active %>" id="mind_map_<%= locale.to_s %>">
|
||||
<%= f.fields_for :title_translations do |f| %>
|
||||
<%= f.text_field locale, :placeholder => "Title", :value => @mind_map.title_translations[locale] %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<% @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 %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="utable-heading-wrap">
|
||||
<div class="utable-heading-header">
|
||||
<h4><%= t("universal_table.mind_map") %></h4>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<button id="toggle_editable"><%= t("universal_table.disable_editing") %></button>
|
||||
</div>
|
||||
<div id="jsmind_container"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="utable-content">
|
||||
<div class="form-actions">
|
||||
<%= f.hidden_field :mind_map_data, id: "mind_map_data_field", value: "[]" %>
|
||||
<%= f.hidden_field :u_table_id, value: @table.id %>
|
||||
<input class="btn btn-primary pull-right" name="commit" type="submit" value="<%= t("save") %>">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
|
@ -1,33 +1,33 @@
|
|||
<table class="table main-list">
|
||||
<thead>
|
||||
<tr class="sort-header">
|
||||
<% @table_fields.each do |f| %>
|
||||
<%= thead(f) %>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @mind_maps.each do |mindmap| %>
|
||||
<tr id="mindmap_<%= mindmap.id.to_s %>">
|
||||
<td>
|
||||
<a href="<%= admin_mind_map_path(mindmap) %>"><%= mindmap.title %></a>
|
||||
<div class="quick-edit">
|
||||
<ul class="nav nav-pills">
|
||||
<li><a href="<%= edit_admin_mind_map_path(mindmap) %>"><%= t(:edit) %></a></li>
|
||||
<li><a href="<%= admin_mind_map_path(mindmap) %>" class="delete text-error" data-method="delete" data-confirm="Are you sure?"><%= t(:delete_) %></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<%= mindmap.created_at.strftime("%Y-%m-%d") %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<%=
|
||||
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 class="table main-list">
|
||||
<thead>
|
||||
<tr class="sort-header">
|
||||
<% @table_fields.each do |f| %>
|
||||
<%= thead(f) %>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @mind_maps.each do |mindmap| %>
|
||||
<tr id="mindmap_<%= mindmap.id.to_s %>">
|
||||
<td>
|
||||
<a href="<%= admin_mind_map_path(mindmap) %>"><%= mindmap.title %></a>
|
||||
<div class="quick-edit">
|
||||
<ul class="nav nav-pills">
|
||||
<li><a href="<%= edit_admin_mind_map_path(mindmap) %>"><%= t(:edit) %></a></li>
|
||||
<li><a href="<%= admin_mind_map_path(mindmap) %>" class="delete text-error" data-method="delete" data-confirm="Are you sure?"><%= t(:delete_) %></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<%= mindmap.created_at.strftime("%Y-%m-%d") %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<%=
|
||||
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
|
||||
%>
|
||||
|
|
@ -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 %>
|
||||
<script type="module">
|
||||
import '/assets/mind_map/utils/custom.overrides.js'
|
||||
import '/assets/mind_map/jsmind/plugins/jsmind.draggable-node.js'
|
||||
import { initJsmind, getJsmindData } from '/assets/mind_map/utils/custom.main.js'
|
||||
import { INITIAL_MIND } from '/assets/mind_map/utils/custom.config.js'
|
||||
|
||||
// 操控心智圖是否可編輯
|
||||
// Control whether the mind map is editable
|
||||
let isEditable = true
|
||||
|
||||
// 心智圖實例
|
||||
// Mind map instance
|
||||
let jm
|
||||
|
||||
// 心智圖初始數據
|
||||
// Initial mind map data
|
||||
let mind = {
|
||||
meta: {},
|
||||
format: 'node_array',
|
||||
data: <%= raw @mind_map.mind_map_data.to_json %>
|
||||
}
|
||||
|
||||
// 心智圖自訂選項(可參考 jsmind 官方文檔)
|
||||
// Custom options for the mind map (refer to the jsmind official documentation)
|
||||
const options = {
|
||||
container: 'jsmind_container',
|
||||
editable: isEditable,
|
||||
theme: 'primary',
|
||||
mode: 'full',
|
||||
tableUID: '<%= @table.uid %>',
|
||||
text: {
|
||||
addNode: "<%= t("universal_table.add_node") %>",
|
||||
deleteNode: "<%= t("universal_table.delete_node") %>",
|
||||
strokeColor: "<%= t("universal_table.stroke_color") %>",
|
||||
bgColor: "<%= t("universal_table.bg_color") %>",
|
||||
textColor: "<%= t("universal_table.text_color") %>"
|
||||
},
|
||||
view: {
|
||||
engine: 'svg',
|
||||
draggable: true,
|
||||
node_overflow: 'wrap',
|
||||
},
|
||||
shortcut: {
|
||||
mapping: {
|
||||
// 避免與 Toolbar 按下 Enter 事件衝突
|
||||
// Avoid conflicts with the Enter key event in the Toolbar
|
||||
addbrother: 2048 + 13,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化心智圖並掛載實例
|
||||
// Initialize the mind map and attach the instance
|
||||
jm = initJsmind(mind, options, isEditable)
|
||||
|
||||
// 儲存當前數據
|
||||
// Save the current data
|
||||
// document.getElementById('save_mind_map').addEventListener('click', (e) => {
|
||||
// e.preventDefault();
|
||||
// e.stopPropagation();
|
||||
// let data = getJsmindData(jm);
|
||||
// console.log(data);
|
||||
// })
|
||||
|
||||
// 調整可編輯狀態
|
||||
// Toggle the editable state
|
||||
document.getElementById('toggle_editable').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
isEditable = !isEditable;
|
||||
e.target.innerHTML = isEditable ? '<%= t("universal_table.disable_editing") %>' : '<%= t("universal_table.enable_editing") %>';
|
||||
|
||||
mind = getJsmindData(jm);
|
||||
jm = initJsmind(mind, options, isEditable);
|
||||
return false;
|
||||
})
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const form = document.getElementById("mind_map_form");
|
||||
const hiddenField = document.getElementById("mind_map_data_field");
|
||||
form.addEventListener("submit", function (e) {
|
||||
const mindMapData = getJsmindData(jm);
|
||||
console.log(mindMapData);
|
||||
hiddenField.value = JSON.stringify(mindMapData.data);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<% 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 %>
|
||||
<script type="module">
|
||||
import '/assets/mind_map/utils/custom.overrides.js'
|
||||
import '/assets/mind_map/jsmind/plugins/jsmind.draggable-node.js'
|
||||
import { initJsmind, getJsmindData } from '/assets/mind_map/utils/custom.main.js'
|
||||
import { INITIAL_MIND } from '/assets/mind_map/utils/custom.config.js'
|
||||
|
||||
// 操控心智圖是否可編輯
|
||||
// Control whether the mind map is editable
|
||||
let isEditable = true
|
||||
|
||||
// 心智圖實例
|
||||
// Mind map instance
|
||||
let jm
|
||||
|
||||
// 心智圖初始數據
|
||||
// Initial mind map data
|
||||
let mind = {
|
||||
meta: {},
|
||||
format: 'node_array',
|
||||
data: <%= raw @mind_map.mind_map_data.to_json %>
|
||||
}
|
||||
|
||||
// 心智圖自訂選項(可參考 jsmind 官方文檔)
|
||||
// Custom options for the mind map (refer to the jsmind official documentation)
|
||||
const options = {
|
||||
container: 'jsmind_container',
|
||||
editable: isEditable,
|
||||
theme: 'primary',
|
||||
mode: 'full',
|
||||
tableUID: '<%= @table.uid %>',
|
||||
text: {
|
||||
addNode: "<%= t("universal_table.add_node") %>",
|
||||
deleteNode: "<%= t("universal_table.delete_node") %>",
|
||||
strokeColor: "<%= t("universal_table.stroke_color") %>",
|
||||
bgColor: "<%= t("universal_table.bg_color") %>",
|
||||
textColor: "<%= t("universal_table.text_color") %>"
|
||||
},
|
||||
view: {
|
||||
engine: 'svg',
|
||||
draggable: true,
|
||||
node_overflow: 'wrap',
|
||||
},
|
||||
shortcut: {
|
||||
mapping: {
|
||||
// 避免與 Toolbar 按下 Enter 事件衝突
|
||||
// Avoid conflicts with the Enter key event in the Toolbar
|
||||
addbrother: 2048 + 13,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化心智圖並掛載實例
|
||||
// Initialize the mind map and attach the instance
|
||||
jm = initJsmind(mind, options, isEditable)
|
||||
|
||||
// 儲存當前數據
|
||||
// Save the current data
|
||||
// document.getElementById('save_mind_map').addEventListener('click', (e) => {
|
||||
// e.preventDefault();
|
||||
// e.stopPropagation();
|
||||
// let data = getJsmindData(jm);
|
||||
// console.log(data);
|
||||
// })
|
||||
|
||||
// 調整可編輯狀態
|
||||
// Toggle the editable state
|
||||
document.getElementById('toggle_editable').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
isEditable = !isEditable;
|
||||
e.target.innerHTML = isEditable ? '<%= t("universal_table.disable_editing") %>' : '<%= t("universal_table.enable_editing") %>';
|
||||
|
||||
mind = getJsmindData(jm);
|
||||
jm = initJsmind(mind, options, isEditable);
|
||||
return false;
|
||||
})
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const form = document.getElementById("mind_map_form");
|
||||
const hiddenField = document.getElementById("mind_map_data_field");
|
||||
form.addEventListener("submit", function (e) {
|
||||
const mindMapData = getJsmindData(jm);
|
||||
console.log(mindMapData);
|
||||
hiddenField.value = JSON.stringify(mindMapData.data);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<% content_for :page_specific_javascript do %>
|
||||
<%= javascript_include_tag "lib/jquery.form" %>
|
||||
<% end %>
|
||||
<div id="index_table">
|
||||
<%= render 'index'%>
|
||||
<% content_for :page_specific_javascript do %>
|
||||
<%= javascript_include_tag "lib/jquery.form" %>
|
||||
<% end %>
|
||||
<div id="index_table">
|
||||
<%= render 'index'%>
|
||||
</div>
|
||||
|
|
@ -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 %>
|
||||
<script type="module">
|
||||
import '/assets/mind_map/utils/custom.overrides.js'
|
||||
import '/assets/mind_map/jsmind/plugins/jsmind.draggable-node.js'
|
||||
import { initJsmind, getJsmindData } from '/assets/mind_map/utils/custom.main.js'
|
||||
import { INITIAL_MIND } from '/assets/mind_map/utils/custom.config.js'
|
||||
|
||||
// 操控心智圖是否可編輯
|
||||
// Control whether the mind map is editable
|
||||
let isEditable = true
|
||||
|
||||
// 心智圖實例
|
||||
// Mind map instance
|
||||
let jm
|
||||
|
||||
// 心智圖初始數據
|
||||
// Initial mind map data
|
||||
let mind = INITIAL_MIND
|
||||
|
||||
// 心智圖自訂選項(可參考 jsmind 官方文檔)
|
||||
// Custom options for the mind map (refer to the jsmind official documentation)
|
||||
const options = {
|
||||
container: 'jsmind_container',
|
||||
editable: isEditable,
|
||||
theme: 'primary',
|
||||
mode: 'full',
|
||||
tableUID: '<%= @table.uid %>',
|
||||
text: {
|
||||
addNode: "<%= t("universal_table.add_node") %>",
|
||||
deleteNode: "<%= t("universal_table.delete_node") %>",
|
||||
strokeColor: "<%= t("universal_table.stroke_color") %>",
|
||||
bgColor: "<%= t("universal_table.bg_color") %>",
|
||||
textColor: "<%= t("universal_table.text_color") %>"
|
||||
},
|
||||
view: {
|
||||
engine: 'svg',
|
||||
draggable: true,
|
||||
node_overflow: 'wrap',
|
||||
},
|
||||
shortcut: {
|
||||
mapping: {
|
||||
// 避免與 Toolbar 按下 Enter 事件衝突
|
||||
// Avoid conflicts with the Enter key event in the Toolbar
|
||||
addbrother: 2048 + 13,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化心智圖並掛載實例
|
||||
// Initialize the mind map and attach the instance
|
||||
jm = initJsmind(mind, options, isEditable)
|
||||
|
||||
// 儲存當前數據
|
||||
// Save the current data
|
||||
// document.getElementById('save_mind_map').addEventListener('click', (e) => {
|
||||
// e.preventDefault();
|
||||
// e.stopPropagation();
|
||||
// let data = getJsmindData(jm);
|
||||
// console.log(data);
|
||||
// })
|
||||
|
||||
// 調整可編輯狀態
|
||||
// Toggle the editable state
|
||||
document.getElementById('toggle_editable').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
isEditable = !isEditable;
|
||||
e.target.innerHTML = isEditable ? '<%= t("universal_table.disable_editing") %>' : '<%= t("universal_table.enable_editing") %>';
|
||||
|
||||
mind = getJsmindData(jm);
|
||||
jm = initJsmind(mind, options, isEditable);
|
||||
return false;
|
||||
})
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const form = document.getElementById("mind_map_form");
|
||||
const hiddenField = document.getElementById("mind_map_data_field");
|
||||
form.addEventListener("submit", function (e) {
|
||||
const mindMapData = getJsmindData(jm);
|
||||
console.log(mindMapData);
|
||||
hiddenField.value = JSON.stringify(mindMapData.data);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<% 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 %>
|
||||
<script type="module">
|
||||
import '/assets/mind_map/utils/custom.overrides.js'
|
||||
import '/assets/mind_map/jsmind/plugins/jsmind.draggable-node.js'
|
||||
import { initJsmind, getJsmindData } from '/assets/mind_map/utils/custom.main.js'
|
||||
import { INITIAL_MIND } from '/assets/mind_map/utils/custom.config.js'
|
||||
|
||||
// 操控心智圖是否可編輯
|
||||
// Control whether the mind map is editable
|
||||
let isEditable = true
|
||||
|
||||
// 心智圖實例
|
||||
// Mind map instance
|
||||
let jm
|
||||
|
||||
// 心智圖初始數據
|
||||
// Initial mind map data
|
||||
let mind = INITIAL_MIND
|
||||
|
||||
// 心智圖自訂選項(可參考 jsmind 官方文檔)
|
||||
// Custom options for the mind map (refer to the jsmind official documentation)
|
||||
const options = {
|
||||
container: 'jsmind_container',
|
||||
editable: isEditable,
|
||||
theme: 'primary',
|
||||
mode: 'full',
|
||||
tableUID: '<%= @table.uid %>',
|
||||
text: {
|
||||
addNode: "<%= t("universal_table.add_node") %>",
|
||||
deleteNode: "<%= t("universal_table.delete_node") %>",
|
||||
strokeColor: "<%= t("universal_table.stroke_color") %>",
|
||||
bgColor: "<%= t("universal_table.bg_color") %>",
|
||||
textColor: "<%= t("universal_table.text_color") %>"
|
||||
},
|
||||
view: {
|
||||
engine: 'svg',
|
||||
draggable: true,
|
||||
node_overflow: 'wrap',
|
||||
},
|
||||
shortcut: {
|
||||
mapping: {
|
||||
// 避免與 Toolbar 按下 Enter 事件衝突
|
||||
// Avoid conflicts with the Enter key event in the Toolbar
|
||||
addbrother: 2048 + 13,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化心智圖並掛載實例
|
||||
// Initialize the mind map and attach the instance
|
||||
jm = initJsmind(mind, options, isEditable)
|
||||
|
||||
// 儲存當前數據
|
||||
// Save the current data
|
||||
// document.getElementById('save_mind_map').addEventListener('click', (e) => {
|
||||
// e.preventDefault();
|
||||
// e.stopPropagation();
|
||||
// let data = getJsmindData(jm);
|
||||
// console.log(data);
|
||||
// })
|
||||
|
||||
// 調整可編輯狀態
|
||||
// Toggle the editable state
|
||||
document.getElementById('toggle_editable').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
isEditable = !isEditable;
|
||||
e.target.innerHTML = isEditable ? '<%= t("universal_table.disable_editing") %>' : '<%= t("universal_table.enable_editing") %>';
|
||||
|
||||
mind = getJsmindData(jm);
|
||||
jm = initJsmind(mind, options, isEditable);
|
||||
return false;
|
||||
})
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const form = document.getElementById("mind_map_form");
|
||||
const hiddenField = document.getElementById("mind_map_data_field");
|
||||
form.addEventListener("submit", function (e) {
|
||||
const mindMapData = getJsmindData(jm);
|
||||
console.log(mindMapData);
|
||||
hiddenField.value = JSON.stringify(mindMapData.data);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,102 +1,102 @@
|
|||
<% if !defined?(i) %>
|
||||
<div class="attributes">
|
||||
<% end %>
|
||||
<div class="attributes-header clearfix">
|
||||
<a class="btn btn-mini pull-right btn-danger delete" href="#"><i class="icon-trash"></i> Delete</a>
|
||||
<% if defined?(i) %>
|
||||
<%= f.hidden_field :_destroy, :value => "false", :class => "attribute_field_to_delete" %>
|
||||
<h4 class="draggable"><i class="icons-list-2"></i> <%= column.title %></h4>
|
||||
<% else %>
|
||||
<h4 class="draggable"><i class="icons-list-2"></i> ColumnXX</h4>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="attributes-body">
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for="key_0">Key</label>
|
||||
<div class="controls">
|
||||
<%= f.text_field :key, :autocomplete => "off", :'data-type' => 'key' %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for="">Title</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<div class="tab-content">
|
||||
<% @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}") %>
|
||||
<div class="tab-pane fade in <%= active %>" id="<%= id %>">
|
||||
<%= f.fields_for :title_translations do |f| %>
|
||||
<%= f.text_field locale, :value => column.title_translations[locale] %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<% @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 %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for="">Display in index</label>
|
||||
<div class="controls">
|
||||
<label class="radio inline">
|
||||
<%= f.radio_button :display_in_index, "true" %>Yes
|
||||
</label>
|
||||
<label class="radio inline">
|
||||
<%= f.radio_button :display_in_index, "false" %>No
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for=""><%= t('universal_table.default_ordered_field') %></label>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<%= f.check_box :default_ordered_field, class: 'default_ordered_field' %>
|
||||
</div>
|
||||
<div class="order_direction<%= ' hidden' if !f.object.default_ordered_field %>">
|
||||
<%= f.select :order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for="">Type</label>
|
||||
<div class="controls">
|
||||
<% select_values = UTable::FIELD_TYPES.collect{|ft| [ft.capitalize,ft]} %>
|
||||
<%= f.select :type, select_values, {}, {class: "type-selector"} %>
|
||||
<span class="link_to_show <%= (!defined?(i) || column.type == "text" || column.type == "integer") ? "" : "hide" %>">
|
||||
<label class="checkbox inline attributes-checkbox ">
|
||||
<%= f.check_box :is_link_to_show %> Link to show
|
||||
</label>
|
||||
<label class="checkbox inline attributes-checkbox">
|
||||
<%= f.check_box :make_categorizable %> Categorizable
|
||||
</label>
|
||||
<label class="checkbox inline attributes-checkbox ">
|
||||
<%= f.check_box :is_searchable %> Searchable
|
||||
</label>
|
||||
</span>
|
||||
<% select_values = UTable::DATE_FORMATS.collect{|ft| [ft.upcase,ft]} %>
|
||||
<label class="checkbox date_format inline attributes-checkbox <%= column.type == "date" || column.type == "period" ? "" : "hide" %>">
|
||||
Date Format <%= f.select :date_format, select_values%>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<% 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 %>
|
||||
</div>
|
||||
<% if !defined?(i) %>
|
||||
</div>
|
||||
<% if !defined?(i) %>
|
||||
<div class="attributes">
|
||||
<% end %>
|
||||
<div class="attributes-header clearfix">
|
||||
<a class="btn btn-mini pull-right btn-danger delete" href="#"><i class="icon-trash"></i> Delete</a>
|
||||
<% if defined?(i) %>
|
||||
<%= f.hidden_field :_destroy, :value => "false", :class => "attribute_field_to_delete" %>
|
||||
<h4 class="draggable"><i class="icons-list-2"></i> <%= column.title %></h4>
|
||||
<% else %>
|
||||
<h4 class="draggable"><i class="icons-list-2"></i> ColumnXX</h4>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="attributes-body">
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for="key_0">Key</label>
|
||||
<div class="controls">
|
||||
<%= f.text_field :key, :autocomplete => "off", :'data-type' => 'key' %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for="">Title</label>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<div class="tab-content">
|
||||
<% @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}") %>
|
||||
<div class="tab-pane fade in <%= active %>" id="<%= id %>">
|
||||
<%= f.fields_for :title_translations do |f| %>
|
||||
<%= f.text_field locale, :value => column.title_translations[locale] %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<% @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 %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for="">Display in index</label>
|
||||
<div class="controls">
|
||||
<label class="radio inline">
|
||||
<%= f.radio_button :display_in_index, "true" %>Yes
|
||||
</label>
|
||||
<label class="radio inline">
|
||||
<%= f.radio_button :display_in_index, "false" %>No
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for=""><%= t('universal_table.default_ordered_field') %></label>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<%= f.check_box :default_ordered_field, class: 'default_ordered_field' %>
|
||||
</div>
|
||||
<div class="order_direction<%= ' hidden' if !f.object.default_ordered_field %>">
|
||||
<%= f.select :order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for="">Type</label>
|
||||
<div class="controls">
|
||||
<% select_values = UTable::FIELD_TYPES.collect{|ft| [ft.capitalize,ft]} %>
|
||||
<%= f.select :type, select_values, {}, {class: "type-selector"} %>
|
||||
<span class="link_to_show <%= (!defined?(i) || column.type == "text" || column.type == "integer") ? "" : "hide" %>">
|
||||
<label class="checkbox inline attributes-checkbox ">
|
||||
<%= f.check_box :is_link_to_show %> Link to show
|
||||
</label>
|
||||
<label class="checkbox inline attributes-checkbox">
|
||||
<%= f.check_box :make_categorizable %> Categorizable
|
||||
</label>
|
||||
<label class="checkbox inline attributes-checkbox ">
|
||||
<%= f.check_box :is_searchable %> Searchable
|
||||
</label>
|
||||
</span>
|
||||
<% select_values = UTable::DATE_FORMATS.collect{|ft| [ft.upcase,ft]} %>
|
||||
<label class="checkbox date_format inline attributes-checkbox <%= column.type == "date" || column.type == "period" ? "" : "hide" %>">
|
||||
Date Format <%= f.select :date_format, select_values%>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<% 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 %>
|
||||
</div>
|
||||
<% if !defined?(i) %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
<div class="control-group">
|
||||
<%= f.label :date, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<div class="default_picker input-append" style="">
|
||||
<% 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)} %>
|
||||
<span class="add-on clearDate"><i class="icons-cross-3"></i></span>
|
||||
<span class="add-on iconbtn"><i data-date-icon="icons-calendar" data-time-icon="icons-clock" class="icons-calendar"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if !date_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<%= f.label :date, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<div class="default_picker input-append" style="">
|
||||
<% 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)} %>
|
||||
<span class="add-on clearDate"><i class="icons-cross-3"></i></span>
|
||||
<span class="add-on iconbtn"><i data-date-icon="icons-calendar" data-time-icon="icons-clock" class="icons-calendar"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if !date_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,79 +1,79 @@
|
|||
<div id="data-table" class="ut-table">
|
||||
<table class="table main-list">
|
||||
<thead>
|
||||
<tr class="sort-header">
|
||||
<% @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") ? "<b class='icons-arrow-up-3'></b>" : "<b class='icons-arrow-down-4'></b>"
|
||||
klass = field.eql?(:title) ? "span5" : "span2"
|
||||
th_data = "<a href='?sort=#{sort}&order=#{order}'>#{field_text} #{active ? arrow : ""}</a>"
|
||||
%>
|
||||
<th class='<%= klass %> <%= active ? "active" : "" %>'><%= th_data.html_safe %></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sortable">
|
||||
<% can_edit = can_edit_or_delete?(@entries.first.u_table) if !(@entries.first.nil?) %>
|
||||
<% @entries.each do |entry| %>
|
||||
<tr data-id="<%= entry.id %>">
|
||||
<td>
|
||||
<%= number_field_tag nil,entry.sort_number,class: 'sort_number',step: 1 %>
|
||||
</td>
|
||||
<% @columns.each_with_index do |column, index| %>
|
||||
<% ce = entry.column_entries.where(:table_column_id => column.id).first rescue nil %>
|
||||
<% if !ce.nil? %>
|
||||
<td>
|
||||
<% case ce.type %>
|
||||
<% when "text" %>
|
||||
<%= ce.text %>
|
||||
<% when "integer" %>
|
||||
<%= ce.number %>
|
||||
<% when "editor" %>
|
||||
<%= ce.content.html_safe rescue "" %>
|
||||
<% when "image" %>
|
||||
<div class="image-expander">
|
||||
<% if !ce.image.nil? %>
|
||||
<a href="<%= ce.image.url %>" target="_blank"><img src="<%= ce.image.thumb.url %>" class="image-preview" /></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% 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 %>
|
||||
<div class="quick-edit">
|
||||
<ul class="nav nav-pills">
|
||||
<li><a href="<%= admin_universal_table_edit_entry_path(entry) %>"><%= t(:edit) %></a></li>
|
||||
<li><a href="<%= admin_universal_table_delete_entry_path(entry.id) %>" class="delete text-error" data-method="delete" data-confirm="Are you sure?"><%= t(:delete_) %></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
</td>
|
||||
<% else %>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<td>
|
||||
<%= entry.created_at %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div id="data-table" class="ut-table">
|
||||
<table class="table main-list">
|
||||
<thead>
|
||||
<tr class="sort-header">
|
||||
<% @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") ? "<b class='icons-arrow-up-3'></b>" : "<b class='icons-arrow-down-4'></b>"
|
||||
klass = field.eql?(:title) ? "span5" : "span2"
|
||||
th_data = "<a href='?sort=#{sort}&order=#{order}'>#{field_text} #{active ? arrow : ""}</a>"
|
||||
%>
|
||||
<th class='<%= klass %> <%= active ? "active" : "" %>'><%= th_data.html_safe %></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sortable">
|
||||
<% can_edit = can_edit_or_delete?(@entries.first.u_table) if !(@entries.first.nil?) %>
|
||||
<% @entries.each do |entry| %>
|
||||
<tr data-id="<%= entry.id %>">
|
||||
<td>
|
||||
<%= number_field_tag nil,entry.sort_number,class: 'sort_number',step: 1 %>
|
||||
</td>
|
||||
<% @columns.each_with_index do |column, index| %>
|
||||
<% ce = entry.column_entries.where(:table_column_id => column.id).first rescue nil %>
|
||||
<% if !ce.nil? %>
|
||||
<td>
|
||||
<% case ce.type %>
|
||||
<% when "text" %>
|
||||
<%= ce.text %>
|
||||
<% when "integer" %>
|
||||
<%= ce.number %>
|
||||
<% when "editor" %>
|
||||
<%= ce.content.html_safe rescue "" %>
|
||||
<% when "image" %>
|
||||
<div class="image-expander">
|
||||
<% if !ce.image.nil? %>
|
||||
<a href="<%= ce.image.url %>" target="_blank"><img src="<%= ce.image.thumb.url %>" class="image-preview" /></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% 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 %>
|
||||
<div class="quick-edit">
|
||||
<ul class="nav nav-pills">
|
||||
<li><a href="<%= admin_universal_table_edit_entry_path(entry) %>"><%= t(:edit) %></a></li>
|
||||
<li><a href="<%= admin_universal_table_delete_entry_path(entry.id) %>" class="delete text-error" data-method="delete" data-confirm="Are you sure?"><%= t(:delete_) %></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
</td>
|
||||
<% else %>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<td>
|
||||
<%= entry.created_at %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
<!-- Language Tabs -->
|
||||
<ul class="nav nav-pills language-nav">
|
||||
<% @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" : "") %>
|
||||
<li class="<%= active %>">
|
||||
<a data-toggle="tab" href=".<%= id %>"><%= t(locale) %></a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<div class="tab-content language-area">
|
||||
<% @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" : "") %>
|
||||
<div class="<%= id %> tab-pane fade <%= active %>">
|
||||
<div class="control-group">
|
||||
<%= f.label :content, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<%= f.fields_for :content_translations do |f| %>
|
||||
<%= f.text_area locale, :value => editor_field.content_translations[locale.to_s], :class => "ckeditor" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if !editor_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
</div>
|
||||
<!-- Language Tabs -->
|
||||
<ul class="nav nav-pills language-nav">
|
||||
<% @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" : "") %>
|
||||
<li class="<%= active %>">
|
||||
<a data-toggle="tab" href=".<%= id %>"><%= t(locale) %></a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<div class="tab-content language-area">
|
||||
<% @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" : "") %>
|
||||
<div class="<%= id %> tab-pane fade <%= active %>">
|
||||
<div class="control-group">
|
||||
<%= f.label :content, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<%= f.fields_for :content_translations do |f| %>
|
||||
<%= f.text_area locale, :value => editor_field.content_translations[locale.to_s], :class => "ckeditor" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if !editor_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
<style type="text/css">
|
||||
#s2id_autogen1{
|
||||
width: 500px !important;
|
||||
}
|
||||
#s2id_autogen2 {
|
||||
width: 500px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="input-area">
|
||||
<div class="control-group">
|
||||
<label class="control-label"><%= t("universal_table.hashtags") %></label>
|
||||
<div class="controls">
|
||||
<input id="universal_table_tags" name="table_tags" />
|
||||
</div>
|
||||
</div>
|
||||
<%
|
||||
tbData = @entry.get_related_entries.map{|tb| {id: tb.id.to_s, text: tb.column_entries.first.text}}
|
||||
%>
|
||||
<div class="control-group">
|
||||
<label class="control-label"><%= t("universal_table.related_entries") %></label>
|
||||
<div class="controls">
|
||||
<%= f.text_field :related_entries, :class => "select2", data: { value: tbData } %>
|
||||
</div>
|
||||
</div>
|
||||
<% @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 %>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="<%= admin_universal_table_path(@table) %>" class="btn">View Entries</a>
|
||||
<input type="submit" value="Submit" class="btn btn-primary" />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$("#universal_table_tags").select2({
|
||||
tags: true,
|
||||
multiple: true,
|
||||
data : <%= raw(TableTag.where(:u_table_id => @table.id).map { |tag| { text: tag.title.html_safe, id: tag.id.to_s } }.to_json) %>,
|
||||
createSearchChoice: function(term, data) {
|
||||
if (!data.length)
|
||||
return { id: term, text: "#" + term.trim().toLowerCase() };
|
||||
}
|
||||
});
|
||||
$("#universal_table_tags").val(<%= raw(@entry.table_tags. collect { |tag| tag.id.to_s }) %>).trigger("change");
|
||||
|
||||
const preselectedData = $("#table_entry_related_entries").data('value');
|
||||
var select2Options = {
|
||||
maximumSelectionSize: 10,
|
||||
minimumResultsForSearch: Infinity,
|
||||
multiple: true,
|
||||
minimumInputLength: 2,
|
||||
width: '100%',
|
||||
placeholder: "<%= t("universal_table.search_entries") %>",
|
||||
ajax: {
|
||||
type: 'get',
|
||||
url: "/admin/universal_tables/get_entries?uid=<%= @entry.u_table.uid rescue @table.uid %>",
|
||||
allowClear: false,
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function (term) {
|
||||
return { q: term };
|
||||
},
|
||||
results: function(data, page) {
|
||||
return {
|
||||
results: data
|
||||
};
|
||||
},
|
||||
cache: false
|
||||
},
|
||||
formatResult: function(i) {
|
||||
return '<div>' + i.text + '</div>';
|
||||
},
|
||||
formatSelection: function(i) {
|
||||
return '<div>' + i.text + '</div>';
|
||||
},
|
||||
escapeMarkup: function(m) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
$("#table_entry_related_entries").select2(select2Options);
|
||||
$("#table_entry_related_entries").select2('data', preselectedData);
|
||||
<% 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 %>
|
||||
<style type="text/css">
|
||||
#s2id_autogen1{
|
||||
width: 500px !important;
|
||||
}
|
||||
#s2id_autogen2 {
|
||||
width: 500px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="input-area">
|
||||
<div class="control-group">
|
||||
<label class="control-label"><%= t("universal_table.hashtags") %></label>
|
||||
<div class="controls">
|
||||
<input id="universal_table_tags" name="table_tags" />
|
||||
</div>
|
||||
</div>
|
||||
<%
|
||||
tbData = @entry.get_related_entries.map{|tb| {id: tb.id.to_s, text: tb.column_entries.first.text}}
|
||||
%>
|
||||
<div class="control-group">
|
||||
<label class="control-label"><%= t("universal_table.related_entries") %></label>
|
||||
<div class="controls">
|
||||
<%= f.text_field :related_entries, :class => "select2", data: { value: tbData } %>
|
||||
</div>
|
||||
</div>
|
||||
<% @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 %>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="<%= admin_universal_table_path(@table) %>" class="btn">View Entries</a>
|
||||
<input type="submit" value="Submit" class="btn btn-primary" />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$("#universal_table_tags").select2({
|
||||
tags: true,
|
||||
multiple: true,
|
||||
data : <%= raw(TableTag.where(:u_table_id => @table.id).map { |tag| { text: tag.title.html_safe, id: tag.id.to_s } }.to_json) %>,
|
||||
createSearchChoice: function(term, data) {
|
||||
if (!data.length)
|
||||
return { id: term, text: "#" + term.trim().toLowerCase() };
|
||||
}
|
||||
});
|
||||
$("#universal_table_tags").val(<%= raw(@entry.table_tags. collect { |tag| tag.id.to_s }) %>).trigger("change");
|
||||
|
||||
const preselectedData = $("#table_entry_related_entries").data('value');
|
||||
var select2Options = {
|
||||
maximumSelectionSize: 10,
|
||||
minimumResultsForSearch: Infinity,
|
||||
multiple: true,
|
||||
minimumInputLength: 2,
|
||||
width: '100%',
|
||||
placeholder: "<%= t("universal_table.search_entries") %>",
|
||||
ajax: {
|
||||
type: 'get',
|
||||
url: "/admin/universal_tables/get_entries?uid=<%= @entry.u_table.uid rescue @table.uid %>",
|
||||
allowClear: false,
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function (term) {
|
||||
return { q: term };
|
||||
},
|
||||
results: function(data, page) {
|
||||
return {
|
||||
results: data
|
||||
};
|
||||
},
|
||||
cache: false
|
||||
},
|
||||
formatResult: function(i) {
|
||||
return '<div>' + i.text + '</div>';
|
||||
},
|
||||
formatSelection: function(i) {
|
||||
return '<div>' + i.text + '</div>';
|
||||
},
|
||||
escapeMarkup: function(m) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
$("#table_entry_related_entries").select2(select2Options);
|
||||
$("#table_entry_related_entries").select2('data', preselectedData);
|
||||
</script>
|
||||
|
|
@ -1,35 +1,35 @@
|
|||
<div class="entry-suggestion">
|
||||
<strong><%= entry.column_entries.first.try(:text).to_s %></strong>
|
||||
|
||||
<% entry.column_entries.each do |ce| %>
|
||||
<% if ce.type == "file" %>
|
||||
<ul class="column_entry_files">
|
||||
<% 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/') %>
|
||||
<div class="voice-player">
|
||||
<span class="voice-title"><%= file_title %></span>
|
||||
<a class="voice-player" data-content="<%= file.file.url %>" href="#" title="<%= file_title %>">
|
||||
<i class="fa fa-play" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<% else %>
|
||||
<li class="column_entry_file">
|
||||
<a class="column_entry_file_link" href="<%= link %>" title="<%= file_title %>" target="_blank">
|
||||
<%= file_title %>
|
||||
</a>
|
||||
<span class="file_size">(<%= size %>)</span>
|
||||
<span class="view_count">
|
||||
<i class="fa fa-eye" title="<%= t("universal_table.downloaded_times") %>"></i>
|
||||
<span class="view-count"><%= file.download_count %></span>
|
||||
</span>
|
||||
</li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="entry-suggestion">
|
||||
<strong><%= entry.column_entries.first.try(:text).to_s %></strong>
|
||||
|
||||
<% entry.column_entries.each do |ce| %>
|
||||
<% if ce.type == "file" %>
|
||||
<ul class="column_entry_files">
|
||||
<% 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/') %>
|
||||
<div class="voice-player">
|
||||
<span class="voice-title"><%= file_title %></span>
|
||||
<a class="voice-player" data-content="<%= file.file.url %>" href="#" title="<%= file_title %>">
|
||||
<i class="fa fa-play" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<% else %>
|
||||
<li class="column_entry_file">
|
||||
<a class="column_entry_file_link" href="<%= link %>" title="<%= file_title %>" target="_blank">
|
||||
<%= file_title %>
|
||||
</a>
|
||||
<span class="file_size">(<%= size %>)</span>
|
||||
<span class="view_count">
|
||||
<i class="fa fa-eye" title="<%= t("universal_table.downloaded_times") %>"></i>
|
||||
<span class="view-count"><%= file.download_count %></span>
|
||||
</span>
|
||||
</li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,289 +1,289 @@
|
|||
<% # encoding: utf-8 %>
|
||||
<% content_for :page_specific_css do %>
|
||||
<style type="text/css">
|
||||
.sort-order-icon{
|
||||
font-size: 25px;
|
||||
cursor: move;
|
||||
}
|
||||
</style>
|
||||
<% 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 %>
|
||||
<style>
|
||||
#fileupload {
|
||||
position: relative;
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
height: 254px;
|
||||
}
|
||||
#fileupload #dropzone.drop {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 2px dashed #0088CC;
|
||||
border-radius: 10px;
|
||||
color: #0088CC;
|
||||
background-color: #FFFFFF;
|
||||
z-index: 0;
|
||||
}
|
||||
#fileupload #dropzone {
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
font-size: 3em;
|
||||
font-family: 'Raleway';
|
||||
line-height: 1.2em;
|
||||
color: #e4e4e4;
|
||||
}
|
||||
#fileupload #dropzone.in {
|
||||
opacity: .7;
|
||||
z-index: 2;
|
||||
border-color: #faa732;
|
||||
color: #faa732;
|
||||
}
|
||||
</style>
|
||||
<div class="control-group">
|
||||
<%= f.label :text, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<p class="add-btn">
|
||||
<%= hidden_field_tag 'column_entry_file_field_count', file_field.column_entry_files.count %>
|
||||
<a id="add_file" class="trigger btn btn-small btn-primary"><i class="icons-plus"></i> <%= t(:add) %></a>
|
||||
</p>
|
||||
<hr>
|
||||
<!-- Add -->
|
||||
<div class="add-target" id="add-target"></div>
|
||||
<!-- Exist -->
|
||||
<% if file_field && !file_field.column_entry_files.blank? %>
|
||||
<div class="exist plugin-sortable">
|
||||
<% 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 %>
|
||||
<hr>
|
||||
</div>
|
||||
<% end %>
|
||||
<div id="fileupload" ondrop="dropHandler(event);" ondragover="dragOverHandler(event);" ondragleave="(function(){$('#dropzone').removeClass('in');})()">
|
||||
<div id="dropzone" class="drop">
|
||||
<div data-icons=""></div>
|
||||
<%=t("universal_table.drag_file_to_here")%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<% if !file_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% content_for :page_specific_javascript do %>
|
||||
<script>
|
||||
if (!FileReader.prototype.readAsBinaryString) {
|
||||
console.log('readAsBinaryString definition not found');
|
||||
|
||||
FileReader.prototype.readAsBinaryString = function (fileData) {
|
||||
var binary = '';
|
||||
var pk = this;
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = function (e) {
|
||||
var bytes = new Uint8Array(reader.result);
|
||||
var length = bytes.byteLength;
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
var a = bytes[i];
|
||||
|
||||
var b = String.fromCharCode(a)
|
||||
binary += b;
|
||||
}
|
||||
|
||||
pk.content = binary;
|
||||
$(pk).trigger('onload');
|
||||
}
|
||||
|
||||
reader.readAsArrayBuffer(fileData);
|
||||
}
|
||||
}
|
||||
function FileListItems (files) {
|
||||
var b;
|
||||
try{
|
||||
b = new DataTransfer();
|
||||
}catch(e){
|
||||
if(window.dataTransfer){
|
||||
b = window.dataTransfer;
|
||||
}else{
|
||||
if(typeof(ClipboardEvent) == "undefined"){ //IE
|
||||
b = new DataTransfer();
|
||||
}else{
|
||||
b = new ClipboardEvent("").clipboardData;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(b.items){
|
||||
b.items.clear();
|
||||
}else{
|
||||
if(b.files.length != 0 ){
|
||||
var files_length = b.files.length;
|
||||
for(var i = files_length - 1;i >= 0;i = i - 1){
|
||||
delete b.files[i];
|
||||
}
|
||||
}
|
||||
if(b.files.length != 0){
|
||||
return files
|
||||
}
|
||||
}
|
||||
for (var i = 0, len = files.length; i<len; i++){
|
||||
if(b.items){
|
||||
b.items.add(files[i])
|
||||
}else{
|
||||
b.files[i] = files[i];
|
||||
}
|
||||
}
|
||||
return b.files
|
||||
}
|
||||
function change_files_to_file_field(file_field,files){
|
||||
var fileupload = $(file_field).parents(".fileupload");
|
||||
if(fileupload.length > 0){
|
||||
fileupload.find(".fileupload-preview").text(files[0].name);
|
||||
}
|
||||
var files_list = new FileListItems(files)
|
||||
try{
|
||||
$(file_field)[0].files = files_list;
|
||||
}catch(e){console.log(e)}
|
||||
if($(file_field)[0].files.length == 0){ //Change failed
|
||||
var file_field_values = [];
|
||||
var file_reader = new FileReader();
|
||||
$("[name=\""+$(file_field)[0].name+"\"][type=\"hidden\"]").remove();
|
||||
var hidden_input = $("<input type=\"hidden\" name=\""+$(file_field)[0].name+"\">");
|
||||
var hidden_input_values = [];
|
||||
$(file_field).after(hidden_input);
|
||||
var files_list_length = files_list.length;
|
||||
for(var i = 0; i < files_list_length; i++){
|
||||
var file = files_list[i];
|
||||
$(file_field)[0].files[i] = file;
|
||||
file_reader.readAsBinaryString(files_list[i]);
|
||||
file_reader.onload = (function(hidden_input,file,i,files_list_length) {
|
||||
return function(e) {
|
||||
var file_info = {};
|
||||
file_info["name"] = file.name;
|
||||
file_info["type"] = file.type;
|
||||
if (file_reader.result)
|
||||
file_reader.content = file_reader.result;
|
||||
file_info["content"] = e ? e.target.result : file_reader.content;
|
||||
if(Array.isArray(hidden_input_values)){
|
||||
hidden_input_values.push(file_info);
|
||||
}
|
||||
if(i == files_list_length - 1){
|
||||
if(hidden_input_values.length == 1){
|
||||
hidden_input_values = hidden_input_values[0];
|
||||
}
|
||||
hidden_input.val(JSON.stringify(hidden_input_values));
|
||||
}
|
||||
};})(hidden_input,file,i,files_list_length);
|
||||
file_field_values.push("C:\\fakepath\\" + files_list[i].name);
|
||||
}
|
||||
Object.defineProperty($(file_field)[0].files, "length", {
|
||||
// only returns odd die sides
|
||||
get: function () {
|
||||
var length = 0;
|
||||
while(this[length]){
|
||||
length++;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
});
|
||||
Object.defineProperty($(file_field)[0], "value", {
|
||||
// only returns odd die sides
|
||||
get: function () {
|
||||
return (this.getAttribute('value') ? this.getAttribute('value') : "");
|
||||
},
|
||||
set: function(value) {
|
||||
this.setAttribute('value',value);
|
||||
}
|
||||
});
|
||||
$(file_field)[0].value = file_field_values.join(", ");
|
||||
}
|
||||
}
|
||||
function dragOverHandler(ev) {
|
||||
document.activeElement.blur();
|
||||
$(ev.target).addClass("in");
|
||||
// Prevent default behavior (Prevent file from being opened)
|
||||
ev.preventDefault();
|
||||
}
|
||||
function dropHandler(ev) {
|
||||
window.ev = ev;
|
||||
window.dataTransfer = ev.dataTransfer;
|
||||
// Prevent default behavior (Prevent file from being opened)
|
||||
ev.preventDefault();
|
||||
var files = [];
|
||||
if (ev.dataTransfer.items) {
|
||||
// Use DataTransferItemList interface to access the file(s)
|
||||
for (var i = 0; i < ev.dataTransfer.items.length; i++) {
|
||||
// If dropped items aren't files, reject them
|
||||
if (ev.dataTransfer.items[i].kind === 'file') {
|
||||
var file = ev.dataTransfer.items[i].getAsFile();
|
||||
files.push(file)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use DataTransfer interface to access the file(s)
|
||||
for (var i = 0; i < ev.dataTransfer.files.length; i++) {
|
||||
var file = ev.dataTransfer.files[i];
|
||||
files.push(file)
|
||||
}
|
||||
}
|
||||
files.forEach(function(file){
|
||||
var single_file = [file];
|
||||
var file_field = add_file_field();
|
||||
change_files_to_file_field(file_field,single_file);
|
||||
})
|
||||
window.files = files;
|
||||
var target = ev.target ? ev.target : ev.srcElement;
|
||||
$(target).removeClass("in");
|
||||
}
|
||||
function add_file_field(){
|
||||
var self = $('#add_file');
|
||||
var new_id = $(self).prev().attr('value');
|
||||
var old_id = new RegExp("new_column_entry_files", "g");
|
||||
var on = $('.language-nav li.active').index();
|
||||
var le = $('#add-target').children('.start-line').length;
|
||||
$(self).prev().attr('value', parseInt(new_id) + 1);
|
||||
$('#add-target').prepend(("<%= escape_javascript(add_attribute 'form_file', f, :column_entry_files) %>").replace(old_id, new_id).replace("new_column_entry_file_sort_order_XXX", parseInt(new_id) + 1));
|
||||
var file_field = $('#add-target').find("*").eq(0).find("[type=\"file\"]");
|
||||
$('#add-target').children('.start-line').eq(le).children('.input-append').find('.tab-content').each(function() {
|
||||
$(self).children('.tab-pane').eq(on).addClass('in active').siblings().removeClass('in active');
|
||||
});
|
||||
formTip();
|
||||
return file_field;
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$(".plugin-sortable").sortable({
|
||||
update : function(event, ui){
|
||||
var existingfiles = $(".exist.plugin-sortable div.fileupload")
|
||||
existingfiles.each(function(i, file){
|
||||
$(file).find("input.file-sort-number-field").val(existingfiles.length - i);
|
||||
})
|
||||
}
|
||||
});
|
||||
$('.main-forms .add-on').tooltip();
|
||||
$(document).on('click', '#add_file', add_file_field);
|
||||
$(document).on('click', '.delete_file', function(){
|
||||
$(this).parents('.input-prepend').remove();
|
||||
});
|
||||
$(document).on('click',"[type='file']",function(){
|
||||
$("[name=\""+$(this).attr("name")+"\"][type=\"hiiden\"]").remove();
|
||||
});
|
||||
$(document).on('click', '.remove_existing_record', function(){
|
||||
if(confirm("<%= I18n.t(:sure?)%>")){
|
||||
$(this).children('.should_destroy').attr('value', 1);
|
||||
$(this).parents('.start-line').hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<% # encoding: utf-8 %>
|
||||
<% content_for :page_specific_css do %>
|
||||
<style type="text/css">
|
||||
.sort-order-icon{
|
||||
font-size: 25px;
|
||||
cursor: move;
|
||||
}
|
||||
</style>
|
||||
<% 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 %>
|
||||
<style>
|
||||
#fileupload {
|
||||
position: relative;
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
height: 254px;
|
||||
}
|
||||
#fileupload #dropzone.drop {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 2px dashed #0088CC;
|
||||
border-radius: 10px;
|
||||
color: #0088CC;
|
||||
background-color: #FFFFFF;
|
||||
z-index: 0;
|
||||
}
|
||||
#fileupload #dropzone {
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
font-size: 3em;
|
||||
font-family: 'Raleway';
|
||||
line-height: 1.2em;
|
||||
color: #e4e4e4;
|
||||
}
|
||||
#fileupload #dropzone.in {
|
||||
opacity: .7;
|
||||
z-index: 2;
|
||||
border-color: #faa732;
|
||||
color: #faa732;
|
||||
}
|
||||
</style>
|
||||
<div class="control-group">
|
||||
<%= f.label :text, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<p class="add-btn">
|
||||
<%= hidden_field_tag 'column_entry_file_field_count', file_field.column_entry_files.count %>
|
||||
<a id="add_file" class="trigger btn btn-small btn-primary"><i class="icons-plus"></i> <%= t(:add) %></a>
|
||||
</p>
|
||||
<hr>
|
||||
<!-- Add -->
|
||||
<div class="add-target" id="add-target"></div>
|
||||
<!-- Exist -->
|
||||
<% if file_field && !file_field.column_entry_files.blank? %>
|
||||
<div class="exist plugin-sortable">
|
||||
<% 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 %>
|
||||
<hr>
|
||||
</div>
|
||||
<% end %>
|
||||
<div id="fileupload" ondrop="dropHandler(event);" ondragover="dragOverHandler(event);" ondragleave="(function(){$('#dropzone').removeClass('in');})()">
|
||||
<div id="dropzone" class="drop">
|
||||
<div data-icons=""></div>
|
||||
<%=t("universal_table.drag_file_to_here")%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<% if !file_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% content_for :page_specific_javascript do %>
|
||||
<script>
|
||||
if (!FileReader.prototype.readAsBinaryString) {
|
||||
console.log('readAsBinaryString definition not found');
|
||||
|
||||
FileReader.prototype.readAsBinaryString = function (fileData) {
|
||||
var binary = '';
|
||||
var pk = this;
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = function (e) {
|
||||
var bytes = new Uint8Array(reader.result);
|
||||
var length = bytes.byteLength;
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
var a = bytes[i];
|
||||
|
||||
var b = String.fromCharCode(a)
|
||||
binary += b;
|
||||
}
|
||||
|
||||
pk.content = binary;
|
||||
$(pk).trigger('onload');
|
||||
}
|
||||
|
||||
reader.readAsArrayBuffer(fileData);
|
||||
}
|
||||
}
|
||||
function FileListItems (files) {
|
||||
var b;
|
||||
try{
|
||||
b = new DataTransfer();
|
||||
}catch(e){
|
||||
if(window.dataTransfer){
|
||||
b = window.dataTransfer;
|
||||
}else{
|
||||
if(typeof(ClipboardEvent) == "undefined"){ //IE
|
||||
b = new DataTransfer();
|
||||
}else{
|
||||
b = new ClipboardEvent("").clipboardData;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(b.items){
|
||||
b.items.clear();
|
||||
}else{
|
||||
if(b.files.length != 0 ){
|
||||
var files_length = b.files.length;
|
||||
for(var i = files_length - 1;i >= 0;i = i - 1){
|
||||
delete b.files[i];
|
||||
}
|
||||
}
|
||||
if(b.files.length != 0){
|
||||
return files
|
||||
}
|
||||
}
|
||||
for (var i = 0, len = files.length; i<len; i++){
|
||||
if(b.items){
|
||||
b.items.add(files[i])
|
||||
}else{
|
||||
b.files[i] = files[i];
|
||||
}
|
||||
}
|
||||
return b.files
|
||||
}
|
||||
function change_files_to_file_field(file_field,files){
|
||||
var fileupload = $(file_field).parents(".fileupload");
|
||||
if(fileupload.length > 0){
|
||||
fileupload.find(".fileupload-preview").text(files[0].name);
|
||||
}
|
||||
var files_list = new FileListItems(files)
|
||||
try{
|
||||
$(file_field)[0].files = files_list;
|
||||
}catch(e){console.log(e)}
|
||||
if($(file_field)[0].files.length == 0){ //Change failed
|
||||
var file_field_values = [];
|
||||
var file_reader = new FileReader();
|
||||
$("[name=\""+$(file_field)[0].name+"\"][type=\"hidden\"]").remove();
|
||||
var hidden_input = $("<input type=\"hidden\" name=\""+$(file_field)[0].name+"\">");
|
||||
var hidden_input_values = [];
|
||||
$(file_field).after(hidden_input);
|
||||
var files_list_length = files_list.length;
|
||||
for(var i = 0; i < files_list_length; i++){
|
||||
var file = files_list[i];
|
||||
$(file_field)[0].files[i] = file;
|
||||
file_reader.readAsBinaryString(files_list[i]);
|
||||
file_reader.onload = (function(hidden_input,file,i,files_list_length) {
|
||||
return function(e) {
|
||||
var file_info = {};
|
||||
file_info["name"] = file.name;
|
||||
file_info["type"] = file.type;
|
||||
if (file_reader.result)
|
||||
file_reader.content = file_reader.result;
|
||||
file_info["content"] = e ? e.target.result : file_reader.content;
|
||||
if(Array.isArray(hidden_input_values)){
|
||||
hidden_input_values.push(file_info);
|
||||
}
|
||||
if(i == files_list_length - 1){
|
||||
if(hidden_input_values.length == 1){
|
||||
hidden_input_values = hidden_input_values[0];
|
||||
}
|
||||
hidden_input.val(JSON.stringify(hidden_input_values));
|
||||
}
|
||||
};})(hidden_input,file,i,files_list_length);
|
||||
file_field_values.push("C:\\fakepath\\" + files_list[i].name);
|
||||
}
|
||||
Object.defineProperty($(file_field)[0].files, "length", {
|
||||
// only returns odd die sides
|
||||
get: function () {
|
||||
var length = 0;
|
||||
while(this[length]){
|
||||
length++;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
});
|
||||
Object.defineProperty($(file_field)[0], "value", {
|
||||
// only returns odd die sides
|
||||
get: function () {
|
||||
return (this.getAttribute('value') ? this.getAttribute('value') : "");
|
||||
},
|
||||
set: function(value) {
|
||||
this.setAttribute('value',value);
|
||||
}
|
||||
});
|
||||
$(file_field)[0].value = file_field_values.join(", ");
|
||||
}
|
||||
}
|
||||
function dragOverHandler(ev) {
|
||||
document.activeElement.blur();
|
||||
$(ev.target).addClass("in");
|
||||
// Prevent default behavior (Prevent file from being opened)
|
||||
ev.preventDefault();
|
||||
}
|
||||
function dropHandler(ev) {
|
||||
window.ev = ev;
|
||||
window.dataTransfer = ev.dataTransfer;
|
||||
// Prevent default behavior (Prevent file from being opened)
|
||||
ev.preventDefault();
|
||||
var files = [];
|
||||
if (ev.dataTransfer.items) {
|
||||
// Use DataTransferItemList interface to access the file(s)
|
||||
for (var i = 0; i < ev.dataTransfer.items.length; i++) {
|
||||
// If dropped items aren't files, reject them
|
||||
if (ev.dataTransfer.items[i].kind === 'file') {
|
||||
var file = ev.dataTransfer.items[i].getAsFile();
|
||||
files.push(file)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use DataTransfer interface to access the file(s)
|
||||
for (var i = 0; i < ev.dataTransfer.files.length; i++) {
|
||||
var file = ev.dataTransfer.files[i];
|
||||
files.push(file)
|
||||
}
|
||||
}
|
||||
files.forEach(function(file){
|
||||
var single_file = [file];
|
||||
var file_field = add_file_field();
|
||||
change_files_to_file_field(file_field,single_file);
|
||||
})
|
||||
window.files = files;
|
||||
var target = ev.target ? ev.target : ev.srcElement;
|
||||
$(target).removeClass("in");
|
||||
}
|
||||
function add_file_field(){
|
||||
var self = $('#add_file');
|
||||
var new_id = $(self).prev().attr('value');
|
||||
var old_id = new RegExp("new_column_entry_files", "g");
|
||||
var on = $('.language-nav li.active').index();
|
||||
var le = $('#add-target').children('.start-line').length;
|
||||
$(self).prev().attr('value', parseInt(new_id) + 1);
|
||||
$('#add-target').prepend(("<%= escape_javascript(add_attribute 'form_file', f, :column_entry_files) %>").replace(old_id, new_id).replace("new_column_entry_file_sort_order_XXX", parseInt(new_id) + 1));
|
||||
var file_field = $('#add-target').find("*").eq(0).find("[type=\"file\"]");
|
||||
$('#add-target').children('.start-line').eq(le).children('.input-append').find('.tab-content').each(function() {
|
||||
$(self).children('.tab-pane').eq(on).addClass('in active').siblings().removeClass('in active');
|
||||
});
|
||||
formTip();
|
||||
return file_field;
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$(".plugin-sortable").sortable({
|
||||
update : function(event, ui){
|
||||
var existingfiles = $(".exist.plugin-sortable div.fileupload")
|
||||
existingfiles.each(function(i, file){
|
||||
$(file).find("input.file-sort-number-field").val(existingfiles.length - i);
|
||||
})
|
||||
}
|
||||
});
|
||||
$('.main-forms .add-on').tooltip();
|
||||
$(document).on('click', '#add_file', add_file_field);
|
||||
$(document).on('click', '.delete_file', function(){
|
||||
$(this).parents('.input-prepend').remove();
|
||||
});
|
||||
$(document).on('click',"[type='file']",function(){
|
||||
$("[name=\""+$(this).attr("name")+"\"][type=\"hiiden\"]").remove();
|
||||
});
|
||||
$(document).on('click', '.remove_existing_record', function(){
|
||||
if(confirm("<%= I18n.t(:sure?)%>")){
|
||||
$(this).children('.should_destroy').attr('value', 1);
|
||||
$(this).parents('.start-line').hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<% end %>
|
||||
|
|
@ -1,65 +1,65 @@
|
|||
<% if form_file.new_record? %>
|
||||
<div class="fileupload fileupload-new start-line" data-provides="fileupload">
|
||||
<% else %>
|
||||
<div class="fileupload fileupload-exists start-line" data-provides="fileupload">
|
||||
<i class="icons-list-2 sort-order-icon"></i>
|
||||
<% 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 %>
|
||||
<div class="input-prepend input-append">
|
||||
<label>
|
||||
<span class="add-on btn btn-file" title="<%= t(:file_) %>">
|
||||
<i class="icons-paperclip"></i>
|
||||
<%= f.file_field :file %>
|
||||
</span>
|
||||
<div class="uneditable-input input-medium">
|
||||
<i class="icon-file fileupload-exists"></i>
|
||||
<span class="fileupload-preview"><%= (form_file.new_record? || form_file.file.blank?) ? t(:select_file) : t(:change_file) %></span>
|
||||
</div>
|
||||
</label>
|
||||
<span class="add-on icons-pencil" title="<%= t('file.name') %>"></span>
|
||||
<span class="tab-content">
|
||||
<% @site_in_use_locales.each_with_index do |locale, i| %>
|
||||
<span class="tab-pane fade <%= ( i == 0 ) ? "in active" : '' %> <%= 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 %>
|
||||
</span>
|
||||
<% end %>
|
||||
</span>
|
||||
|
||||
<span class="add-on btn-group btn" title="<%= t('universal_table.show_lang') %>">
|
||||
<i class="icons-earth"></i> <span class="caret"></span>
|
||||
<ul class="dropdown-menu">
|
||||
<% @site_in_use_locales.each do |locale| %>
|
||||
<li>
|
||||
<label class="checkbox">
|
||||
<%= check_box_tag "#{f.object_name}[choose_lang][]", locale, form_file.choose_lang.include?(locale.to_s) %>
|
||||
<%= t(locale.to_s) %>
|
||||
</label>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<%= hidden_field_tag "#{f.object_name}[choose_lang][]", '' %>
|
||||
</span>
|
||||
|
||||
<% if form_file.new_record? %>
|
||||
<span class="delete_file add-on btn" title="<%= t(:delete_) %>">
|
||||
<a class="icon-trash"></a>
|
||||
<%= f.hidden_field :sort_number, :value => "new_column_entry_file_sort_order_XXX", :class => "input-mini" %>
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="remove_existing_record add-on btn" title="<%= t(:remove) %>">
|
||||
<%= f.hidden_field :id %>
|
||||
<%= f.hidden_field :sort_number , :class => "file-sort-number-field" %>
|
||||
<a class=" icon-remove"></a>
|
||||
<%= f.hidden_field :_destroy, :value => nil, :class => 'should_destroy' %>
|
||||
</span>
|
||||
<span class="downloaded_times">Downloaded <b><%= form_file.download_count %></b> time<%= form_file.download_count > 1 ? "s" : "" %>.</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if form_file.new_record? %>
|
||||
<div class="fileupload fileupload-new start-line" data-provides="fileupload">
|
||||
<% else %>
|
||||
<div class="fileupload fileupload-exists start-line" data-provides="fileupload">
|
||||
<i class="icons-list-2 sort-order-icon"></i>
|
||||
<% 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 %>
|
||||
<div class="input-prepend input-append">
|
||||
<label>
|
||||
<span class="add-on btn btn-file" title="<%= t(:file_) %>">
|
||||
<i class="icons-paperclip"></i>
|
||||
<%= f.file_field :file %>
|
||||
</span>
|
||||
<div class="uneditable-input input-medium">
|
||||
<i class="icon-file fileupload-exists"></i>
|
||||
<span class="fileupload-preview"><%= (form_file.new_record? || form_file.file.blank?) ? t(:select_file) : t(:change_file) %></span>
|
||||
</div>
|
||||
</label>
|
||||
<span class="add-on icons-pencil" title="<%= t('file.name') %>"></span>
|
||||
<span class="tab-content">
|
||||
<% @site_in_use_locales.each_with_index do |locale, i| %>
|
||||
<span class="tab-pane fade <%= ( i == 0 ) ? "in active" : '' %> <%= 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 %>
|
||||
</span>
|
||||
<% end %>
|
||||
</span>
|
||||
|
||||
<span class="add-on btn-group btn" title="<%= t('universal_table.show_lang') %>">
|
||||
<i class="icons-earth"></i> <span class="caret"></span>
|
||||
<ul class="dropdown-menu">
|
||||
<% @site_in_use_locales.each do |locale| %>
|
||||
<li>
|
||||
<label class="checkbox">
|
||||
<%= check_box_tag "#{f.object_name}[choose_lang][]", locale, form_file.choose_lang.include?(locale.to_s) %>
|
||||
<%= t(locale.to_s) %>
|
||||
</label>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<%= hidden_field_tag "#{f.object_name}[choose_lang][]", '' %>
|
||||
</span>
|
||||
|
||||
<% if form_file.new_record? %>
|
||||
<span class="delete_file add-on btn" title="<%= t(:delete_) %>">
|
||||
<a class="icon-trash"></a>
|
||||
<%= f.hidden_field :sort_number, :value => "new_column_entry_file_sort_order_XXX", :class => "input-mini" %>
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="remove_existing_record add-on btn" title="<%= t(:remove) %>">
|
||||
<%= f.hidden_field :id %>
|
||||
<%= f.hidden_field :sort_number , :class => "file-sort-number-field" %>
|
||||
<a class=" icon-remove"></a>
|
||||
<%= f.hidden_field :_destroy, :value => nil, :class => 'should_destroy' %>
|
||||
</span>
|
||||
<span class="downloaded_times">Downloaded <b><%= form_file.download_count %></b> time<%= form_file.download_count > 1 ? "s" : "" %>.</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
<div class="control-group">
|
||||
<%= f.label :image, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div class="fileupload fileupload-new clearfix <%= 'fileupload-edit' if image_field.image.file %>" data-provides="fileupload">
|
||||
<div class="fileupload-new thumbnail pull-left">
|
||||
<% if image_field.image.file %>
|
||||
<%= image_tag image_field.image %>
|
||||
<% else %>
|
||||
<img src="http://www.placehold.it/50x50/EFEFEF/AAAAAA" />
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="fileupload-preview fileupload-exists thumbnail pull-left"></div>
|
||||
<span class="btn btn-file">
|
||||
<span class="fileupload-new"><%= t(:select_image) %></span>
|
||||
<span class="fileupload-exists"><%= t(:change) %></span>
|
||||
<%= f.file_field :image %>
|
||||
</span>
|
||||
<a href="#" class="btn fileupload-exists" data-dismiss="fileupload"><%= t(:cancel) %></a>
|
||||
<div class="controls" data-toggle="buttons-checkbox">
|
||||
<label class="checkbox inline btn btn-danger fileupload-remove">
|
||||
<%= f.check_box :remove_image %><%= t(:remove) %>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if !image_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
<div class="control-group">
|
||||
<%= f.label :image, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div class="fileupload fileupload-new clearfix <%= 'fileupload-edit' if image_field.image.file %>" data-provides="fileupload">
|
||||
<div class="fileupload-new thumbnail pull-left">
|
||||
<% if image_field.image.file %>
|
||||
<%= image_tag image_field.image %>
|
||||
<% else %>
|
||||
<img src="http://www.placehold.it/50x50/EFEFEF/AAAAAA" />
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="fileupload-preview fileupload-exists thumbnail pull-left"></div>
|
||||
<span class="btn btn-file">
|
||||
<span class="fileupload-new"><%= t(:select_image) %></span>
|
||||
<span class="fileupload-exists"><%= t(:change) %></span>
|
||||
<%= f.file_field :image %>
|
||||
</span>
|
||||
<a href="#" class="btn fileupload-exists" data-dismiss="fileupload"><%= t(:cancel) %></a>
|
||||
<div class="controls" data-toggle="buttons-checkbox">
|
||||
<label class="checkbox inline btn btn-danger fileupload-remove">
|
||||
<%= f.check_box :remove_image %><%= t(:remove) %>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if !image_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -1,49 +1,49 @@
|
|||
<table class="table main-list">
|
||||
<thead>
|
||||
<tr class="sort-header">
|
||||
<% @table_fields.each do |f| %>
|
||||
<%= thead(f) %>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @tables.each do |table| %>
|
||||
<% can_edit = can_edit_or_delete?(table) %>
|
||||
<tr id="table_<%= table.id.to_s %>">
|
||||
<td>
|
||||
<a href="<%= admin_universal_table_path(table) %>"><%= table.title %></a>
|
||||
<div class="quick-edit">
|
||||
<ul class="nav nav-pills">
|
||||
<% if can_edit %>
|
||||
<li><a href="<%= edit_admin_universal_table_path(table) %>"><%= t(:edit) %></a></li>
|
||||
<li><a href="<%= "/admin/universal_table/#{table.id.to_s}/mind_maps" %>"><%= t("universal_table.mind_map") %></a></li>
|
||||
<% if table.ordered_with_sort_number %>
|
||||
<li><a href="<%= admin_universal_table_edit_sort_path(table) %>"><%= t('universal_table.edit_sort') %></a></li>
|
||||
<% end %>
|
||||
<li><a href="/admin/universal_tables/<%=table.id.to_s%>/export_data?format=xlsx" data-table-id="<%= table.id.to_s %>" class="export-xls"><%= t('universal_table.export_xls') %></a></li>
|
||||
<li><a href="<%= admin_universal_table_path(table) %>" class="delete text-error" data-method="delete" data-confirm="Are you sure?"><%= t(:delete_) %></a></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<%= table.created_at.strftime("%Y-%m-%d") %>
|
||||
</td>
|
||||
<td>
|
||||
<%= table.table_entries.count %>
|
||||
</td>
|
||||
<td>
|
||||
<% if can_edit %>
|
||||
<form action="/admin/universal_tables/import_data_from_excel" method="post" enctype="multipart/form-data" class="import_from_excel_form">
|
||||
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
|
||||
<input type="file" name="import_data" />
|
||||
<button class="btn btn-primary btn-small"><i class="icons-upload"></i></button>
|
||||
<input type="hidden" name="universal_table_id" value="<%= table.id.to_s %>" />
|
||||
<a href="<%= admin_universal_table_export_structure_path(table, :format => "xlsx") %>"><%= t("universal_table.export_structure") %></a>
|
||||
</form>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
<table class="table main-list">
|
||||
<thead>
|
||||
<tr class="sort-header">
|
||||
<% @table_fields.each do |f| %>
|
||||
<%= thead(f) %>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @tables.each do |table| %>
|
||||
<% can_edit = can_edit_or_delete?(table) %>
|
||||
<tr id="table_<%= table.id.to_s %>">
|
||||
<td>
|
||||
<a href="<%= admin_universal_table_path(table) %>"><%= table.title %></a>
|
||||
<div class="quick-edit">
|
||||
<ul class="nav nav-pills">
|
||||
<% if can_edit %>
|
||||
<li><a href="<%= edit_admin_universal_table_path(table) %>"><%= t(:edit) %></a></li>
|
||||
<li><a href="<%= "/admin/universal_table/#{table.id.to_s}/mind_maps" %>"><%= t("universal_table.mind_map") %></a></li>
|
||||
<% if table.ordered_with_sort_number %>
|
||||
<li><a href="<%= admin_universal_table_edit_sort_path(table) %>"><%= t('universal_table.edit_sort') %></a></li>
|
||||
<% end %>
|
||||
<li><a href="/admin/universal_tables/<%=table.id.to_s%>/export_data?format=xlsx" data-table-id="<%= table.id.to_s %>" class="export-xls"><%= t('universal_table.export_xls') %></a></li>
|
||||
<li><a href="<%= admin_universal_table_path(table) %>" class="delete text-error" data-method="delete" data-confirm="Are you sure?"><%= t(:delete_) %></a></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<%= table.created_at.strftime("%Y-%m-%d") %>
|
||||
</td>
|
||||
<td>
|
||||
<%= table.table_entries.count %>
|
||||
</td>
|
||||
<td>
|
||||
<% if can_edit %>
|
||||
<form action="/admin/universal_tables/import_data_from_excel" method="post" enctype="multipart/form-data" class="import_from_excel_form">
|
||||
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
|
||||
<input type="file" name="import_data" />
|
||||
<button class="btn btn-primary btn-small"><i class="icons-upload"></i></button>
|
||||
<input type="hidden" name="universal_table_id" value="<%= table.id.to_s %>" />
|
||||
<a href="<%= admin_universal_table_export_structure_path(table, :format => "xlsx") %>"><%= t("universal_table.export_structure") %></a>
|
||||
</form>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
<div class="control-group">
|
||||
<%= f.label :number, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<%= f.number_field :number, :value => integer_field.number %>
|
||||
</div>
|
||||
<% if column.make_categorizable %>
|
||||
<%= render_unique_number(f,column,i).html_safe %>
|
||||
<script type="text/javascript">
|
||||
$("select#<%= column.key + "_" + i.to_s %>").on("change",function(){
|
||||
var el = $(this),
|
||||
value = el.val(),
|
||||
inputs = el.parent().parent().find("input[type=number]");
|
||||
inputs.val(value);
|
||||
})
|
||||
</script>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
<% if !integer_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
<div class="control-group">
|
||||
<%= f.label :number, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<%= f.number_field :number, :value => integer_field.number %>
|
||||
</div>
|
||||
<% if column.make_categorizable %>
|
||||
<%= render_unique_number(f,column,i).html_safe %>
|
||||
<script type="text/javascript">
|
||||
$("select#<%= column.key + "_" + i.to_s %>").on("change",function(){
|
||||
var el = $(this),
|
||||
value = el.val(),
|
||||
inputs = el.parent().parent().find("input[type=number]");
|
||||
inputs.val(value);
|
||||
})
|
||||
</script>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
<% if !integer_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -1,34 +1,34 @@
|
|||
<div class="control-group ut-control-group">
|
||||
<div class="control-group ut-control-group-col">
|
||||
<%= f.label :period_from, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<div class="default_picker input-append" style="">
|
||||
<% 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" %>
|
||||
<span class="add-on clearDate"><i class="icons-cross-3"></i></span>
|
||||
<span class="add-on iconbtn"><i data-date-icon="icons-calendar" data-time-icon="icons-clock" class="icons-calendar"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group ut-control-group-col ut-control-group-col-right">
|
||||
<%= f.label :period_to, "~", :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<div class="default_picker input-append" style="">
|
||||
<% 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" %>
|
||||
<span class="add-on clearDate"><i class="icons-cross-3"></i></span>
|
||||
<span class="add-on iconbtn"><i data-date-icon="icons-calendar" data-time-icon="icons-clock" class="icons-calendar"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if !period_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
|
||||
<div class="control-group ut-control-group">
|
||||
<div class="control-group ut-control-group-col">
|
||||
<%= f.label :period_from, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<div class="default_picker input-append" style="">
|
||||
<% 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" %>
|
||||
<span class="add-on clearDate"><i class="icons-cross-3"></i></span>
|
||||
<span class="add-on iconbtn"><i data-date-icon="icons-calendar" data-time-icon="icons-clock" class="icons-calendar"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group ut-control-group-col ut-control-group-col-right">
|
||||
<%= f.label :period_to, "~", :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<div class="default_picker input-append" style="">
|
||||
<% 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" %>
|
||||
<span class="add-on clearDate"><i class="icons-cross-3"></i></span>
|
||||
<span class="add-on iconbtn"><i data-date-icon="icons-calendar" data-time-icon="icons-clock" class="icons-calendar"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if !period_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
|
@ -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 %>
|
||||
|
||||
<fieldset class="utable-heading-wrap">
|
||||
<div class="utable-heading-header">
|
||||
<h4><%= t("universal_table.table_name") %></h4>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<div class="tab-content">
|
||||
<% @site_in_use_locales.each do |locale| %>
|
||||
<% active = (locale == @site_in_use_locales.first ? "active in" : "") %>
|
||||
<div class="tab-pane fade <%= active %>" id="table_name_<%= locale.to_s %>">
|
||||
<%= f.fields_for :title_translations do |f| %>
|
||||
<%= f.text_field locale, :placeholder => "Title", :value => @table.title_translations[locale] %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<% @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 %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<div class="utable-heading-header">
|
||||
<h4><%= t("universal_table.default_ordered_field") %></h4>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for=""><%= t('universal_table.created_at') %></label>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<%= f.check_box :ordered_with_created_at, class: 'default_ordered_field ordered_with_created_at' %>
|
||||
</div>
|
||||
<div class="order_direction<%= ' hidden' if !f.object.ordered_with_created_at %>">
|
||||
<%= f.select :created_at_order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for=""><%= t('universal_table.sort_number') %></label>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<%= f.check_box :ordered_with_sort_number, class: 'default_ordered_field' %>
|
||||
</div>
|
||||
<div class="order_direction<%= ' hidden' if !f.object.ordered_with_sort_number %>">
|
||||
<%= f.select :sort_number_order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="utable-content">
|
||||
<div id="attributes-area" class="input-area">
|
||||
<% @table.table_columns.asc(:order).each_with_index do |table_column, index| %>
|
||||
<div class="attributes default ">
|
||||
<%= 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 %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-success add-attributes"><%= t("universal_table.add_column") %></button>
|
||||
<input class="btn btn-primary" name="commit" type="submit" value="<%= t("save") %>">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
var columnArea = $("#attributes-area"),
|
||||
totalColumns = columnArea.find(".attributes.default").length,
|
||||
columnCounter = totalColumns;
|
||||
$("button.add-attributes").on("click",function(){
|
||||
var html = "<%= escape_javascript(add_attribute 'column', f, :table_columns) %>",
|
||||
replaceReg = new RegExp("new_table_columns","g"),
|
||||
idNumber = new RegExp("XXX","g");
|
||||
html = html.replace(replaceReg,columnCounter);
|
||||
html = html.replace("ColumnXX", "Column " + (columnCounter + 1));
|
||||
html = html.replace(idNumber,columnCounter);
|
||||
columnArea.append(html);
|
||||
columnCounter++;
|
||||
})
|
||||
|
||||
$(document.body).on("click","a.delete",function(){
|
||||
if($(this).parent().find(".attribute_field_to_delete").length == 0){
|
||||
$(this).parent().parent().slideUp(function(){
|
||||
$(this).remove();
|
||||
updateOrder();
|
||||
});
|
||||
}else{
|
||||
if(confirm("Are you sure?")){
|
||||
$(this).parent().parent().slideUp(function(){
|
||||
updateOrder();
|
||||
});
|
||||
$(this).parent().find(".attribute_field_to_delete").val("true");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
|
||||
$(document.body).on("change","select.type-selector",function(){
|
||||
var el = $(this),
|
||||
label = el.parent().find("span.link_to_show");
|
||||
if(el.val() == "text" || el.val() == "integer"){
|
||||
label.removeClass("hide");
|
||||
}else{
|
||||
label.addClass("hide");
|
||||
label.find("input[type=checkbox]").prop("checked",false);
|
||||
}
|
||||
label = el.parent().find("label.date_format");
|
||||
if(el.val() == "date" || el.val() == "period"){
|
||||
label.removeClass("hide");
|
||||
}else{
|
||||
label.addClass("hide");
|
||||
}
|
||||
})
|
||||
$(document).on('change','.default_ordered_field',function(){
|
||||
$('.order_direction').addClass('hidden');
|
||||
if ($(this).prop('checked')){
|
||||
$('.default_ordered_field').not(this).prop('checked',false);
|
||||
$(this).parents('.controls').eq(0).find('.order_direction').removeClass('hidden');
|
||||
}
|
||||
});
|
||||
$(document).ready(function(){
|
||||
if ($('.ordered_with_created_at').prop('checked')){
|
||||
$('.default_ordered_field').not($('.ordered_with_created_at')[0]).prop('checked',false);
|
||||
$('.order_direction').not($('.ordered_with_created_at').eq(0).parents('.controls').eq(0).find('.order_direction')[0]).addClass('hidden');
|
||||
}
|
||||
})
|
||||
function key_on_blur() {
|
||||
$('input[data-type=key]').on('blur',function() {
|
||||
var index_this = $(this).parents('.attributes').index()
|
||||
console.log(index_this)
|
||||
var input_this = parseInt($(this).val()) - 1
|
||||
if (input_this > ($('#attributes-area>.attributes').length-1)){
|
||||
input_this = $('#attributes-area>.attributes').length-1
|
||||
}else if (input_this < 0){
|
||||
input_this = 0
|
||||
}
|
||||
if (index_this > input_this){
|
||||
$(this).parents('#attributes-area>.attributes').insertBefore($('#attributes-area>.attributes').eq(input_this))
|
||||
}
|
||||
else if (index_this < input_this){
|
||||
$(this).parents('#attributes-area>.attributes').insertAfter($('#attributes-area>.attributes').eq(input_this))
|
||||
}
|
||||
update_key(this)
|
||||
});
|
||||
}
|
||||
|
||||
function update_key(ele){
|
||||
var ui_child=$(ele).parents('#attributes-area').find('.attributes');
|
||||
for (var i=0;i<ui_child.length;i++){
|
||||
var now_ele = ui_child.eq(i);
|
||||
now_ele.find('input[data-type=key]').val(i+1);
|
||||
}
|
||||
updateOrder();
|
||||
}
|
||||
$('#attributes-area').ready(function(){
|
||||
$("#attributes-area").sortable({
|
||||
update: function( event, ui ) {
|
||||
update_key($(ui.item[0]).find('input[data-type=key]'))
|
||||
}
|
||||
});
|
||||
$("#attributes-area").on('change',key_on_blur)
|
||||
key_on_blur()
|
||||
})
|
||||
var updateOrder = function(){
|
||||
var attributes = $("#attributes-area").find(".attributes:visible");
|
||||
attributes.each(function(i){
|
||||
$(this).find("input.order-hidden-field").val(i);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<% 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 %>
|
||||
|
||||
<fieldset class="utable-heading-wrap">
|
||||
<div class="utable-heading-header">
|
||||
<h4><%= t("universal_table.table_name") %></h4>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<div class="tab-content">
|
||||
<% @site_in_use_locales.each do |locale| %>
|
||||
<% active = (locale == @site_in_use_locales.first ? "active in" : "") %>
|
||||
<div class="tab-pane fade <%= active %>" id="table_name_<%= locale.to_s %>">
|
||||
<%= f.fields_for :title_translations do |f| %>
|
||||
<%= f.text_field locale, :placeholder => "Title", :value => @table.title_translations[locale] %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<% @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 %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<div class="utable-heading-header">
|
||||
<h4><%= t("universal_table.default_ordered_field") %></h4>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for=""><%= t('universal_table.created_at') %></label>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<%= f.check_box :ordered_with_created_at, class: 'default_ordered_field ordered_with_created_at' %>
|
||||
</div>
|
||||
<div class="order_direction<%= ' hidden' if !f.object.ordered_with_created_at %>">
|
||||
<%= f.select :created_at_order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label muted" for=""><%= t('universal_table.sort_number') %></label>
|
||||
<div class="controls">
|
||||
<div>
|
||||
<%= f.check_box :ordered_with_sort_number, class: 'default_ordered_field' %>
|
||||
</div>
|
||||
<div class="order_direction<%= ' hidden' if !f.object.ordered_with_sort_number %>">
|
||||
<%= f.select :sort_number_order_direction,['desc','asc'].map{|v| [t("universal_table.#{v}"),v]} %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="utable-content">
|
||||
<div id="attributes-area" class="input-area">
|
||||
<% @table.table_columns.asc(:order).each_with_index do |table_column, index| %>
|
||||
<div class="attributes default ">
|
||||
<%= 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 %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-success add-attributes"><%= t("universal_table.add_column") %></button>
|
||||
<input class="btn btn-primary" name="commit" type="submit" value="<%= t("save") %>">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
var columnArea = $("#attributes-area"),
|
||||
totalColumns = columnArea.find(".attributes.default").length,
|
||||
columnCounter = totalColumns;
|
||||
$("button.add-attributes").on("click",function(){
|
||||
var html = "<%= escape_javascript(add_attribute 'column', f, :table_columns) %>",
|
||||
replaceReg = new RegExp("new_table_columns","g"),
|
||||
idNumber = new RegExp("XXX","g");
|
||||
html = html.replace(replaceReg,columnCounter);
|
||||
html = html.replace("ColumnXX", "Column " + (columnCounter + 1));
|
||||
html = html.replace(idNumber,columnCounter);
|
||||
columnArea.append(html);
|
||||
columnCounter++;
|
||||
})
|
||||
|
||||
$(document.body).on("click","a.delete",function(){
|
||||
if($(this).parent().find(".attribute_field_to_delete").length == 0){
|
||||
$(this).parent().parent().slideUp(function(){
|
||||
$(this).remove();
|
||||
updateOrder();
|
||||
});
|
||||
}else{
|
||||
if(confirm("Are you sure?")){
|
||||
$(this).parent().parent().slideUp(function(){
|
||||
updateOrder();
|
||||
});
|
||||
$(this).parent().find(".attribute_field_to_delete").val("true");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
|
||||
$(document.body).on("change","select.type-selector",function(){
|
||||
var el = $(this),
|
||||
label = el.parent().find("span.link_to_show");
|
||||
if(el.val() == "text" || el.val() == "integer"){
|
||||
label.removeClass("hide");
|
||||
}else{
|
||||
label.addClass("hide");
|
||||
label.find("input[type=checkbox]").prop("checked",false);
|
||||
}
|
||||
label = el.parent().find("label.date_format");
|
||||
if(el.val() == "date" || el.val() == "period"){
|
||||
label.removeClass("hide");
|
||||
}else{
|
||||
label.addClass("hide");
|
||||
}
|
||||
})
|
||||
$(document).on('change','.default_ordered_field',function(){
|
||||
$('.order_direction').addClass('hidden');
|
||||
if ($(this).prop('checked')){
|
||||
$('.default_ordered_field').not(this).prop('checked',false);
|
||||
$(this).parents('.controls').eq(0).find('.order_direction').removeClass('hidden');
|
||||
}
|
||||
});
|
||||
$(document).ready(function(){
|
||||
if ($('.ordered_with_created_at').prop('checked')){
|
||||
$('.default_ordered_field').not($('.ordered_with_created_at')[0]).prop('checked',false);
|
||||
$('.order_direction').not($('.ordered_with_created_at').eq(0).parents('.controls').eq(0).find('.order_direction')[0]).addClass('hidden');
|
||||
}
|
||||
})
|
||||
function key_on_blur() {
|
||||
$('input[data-type=key]').on('blur',function() {
|
||||
var index_this = $(this).parents('.attributes').index()
|
||||
console.log(index_this)
|
||||
var input_this = parseInt($(this).val()) - 1
|
||||
if (input_this > ($('#attributes-area>.attributes').length-1)){
|
||||
input_this = $('#attributes-area>.attributes').length-1
|
||||
}else if (input_this < 0){
|
||||
input_this = 0
|
||||
}
|
||||
if (index_this > input_this){
|
||||
$(this).parents('#attributes-area>.attributes').insertBefore($('#attributes-area>.attributes').eq(input_this))
|
||||
}
|
||||
else if (index_this < input_this){
|
||||
$(this).parents('#attributes-area>.attributes').insertAfter($('#attributes-area>.attributes').eq(input_this))
|
||||
}
|
||||
update_key(this)
|
||||
});
|
||||
}
|
||||
|
||||
function update_key(ele){
|
||||
var ui_child=$(ele).parents('#attributes-area').find('.attributes');
|
||||
for (var i=0;i<ui_child.length;i++){
|
||||
var now_ele = ui_child.eq(i);
|
||||
now_ele.find('input[data-type=key]').val(i+1);
|
||||
}
|
||||
updateOrder();
|
||||
}
|
||||
$('#attributes-area').ready(function(){
|
||||
$("#attributes-area").sortable({
|
||||
update: function( event, ui ) {
|
||||
update_key($(ui.item[0]).find('input[data-type=key]'))
|
||||
}
|
||||
});
|
||||
$("#attributes-area").on('change',key_on_blur)
|
||||
key_on_blur()
|
||||
})
|
||||
var updateOrder = function(){
|
||||
var attributes = $("#attributes-area").find(".attributes:visible");
|
||||
attributes.each(function(i){
|
||||
$(this).find("input.order-hidden-field").val(i);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,46 +1,46 @@
|
|||
<div class="control-group">
|
||||
<%= f.label :text, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<div class="tab-content">
|
||||
<% @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}" %>
|
||||
<div class="tab-pane fade in <%= active %>" id="<%= id %>">
|
||||
<%= f.fields_for :text_translations do |f| %>
|
||||
<%= f.text_field locale, :value => text_field.text_translations[locale.to_s], :for => locale.to_s %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<% @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 %>
|
||||
</div>
|
||||
</div>
|
||||
<% if column.make_categorizable %>
|
||||
<%= render_unique_texts(f,column,i).html_safe %>
|
||||
<script type="text/javascript">
|
||||
$("select#<%= column.key + "_" + i.to_s %>").on("change",function(){
|
||||
var el = $(this),
|
||||
locales = <%= @site_in_use_locales.to_json.html_safe %>,
|
||||
values = JSON.parse(el.val()),
|
||||
inputs = el.parent().parent().find("input[type=text]");
|
||||
$.each(locales,function(i,locale){
|
||||
var input = inputs.filter("input[for=" + locale + "]");
|
||||
input.val(values[locale]);
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
<% if !text_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
<div class="control-group">
|
||||
<%= f.label :text, column.title, :class => "control-label" %>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<div class="tab-content">
|
||||
<% @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}" %>
|
||||
<div class="tab-pane fade in <%= active %>" id="<%= id %>">
|
||||
<%= f.fields_for :text_translations do |f| %>
|
||||
<%= f.text_field locale, :value => text_field.text_translations[locale.to_s], :for => locale.to_s %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<% @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 %>
|
||||
</div>
|
||||
</div>
|
||||
<% if column.make_categorizable %>
|
||||
<%= render_unique_texts(f,column,i).html_safe %>
|
||||
<script type="text/javascript">
|
||||
$("select#<%= column.key + "_" + i.to_s %>").on("change",function(){
|
||||
var el = $(this),
|
||||
locales = <%= @site_in_use_locales.to_json.html_safe %>,
|
||||
values = JSON.parse(el.val()),
|
||||
inputs = el.parent().parent().find("input[type=text]");
|
||||
$.each(locales,function(i,locale){
|
||||
var input = inputs.filter("input[for=" + locale + "]");
|
||||
input.val(values[locale]);
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
<% if !text_field.new_record? %>
|
||||
<%= f.hidden_field :id %>
|
||||
<% else %>
|
||||
<%= f.hidden_field :table_column_id, :value => column.id %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -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 %>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<%= form_for @entry, url: "/admin/universal_tables/update_entry", html: {class: "form-horizontal main-forms"} do |f| %>
|
||||
<fieldset>
|
||||
<%= f.hidden_field_tag 'id', f.object.id %>
|
||||
<%= f.hidden_field_tag 'page', params[:page] %>
|
||||
<%= render :partial => "entry_form", :locals => {:f => f} %>
|
||||
</fieldset>
|
||||
<%= form_for @entry, url: "/admin/universal_tables/update_entry", html: {class: "form-horizontal main-forms"} do |f| %>
|
||||
<fieldset>
|
||||
<%= f.hidden_field_tag 'id', f.object.id %>
|
||||
<%= f.hidden_field_tag 'page', params[:page] %>
|
||||
<%= render :partial => "entry_form", :locals => {:f => f} %>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
|
|
@ -1,109 +1,109 @@
|
|||
<% content_for :page_specific_css do %>
|
||||
<%= stylesheet_link_tag "universal_table/universal-table" %>
|
||||
<% end %>
|
||||
<div style="margin-bottom: 1em;">
|
||||
<button type="button" class="btn btn-primary" id="update_sort_button"><%= t('universal_table.manual_update_sort') %></button>
|
||||
</div>
|
||||
<%= render partial: 'edit_sort' %>
|
||||
<script type="text/javascript">
|
||||
function update_sort(){
|
||||
var ids = $.map($('#sortable>tr'),function(v){return $(v).data('id')});
|
||||
$.ajax({
|
||||
url: "<%= admin_universal_table_update_sort_path(@table) %>",
|
||||
type: 'POST',
|
||||
dataType: 'text',
|
||||
data: {ids: ids},
|
||||
success: function(data){
|
||||
$('#data-table').replaceWith(data);
|
||||
sortable();
|
||||
}
|
||||
});
|
||||
}
|
||||
function sortable(){
|
||||
$( "#sortable" ).sortable({
|
||||
update: function( event, ui ) {
|
||||
update_sort();
|
||||
}
|
||||
});
|
||||
$('.sort_number').change(function(){
|
||||
var new_sort_number = parseFloat($(this).val());
|
||||
var min_number = $('.sort_number').length;
|
||||
var max_number = 0;
|
||||
var pool = $('.sort_number').not(this);
|
||||
var same_order = pool.filter(function(){
|
||||
var tmp_sort = parseFloat($(this).val());
|
||||
if (tmp_sort<min_number){
|
||||
min_number = tmp_sort;
|
||||
}
|
||||
if (tmp_sort>max_number){
|
||||
max_number = tmp_sort;
|
||||
}
|
||||
return tmp_sort==new_sort_number
|
||||
});
|
||||
var tmp_same_order = null;
|
||||
if (same_order.length>0){
|
||||
tmp_same_order = same_order.eq(0);
|
||||
tmp_same_order.parents('tr').eq(0).before($(this).parents('tr').eq(0));
|
||||
}else{
|
||||
//ex. 1 2 3 5,insert 4
|
||||
if (parseInt(pool.eq(0).val())<parseInt(pool.eq(-1).val())){ //asc
|
||||
$.each(pool,function(){
|
||||
var tmp_sort = parseFloat($(this).val());
|
||||
if (new_sort_number>max_number){
|
||||
if (tmp_sort<=new_sort_number){
|
||||
tmp_same_order = $(this);
|
||||
}
|
||||
}else{
|
||||
if (tmp_sort>=new_sort_number){
|
||||
tmp_same_order = $(this);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (new_sort_number>max_number){
|
||||
tmp_same_order.parents('tr').eq(0).after($(this).parents('tr').eq(0));
|
||||
}else{
|
||||
tmp_same_order.parents('tr').eq(0).before($(this).parents('tr').eq(0));
|
||||
}
|
||||
}else{ //desc
|
||||
//ex. 5 3 2 1,insert 4
|
||||
$.each(pool,function(){
|
||||
var tmp_sort = parseFloat($(this).val());
|
||||
if (new_sort_number<min_number){
|
||||
if (tmp_sort>=new_sort_number){
|
||||
tmp_same_order = $(this);
|
||||
}
|
||||
}else if (new_sort_number>max_number){
|
||||
if (tmp_sort<=new_sort_number){
|
||||
tmp_same_order = $(this);
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
if (tmp_sort<=new_sort_number){
|
||||
tmp_same_order = $(this);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (new_sort_number<min_number){
|
||||
tmp_same_order.parents('tr').eq(0).after($(this).parents('tr').eq(0));
|
||||
}else{
|
||||
tmp_same_order.parents('tr').eq(0).before($(this).parents('tr').eq(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
update_sort();
|
||||
});
|
||||
}
|
||||
$(document).ready(function(){
|
||||
$('#update_sort_button').click(update_sort);
|
||||
sortable();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<% content_for :page_specific_css do %>
|
||||
<%= stylesheet_link_tag "universal_table/universal-table" %>
|
||||
<% end %>
|
||||
<div style="margin-bottom: 1em;">
|
||||
<button type="button" class="btn btn-primary" id="update_sort_button"><%= t('universal_table.manual_update_sort') %></button>
|
||||
</div>
|
||||
<%= render partial: 'edit_sort' %>
|
||||
<script type="text/javascript">
|
||||
function update_sort(){
|
||||
var ids = $.map($('#sortable>tr'),function(v){return $(v).data('id')});
|
||||
$.ajax({
|
||||
url: "<%= admin_universal_table_update_sort_path(@table) %>",
|
||||
type: 'POST',
|
||||
dataType: 'text',
|
||||
data: {ids: ids},
|
||||
success: function(data){
|
||||
$('#data-table').replaceWith(data);
|
||||
sortable();
|
||||
}
|
||||
});
|
||||
}
|
||||
function sortable(){
|
||||
$( "#sortable" ).sortable({
|
||||
update: function( event, ui ) {
|
||||
update_sort();
|
||||
}
|
||||
});
|
||||
$('.sort_number').change(function(){
|
||||
var new_sort_number = parseFloat($(this).val());
|
||||
var min_number = $('.sort_number').length;
|
||||
var max_number = 0;
|
||||
var pool = $('.sort_number').not(this);
|
||||
var same_order = pool.filter(function(){
|
||||
var tmp_sort = parseFloat($(this).val());
|
||||
if (tmp_sort<min_number){
|
||||
min_number = tmp_sort;
|
||||
}
|
||||
if (tmp_sort>max_number){
|
||||
max_number = tmp_sort;
|
||||
}
|
||||
return tmp_sort==new_sort_number
|
||||
});
|
||||
var tmp_same_order = null;
|
||||
if (same_order.length>0){
|
||||
tmp_same_order = same_order.eq(0);
|
||||
tmp_same_order.parents('tr').eq(0).before($(this).parents('tr').eq(0));
|
||||
}else{
|
||||
//ex. 1 2 3 5,insert 4
|
||||
if (parseInt(pool.eq(0).val())<parseInt(pool.eq(-1).val())){ //asc
|
||||
$.each(pool,function(){
|
||||
var tmp_sort = parseFloat($(this).val());
|
||||
if (new_sort_number>max_number){
|
||||
if (tmp_sort<=new_sort_number){
|
||||
tmp_same_order = $(this);
|
||||
}
|
||||
}else{
|
||||
if (tmp_sort>=new_sort_number){
|
||||
tmp_same_order = $(this);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (new_sort_number>max_number){
|
||||
tmp_same_order.parents('tr').eq(0).after($(this).parents('tr').eq(0));
|
||||
}else{
|
||||
tmp_same_order.parents('tr').eq(0).before($(this).parents('tr').eq(0));
|
||||
}
|
||||
}else{ //desc
|
||||
//ex. 5 3 2 1,insert 4
|
||||
$.each(pool,function(){
|
||||
var tmp_sort = parseFloat($(this).val());
|
||||
if (new_sort_number<min_number){
|
||||
if (tmp_sort>=new_sort_number){
|
||||
tmp_same_order = $(this);
|
||||
}
|
||||
}else if (new_sort_number>max_number){
|
||||
if (tmp_sort<=new_sort_number){
|
||||
tmp_same_order = $(this);
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
if (tmp_sort<=new_sort_number){
|
||||
tmp_same_order = $(this);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (new_sort_number<min_number){
|
||||
tmp_same_order.parents('tr').eq(0).after($(this).parents('tr').eq(0));
|
||||
}else{
|
||||
tmp_same_order.parents('tr').eq(0).before($(this).parents('tr').eq(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
update_sort();
|
||||
});
|
||||
}
|
||||
$(document).ready(function(){
|
||||
$('#update_sort_button').click(update_sort);
|
||||
sortable();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,96 +1,96 @@
|
|||
<% content_for :page_specific_javascript do %>
|
||||
<%= javascript_include_tag "lib/jquery.form" %>
|
||||
<% end %>
|
||||
<div id="index_table">
|
||||
<%= render 'index'%>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$("form.import_from_excel_form").on("submit",function(){
|
||||
var form = this;
|
||||
if($(this).find("input[type=file]").val() != ""){
|
||||
$(this).ajaxSubmit({
|
||||
dataType : "json",
|
||||
success : function(data){
|
||||
if(data.success){
|
||||
alert("Import successfull.")
|
||||
$("tr#table_" + data.id + " td:eq(2)").text(data.count);
|
||||
}else{
|
||||
alert(data.msg);
|
||||
}
|
||||
form.reset();
|
||||
}
|
||||
})
|
||||
}
|
||||
return false;
|
||||
})
|
||||
</script>
|
||||
<div id="downloadModal" data-backdrop="static" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="downloadModalLabel" aria-hidden="true">
|
||||
<div class="modal-header">
|
||||
<h3 id="downloadModalLabel">Download</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="wait-zone" style="text-align: center;">
|
||||
Please wait while we prepare your download. This may take a while.
|
||||
<br />
|
||||
<img src="/assets/spin.gif" />
|
||||
</p>
|
||||
<p id="link-zone" style="display: none; text-align: center;">
|
||||
Please click the link below to download.
|
||||
<br />
|
||||
<a href="" id="download-link" target="_blank">Download</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" id="modal-close-btn" style="display:none;" data-dismiss="modal" aria-hidden="true">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="/assets/lib/process.manager.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var downloadModal = $("#downloadModal"),
|
||||
checkForThread = null,
|
||||
waitZone = $("#wait-zone"),
|
||||
linkZone = $("#link-zone"),
|
||||
downloadLink = $("a#download-link"),
|
||||
modalBtn = $("#modal-close-btn"),
|
||||
processManager = new ProcessManager();
|
||||
|
||||
$(document).on("click", ".export-xls", function(){
|
||||
var link = $(this).attr("href"),
|
||||
title = null,
|
||||
id = $(this).data("table-id");
|
||||
|
||||
linkZone.hide();
|
||||
waitZone.show();
|
||||
modalBtn.hide();
|
||||
$.ajax({
|
||||
url : link,
|
||||
type : "get",
|
||||
dataType : "json"
|
||||
}).done(function(data){
|
||||
title = data.title;
|
||||
checkForThread = new Process(function(){
|
||||
$.ajax({
|
||||
url : "/admin/universal_tables/checkforthread",
|
||||
type : "get",
|
||||
data : {"utable_id" : id, "utable_title" : title},
|
||||
dataType : "json"
|
||||
}).done(function(data){
|
||||
if(!data.status){
|
||||
downloadLink.attr("href", "/uploads/utable_export/" + id + "/" + title + ".xlsx");
|
||||
waitZone.hide();
|
||||
linkZone.show();
|
||||
modalBtn.show();
|
||||
checkForThread.kill();
|
||||
}
|
||||
})
|
||||
})
|
||||
checkForThread.setTimeInterval(1000);
|
||||
checkForThread.setRepeat(Process.CONSTANTS.REPEAT_INFINITE);
|
||||
processManager.queue(checkForThread);
|
||||
})
|
||||
downloadModal.modal("show");
|
||||
return false;
|
||||
})
|
||||
|
||||
<% content_for :page_specific_javascript do %>
|
||||
<%= javascript_include_tag "lib/jquery.form" %>
|
||||
<% end %>
|
||||
<div id="index_table">
|
||||
<%= render 'index'%>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$("form.import_from_excel_form").on("submit",function(){
|
||||
var form = this;
|
||||
if($(this).find("input[type=file]").val() != ""){
|
||||
$(this).ajaxSubmit({
|
||||
dataType : "json",
|
||||
success : function(data){
|
||||
if(data.success){
|
||||
alert("Import successfull.")
|
||||
$("tr#table_" + data.id + " td:eq(2)").text(data.count);
|
||||
}else{
|
||||
alert(data.msg);
|
||||
}
|
||||
form.reset();
|
||||
}
|
||||
})
|
||||
}
|
||||
return false;
|
||||
})
|
||||
</script>
|
||||
<div id="downloadModal" data-backdrop="static" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="downloadModalLabel" aria-hidden="true">
|
||||
<div class="modal-header">
|
||||
<h3 id="downloadModalLabel">Download</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="wait-zone" style="text-align: center;">
|
||||
Please wait while we prepare your download. This may take a while.
|
||||
<br />
|
||||
<img src="/assets/spin.gif" />
|
||||
</p>
|
||||
<p id="link-zone" style="display: none; text-align: center;">
|
||||
Please click the link below to download.
|
||||
<br />
|
||||
<a href="" id="download-link" target="_blank">Download</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" id="modal-close-btn" style="display:none;" data-dismiss="modal" aria-hidden="true">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="/assets/lib/process.manager.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var downloadModal = $("#downloadModal"),
|
||||
checkForThread = null,
|
||||
waitZone = $("#wait-zone"),
|
||||
linkZone = $("#link-zone"),
|
||||
downloadLink = $("a#download-link"),
|
||||
modalBtn = $("#modal-close-btn"),
|
||||
processManager = new ProcessManager();
|
||||
|
||||
$(document).on("click", ".export-xls", function(){
|
||||
var link = $(this).attr("href"),
|
||||
title = null,
|
||||
id = $(this).data("table-id");
|
||||
|
||||
linkZone.hide();
|
||||
waitZone.show();
|
||||
modalBtn.hide();
|
||||
$.ajax({
|
||||
url : link,
|
||||
type : "get",
|
||||
dataType : "json"
|
||||
}).done(function(data){
|
||||
title = data.title;
|
||||
checkForThread = new Process(function(){
|
||||
$.ajax({
|
||||
url : "/admin/universal_tables/checkforthread",
|
||||
type : "get",
|
||||
data : {"utable_id" : id, "utable_title" : title},
|
||||
dataType : "json"
|
||||
}).done(function(data){
|
||||
if(!data.status){
|
||||
downloadLink.attr("href", "/uploads/utable_export/" + id + "/" + title + ".xlsx");
|
||||
waitZone.hide();
|
||||
linkZone.show();
|
||||
modalBtn.show();
|
||||
checkForThread.kill();
|
||||
}
|
||||
})
|
||||
})
|
||||
checkForThread.setTimeInterval(1000);
|
||||
checkForThread.setRepeat(Process.CONSTANTS.REPEAT_INFINITE);
|
||||
processManager.queue(checkForThread);
|
||||
})
|
||||
downloadModal.modal("show");
|
||||
return false;
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
@ -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 %>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<%= form_for @entry, url: "/admin/universal_tables/add_entry", html: {class: "form-horizontal main-forms"} do |f| %>
|
||||
<fieldset>
|
||||
<%= f.hidden_field :u_table_id, :value => @table.id %>
|
||||
<%= render :partial => "entry_form", :locals => {:f => f} %>
|
||||
</fieldset>
|
||||
<%= form_for @entry, url: "/admin/universal_tables/add_entry", html: {class: "form-horizontal main-forms"} do |f| %>
|
||||
<fieldset>
|
||||
<%= f.hidden_field :u_table_id, :value => @table.id %>
|
||||
<%= render :partial => "entry_form", :locals => {:f => f} %>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
|
|
@ -1,209 +1,209 @@
|
|||
<% content_for :page_specific_css do %>
|
||||
<%= stylesheet_link_tag "universal_table/universal-table" %>
|
||||
<% end %>
|
||||
<style>
|
||||
#entry-status{
|
||||
float: right;
|
||||
margin-right: 1em;
|
||||
|
||||
}
|
||||
.toggle_entries[data-status="show"] {
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-family: 'Varela Round';
|
||||
letter-spacing: -.4px;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
background-color: #007AFF;
|
||||
margin-right: 0.5em;
|
||||
&:hover{
|
||||
background-color:#1A73E8;
|
||||
}
|
||||
}
|
||||
.toggle_entries[data-status="hide"] {
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-family: 'Varela Round';
|
||||
letter-spacing: -.4px;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
background-color: #5F6368;
|
||||
margin-right: 0.5em;
|
||||
&:hover{
|
||||
background-color:#4b4e53;
|
||||
}
|
||||
}
|
||||
.hidden_entry{
|
||||
td{
|
||||
background-color: #9eacb5a8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<form class="form-search" action="<%= admin_universal_table_path(@table) %>" method="get">
|
||||
<input type="text" name="q" class="input-large search-query" placeholder="Search from text or editor columns">
|
||||
<button type="submit" class="btn btn-primary">Search</button>
|
||||
<% if params[:q].present? %>
|
||||
<a href="<%= admin_universal_table_path(@table) %>" class="btn btn-info">Reset</a>
|
||||
<% end %>
|
||||
</form>
|
||||
|
||||
<div id="data-table" class="ut-table">
|
||||
<table class="table main-list">
|
||||
<thead>
|
||||
<tr class="sort-header">
|
||||
<th width="15">
|
||||
<%= t(:status) %>
|
||||
<div class="status hide" id="entry-status">
|
||||
<a href="" class="toggle_entries" data-status="hide"><%= t(:hide) %></a>
|
||||
<a href="" class="toggle_entries" data-status="show"><%= t(:show) %></a>
|
||||
</div>
|
||||
</th>
|
||||
<% @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") ? "<b class='icons-arrow-up-3'></b>" : "<b class='icons-arrow-down-4'></b>"
|
||||
klass = field.eql?(:title) ? "span5" : "span2"
|
||||
th_data = "<a href='?sort=#{sort}&order=#{order}'>#{field} #{active ? arrow : ""}</a>"
|
||||
%>
|
||||
<th class='<%= klass %> <%= active ? "active" : "" %>'><%= th_data.html_safe %></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% can_edit = can_edit_or_delete?(@table) %>
|
||||
<% @entries.each do |entry| %>
|
||||
<tr id="tr_entry_<%= entry.id.to_s %>" class="<%= entry.is_hidden? ? 'hidden_entry' : '' %>">
|
||||
<td><input class="hide-toggle" type="checkbox" value="<%= entry.id.to_s %>" /></td>
|
||||
<% @columns.each_with_index do |column, index| %>
|
||||
<% ce = entry.column_entries.where(:table_column_id => column.id).first rescue nil %>
|
||||
<td>
|
||||
<% if !ce.nil? %>
|
||||
<% case ce.type %>
|
||||
<% when "text" %>
|
||||
<%= ce.text %>
|
||||
<% when "integer" %>
|
||||
<%= ce.number %>
|
||||
<% when "editor" %>
|
||||
<%= ce.content.html_safe rescue "" %>
|
||||
<% when "image" %>
|
||||
<div class="image-expander">
|
||||
<% if !ce.image.nil? %>
|
||||
<a href="<%= ce.image.url %>" target="_blank"><img src="<%= ce.image.thumb.url %>" class="image-preview" /></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% 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 %>
|
||||
<ol>
|
||||
<% 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 %>
|
||||
<a class="voice-player" data-content="<%= entry_file.file.url %>" href="" title="播放讀音"><i class="fa fa-play" aria-hidden="true"></i></a>
|
||||
<% else %>
|
||||
<li><%= link_to entry_file.get_file_title, entry_file.file.url, target: "_blank" %></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ol>
|
||||
<% end %>
|
||||
<% else %>
|
||||
|
||||
<% end %>
|
||||
<% if index == 0 && can_edit %>
|
||||
<div class="quick-edit">
|
||||
<ul class="nav nav-pills">
|
||||
<li><a href="<%= admin_universal_table_edit_entry_path(:universal_table_id=> entry.to_param, :page => params[:page]) %>"><%= t(:edit) %></a></li>
|
||||
<li><a href="<%= admin_universal_table_delete_entry_path(entry.id) %>" class="delete text-error" data-method="delete" data-confirm="Are you sure?"><%= t(:delete_) %></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="bottomnav clearfix">
|
||||
<%= content_tag :div, paginate(@entries), class: "pagination pagination-centered" %>
|
||||
<div class="action pull-right">
|
||||
<a href="<%= admin_universal_table_new_entry_path(@table) %>" class="btn btn-primary" role="button" data-toggle="modal">Add Entry</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(".action").after($("#entry-status"));
|
||||
let audio;
|
||||
$(".voice-player").on("click", function(){
|
||||
let status = $(this).attr('status');
|
||||
if (audio) {
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
}
|
||||
|
||||
if (status == 'playing') {
|
||||
$(this).attr('status', '');
|
||||
$(this).find('i').removeClass('fa-pause');
|
||||
$(this).find('i').addClass('fa-play');
|
||||
} else {
|
||||
let mp3_url = $(this).attr('data-content');
|
||||
let _this = $(this);
|
||||
audio = new Audio(mp3_url);
|
||||
audio.play();
|
||||
audio.onended = function() {
|
||||
_this.attr('status', '');
|
||||
_this.find('i').removeClass('fa-pause');
|
||||
_this.find('i').addClass('fa-play');
|
||||
};
|
||||
$(this).find('i').removeClass('fa-play');
|
||||
$(this).find('i').addClass('fa-pause');
|
||||
$(this).attr('status', 'playing');
|
||||
}
|
||||
return false;
|
||||
})
|
||||
$(".hide-toggle").on("click", function(){
|
||||
var count = $(".hide-toggle:checked").length;
|
||||
if(count > 0){
|
||||
$("#entry-status").removeClass("hide")
|
||||
}else{
|
||||
$("#entry-status").addClass("hide");
|
||||
|
||||
}
|
||||
})
|
||||
$(".toggle_entries").on("click", function(){
|
||||
let checkedValues = $('.hide-toggle:checked').map(function() {
|
||||
return $(this).val();
|
||||
}).get();
|
||||
let status = $(this).data("status");
|
||||
$.ajax({
|
||||
url: "/admin/universal_tables/toggle_entries",
|
||||
method: "post",
|
||||
data: {"ids": checkedValues, "status": status},
|
||||
type: "json"
|
||||
}).done(function(){
|
||||
if(status == "hide"){
|
||||
$('.hide-toggle:checked').parents("tr").addClass("hidden_entry");
|
||||
}else{
|
||||
$('.hide-toggle:checked').parents("tr").removeClass("hidden_entry");
|
||||
}
|
||||
})
|
||||
return false;
|
||||
})
|
||||
<% content_for :page_specific_css do %>
|
||||
<%= stylesheet_link_tag "universal_table/universal-table" %>
|
||||
<% end %>
|
||||
<style>
|
||||
#entry-status{
|
||||
float: right;
|
||||
margin-right: 1em;
|
||||
|
||||
}
|
||||
.toggle_entries[data-status="show"] {
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-family: 'Varela Round';
|
||||
letter-spacing: -.4px;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
background-color: #007AFF;
|
||||
margin-right: 0.5em;
|
||||
&:hover{
|
||||
background-color:#1A73E8;
|
||||
}
|
||||
}
|
||||
.toggle_entries[data-status="hide"] {
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-family: 'Varela Round';
|
||||
letter-spacing: -.4px;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
background-color: #5F6368;
|
||||
margin-right: 0.5em;
|
||||
&:hover{
|
||||
background-color:#4b4e53;
|
||||
}
|
||||
}
|
||||
.hidden_entry{
|
||||
td{
|
||||
background-color: #9eacb5a8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<form class="form-search" action="<%= admin_universal_table_path(@table) %>" method="get">
|
||||
<input type="text" name="q" class="input-large search-query" placeholder="Search from text or editor columns">
|
||||
<button type="submit" class="btn btn-primary">Search</button>
|
||||
<% if params[:q].present? %>
|
||||
<a href="<%= admin_universal_table_path(@table) %>" class="btn btn-info">Reset</a>
|
||||
<% end %>
|
||||
</form>
|
||||
|
||||
<div id="data-table" class="ut-table">
|
||||
<table class="table main-list">
|
||||
<thead>
|
||||
<tr class="sort-header">
|
||||
<th width="15">
|
||||
<%= t(:status) %>
|
||||
<div class="status hide" id="entry-status">
|
||||
<a href="" class="toggle_entries" data-status="hide"><%= t(:hide) %></a>
|
||||
<a href="" class="toggle_entries" data-status="show"><%= t(:show) %></a>
|
||||
</div>
|
||||
</th>
|
||||
<% @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") ? "<b class='icons-arrow-up-3'></b>" : "<b class='icons-arrow-down-4'></b>"
|
||||
klass = field.eql?(:title) ? "span5" : "span2"
|
||||
th_data = "<a href='?sort=#{sort}&order=#{order}'>#{field} #{active ? arrow : ""}</a>"
|
||||
%>
|
||||
<th class='<%= klass %> <%= active ? "active" : "" %>'><%= th_data.html_safe %></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% can_edit = can_edit_or_delete?(@table) %>
|
||||
<% @entries.each do |entry| %>
|
||||
<tr id="tr_entry_<%= entry.id.to_s %>" class="<%= entry.is_hidden? ? 'hidden_entry' : '' %>">
|
||||
<td><input class="hide-toggle" type="checkbox" value="<%= entry.id.to_s %>" /></td>
|
||||
<% @columns.each_with_index do |column, index| %>
|
||||
<% ce = entry.column_entries.where(:table_column_id => column.id).first rescue nil %>
|
||||
<td>
|
||||
<% if !ce.nil? %>
|
||||
<% case ce.type %>
|
||||
<% when "text" %>
|
||||
<%= ce.text %>
|
||||
<% when "integer" %>
|
||||
<%= ce.number %>
|
||||
<% when "editor" %>
|
||||
<%= ce.content.html_safe rescue "" %>
|
||||
<% when "image" %>
|
||||
<div class="image-expander">
|
||||
<% if !ce.image.nil? %>
|
||||
<a href="<%= ce.image.url %>" target="_blank"><img src="<%= ce.image.thumb.url %>" class="image-preview" /></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% 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 %>
|
||||
<ol>
|
||||
<% 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 %>
|
||||
<a class="voice-player" data-content="<%= entry_file.file.url %>" href="" title="播放讀音"><i class="fa fa-play" aria-hidden="true"></i></a>
|
||||
<% else %>
|
||||
<li><%= link_to entry_file.get_file_title, entry_file.file.url, target: "_blank" %></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ol>
|
||||
<% end %>
|
||||
<% else %>
|
||||
|
||||
<% end %>
|
||||
<% if index == 0 && can_edit %>
|
||||
<div class="quick-edit">
|
||||
<ul class="nav nav-pills">
|
||||
<li><a href="<%= admin_universal_table_edit_entry_path(:universal_table_id=> entry.to_param, :page => params[:page]) %>"><%= t(:edit) %></a></li>
|
||||
<li><a href="<%= admin_universal_table_delete_entry_path(entry.id) %>" class="delete text-error" data-method="delete" data-confirm="Are you sure?"><%= t(:delete_) %></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="bottomnav clearfix">
|
||||
<%= content_tag :div, paginate(@entries), class: "pagination pagination-centered" %>
|
||||
<div class="action pull-right">
|
||||
<a href="<%= admin_universal_table_new_entry_path(@table) %>" class="btn btn-primary" role="button" data-toggle="modal">Add Entry</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(".action").after($("#entry-status"));
|
||||
let audio;
|
||||
$(".voice-player").on("click", function(){
|
||||
let status = $(this).attr('status');
|
||||
if (audio) {
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
}
|
||||
|
||||
if (status == 'playing') {
|
||||
$(this).attr('status', '');
|
||||
$(this).find('i').removeClass('fa-pause');
|
||||
$(this).find('i').addClass('fa-play');
|
||||
} else {
|
||||
let mp3_url = $(this).attr('data-content');
|
||||
let _this = $(this);
|
||||
audio = new Audio(mp3_url);
|
||||
audio.play();
|
||||
audio.onended = function() {
|
||||
_this.attr('status', '');
|
||||
_this.find('i').removeClass('fa-pause');
|
||||
_this.find('i').addClass('fa-play');
|
||||
};
|
||||
$(this).find('i').removeClass('fa-play');
|
||||
$(this).find('i').addClass('fa-pause');
|
||||
$(this).attr('status', 'playing');
|
||||
}
|
||||
return false;
|
||||
})
|
||||
$(".hide-toggle").on("click", function(){
|
||||
var count = $(".hide-toggle:checked").length;
|
||||
if(count > 0){
|
||||
$("#entry-status").removeClass("hide")
|
||||
}else{
|
||||
$("#entry-status").addClass("hide");
|
||||
|
||||
}
|
||||
})
|
||||
$(".toggle_entries").on("click", function(){
|
||||
let checkedValues = $('.hide-toggle:checked').map(function() {
|
||||
return $(this).val();
|
||||
}).get();
|
||||
let status = $(this).data("status");
|
||||
$.ajax({
|
||||
url: "/admin/universal_tables/toggle_entries",
|
||||
method: "post",
|
||||
data: {"ids": checkedValues, "status": status},
|
||||
type: "json"
|
||||
}).done(function(){
|
||||
if(status == "hide"){
|
||||
$('.hide-toggle:checked').parents("tr").addClass("hidden_entry");
|
||||
}else{
|
||||
$('.hide-toggle:checked').parents("tr").removeClass("hidden_entry");
|
||||
}
|
||||
})
|
||||
return false;
|
||||
})
|
||||
</script>
|
||||
|
|
@ -1,147 +1,147 @@
|
|||
<% if @ext == 'pdf' %>
|
||||
<%= render partial: 'archives/viewer' %>
|
||||
<% else %>
|
||||
<html lang="<%= I18n.locale.to_s%>" style="margin: 0em; padding: 0em; width: 100%; height: 100%; overflow: hidden; background-color: rgb(230, 230, 230);">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=0.1">
|
||||
<title><%=@filename%></title>
|
||||
<%= stylesheet_link_tag "archive/download_file.css" %>
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="display: none;"><%=@filename%></h1>
|
||||
<% if @ext != "png" && @ext != "jpg" && @ext != "bmp" %>
|
||||
<object data="<%=@url%>" height="100%" type="application/<%=@ext%>" width="100%">
|
||||
<iframe height="100%" src="<%=@url%>" title="<%=@filename%>" width="100%"></iframe>
|
||||
<img alt="<%=@filename%>" src="<%=@url%>">
|
||||
</object>
|
||||
<% else %>
|
||||
<img alt="<%=@filename%>" src="<%=@url%>">
|
||||
<script type="text/javascript">
|
||||
var img = document.getElementsByTagName('img')[0];
|
||||
var width = img.width;
|
||||
var height = img.height;
|
||||
window.innerWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
window.innerHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
|
||||
var window_width = window.innerWidth;
|
||||
var window_height = window.innerHeight;
|
||||
var zoom_in_cursor,zoom_out_cursor;
|
||||
var IE_ver = 11;
|
||||
if(navigator.userAgent.search("MSIE") != -1){
|
||||
IE_ver = Number(navigator.userAgent.split("MSIE")[1].split(";")[0]);
|
||||
}
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "-"+img.height/2+"px";
|
||||
img.style.marginLeft = "-"+img.width/2+"px";
|
||||
}else{
|
||||
img.style.transform= "translate(-50%, -50%)";
|
||||
img.style["-ms-transform"]= "translate(-50%, -50%)";
|
||||
img.style["-moz-transform"]= "translate(-50%, -50%)";
|
||||
}
|
||||
if (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0) {
|
||||
zoom_in_cursor = 'url("/assets/archive/zoomin.cur"), auto';
|
||||
zoom_out_cursor = 'url("/assets/archive/zoomout.cur"), auto';
|
||||
}else{
|
||||
zoom_in_cursor = 'zoom-in';
|
||||
zoom_out_cursor = 'zoom-out';
|
||||
}
|
||||
if(height > window_height && (height / width) > (window_height / window_width) ){
|
||||
img.height = window_height;
|
||||
img.width = window_height / height * width;
|
||||
img.style.cursor = zoom_in_cursor;
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "-"+img.height/2+"px";
|
||||
img.style.marginLeft = "-"+img.width/2+"px";
|
||||
}
|
||||
img.onclick=function(e){
|
||||
var event = e || window.event;
|
||||
if(img.style.cursor == zoom_in_cursor){
|
||||
var cursor_x = event.clientX;
|
||||
var cursor_y = event.clientY;
|
||||
img.height = height;
|
||||
img.width = width;
|
||||
img.style.cursor = zoom_out_cursor;
|
||||
document.getElementsByTagName('html')[0].style.overflow = "";
|
||||
img.style.transform= "none";
|
||||
img.style["-ms-transform"]= "none";
|
||||
img.style["-moz-transform"]= "none";
|
||||
img.style.top= "0";
|
||||
img.style.left= "0";
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "0";
|
||||
img.style.marginLeft = "0";
|
||||
}
|
||||
window.scroll(
|
||||
(((cursor_x - (window_width - window_height / height * width)/2) * height / window_height) - window_width / 2),
|
||||
((cursor_y * height / window_height) - window_height / 2)
|
||||
);
|
||||
}else{
|
||||
img.height = window_height;
|
||||
img.width = window_height / height * width;
|
||||
img.style.cursor = zoom_in_cursor;
|
||||
document.getElementsByTagName('html')[0].style.overflow = "hidden";
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "-"+img.height/2+"px";
|
||||
img.style.marginLeft = "-"+img.width/2+"px";
|
||||
}else{
|
||||
img.style.transform= "translate(-50%, -50%)";
|
||||
img.style["-ms-transform"]= "translate(-50%, -50%)";
|
||||
img.style["-moz-transform"]= "translate(-50%, -50%)";
|
||||
}
|
||||
img.style.top= "50%";
|
||||
img.style.left= "50%";
|
||||
window.scroll(0, 0);
|
||||
}
|
||||
};
|
||||
}else if(width > window_width){
|
||||
img.width = window_width;
|
||||
img.height = window_width / width * height;
|
||||
img.style.cursor = zoom_in_cursor;
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "-"+img.height/2+"px";
|
||||
img.style.marginLeft = "-"+img.width/2+"px";
|
||||
}
|
||||
img.onclick=function(e){
|
||||
var event = e || window.event;
|
||||
if(img.style.cursor == zoom_in_cursor){
|
||||
var cursor_x = event.clientX;
|
||||
var cursor_y = event.clientY;
|
||||
img.height = height;
|
||||
img.width = width;
|
||||
img.style.cursor = zoom_out_cursor;
|
||||
document.getElementsByTagName('html')[0].style.overflow = "";
|
||||
img.style.transform= "none";
|
||||
img.style["-ms-transform"]= "none";
|
||||
img.style["-moz-transform"]= "none";
|
||||
img.style.top= "0";
|
||||
img.style.left= "0";
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "0";
|
||||
img.style.marginLeft = "0";
|
||||
}
|
||||
window.scroll( ((cursor_x * height / window_height) - window_width / 2),
|
||||
(((cursor_y - (window_height - window_width / width * height)/2) * height / window_height) - window_height / 2)
|
||||
);
|
||||
}else{
|
||||
img.width = window_width;
|
||||
img.height = window_width / width * height;
|
||||
img.style.cursor = zoom_in_cursor;
|
||||
document.getElementsByTagName('html')[0].style.overflow = "hidden";
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "-"+img.height/2+"px";
|
||||
img.style.marginLeft = "-"+img.width/2+"px";
|
||||
}else{
|
||||
img.style.transform= "translate(-50%, -50%)";
|
||||
img.style["-ms-transform"]= "translate(-50%, -50%)";
|
||||
img.style["-moz-transform"]= "translate(-50%, -50%)";
|
||||
}
|
||||
img.style.top= "50%";
|
||||
img.style.left= "50%";
|
||||
window.scroll(0, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
</body>
|
||||
</html>
|
||||
<% if @ext == 'pdf' %>
|
||||
<%= render partial: 'archives/viewer' %>
|
||||
<% else %>
|
||||
<html lang="<%= I18n.locale.to_s%>" style="margin: 0em; padding: 0em; width: 100%; height: 100%; overflow: hidden; background-color: rgb(230, 230, 230);">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=0.1">
|
||||
<title><%=@filename%></title>
|
||||
<%= stylesheet_link_tag "archive/download_file.css" %>
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="display: none;"><%=@filename%></h1>
|
||||
<% if @ext != "png" && @ext != "jpg" && @ext != "bmp" %>
|
||||
<object data="<%=@url%>" height="100%" type="application/<%=@ext%>" width="100%">
|
||||
<iframe height="100%" src="<%=@url%>" title="<%=@filename%>" width="100%"></iframe>
|
||||
<img alt="<%=@filename%>" src="<%=@url%>">
|
||||
</object>
|
||||
<% else %>
|
||||
<img alt="<%=@filename%>" src="<%=@url%>">
|
||||
<script type="text/javascript">
|
||||
var img = document.getElementsByTagName('img')[0];
|
||||
var width = img.width;
|
||||
var height = img.height;
|
||||
window.innerWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
window.innerHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
|
||||
var window_width = window.innerWidth;
|
||||
var window_height = window.innerHeight;
|
||||
var zoom_in_cursor,zoom_out_cursor;
|
||||
var IE_ver = 11;
|
||||
if(navigator.userAgent.search("MSIE") != -1){
|
||||
IE_ver = Number(navigator.userAgent.split("MSIE")[1].split(";")[0]);
|
||||
}
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "-"+img.height/2+"px";
|
||||
img.style.marginLeft = "-"+img.width/2+"px";
|
||||
}else{
|
||||
img.style.transform= "translate(-50%, -50%)";
|
||||
img.style["-ms-transform"]= "translate(-50%, -50%)";
|
||||
img.style["-moz-transform"]= "translate(-50%, -50%)";
|
||||
}
|
||||
if (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0) {
|
||||
zoom_in_cursor = 'url("/assets/archive/zoomin.cur"), auto';
|
||||
zoom_out_cursor = 'url("/assets/archive/zoomout.cur"), auto';
|
||||
}else{
|
||||
zoom_in_cursor = 'zoom-in';
|
||||
zoom_out_cursor = 'zoom-out';
|
||||
}
|
||||
if(height > window_height && (height / width) > (window_height / window_width) ){
|
||||
img.height = window_height;
|
||||
img.width = window_height / height * width;
|
||||
img.style.cursor = zoom_in_cursor;
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "-"+img.height/2+"px";
|
||||
img.style.marginLeft = "-"+img.width/2+"px";
|
||||
}
|
||||
img.onclick=function(e){
|
||||
var event = e || window.event;
|
||||
if(img.style.cursor == zoom_in_cursor){
|
||||
var cursor_x = event.clientX;
|
||||
var cursor_y = event.clientY;
|
||||
img.height = height;
|
||||
img.width = width;
|
||||
img.style.cursor = zoom_out_cursor;
|
||||
document.getElementsByTagName('html')[0].style.overflow = "";
|
||||
img.style.transform= "none";
|
||||
img.style["-ms-transform"]= "none";
|
||||
img.style["-moz-transform"]= "none";
|
||||
img.style.top= "0";
|
||||
img.style.left= "0";
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "0";
|
||||
img.style.marginLeft = "0";
|
||||
}
|
||||
window.scroll(
|
||||
(((cursor_x - (window_width - window_height / height * width)/2) * height / window_height) - window_width / 2),
|
||||
((cursor_y * height / window_height) - window_height / 2)
|
||||
);
|
||||
}else{
|
||||
img.height = window_height;
|
||||
img.width = window_height / height * width;
|
||||
img.style.cursor = zoom_in_cursor;
|
||||
document.getElementsByTagName('html')[0].style.overflow = "hidden";
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "-"+img.height/2+"px";
|
||||
img.style.marginLeft = "-"+img.width/2+"px";
|
||||
}else{
|
||||
img.style.transform= "translate(-50%, -50%)";
|
||||
img.style["-ms-transform"]= "translate(-50%, -50%)";
|
||||
img.style["-moz-transform"]= "translate(-50%, -50%)";
|
||||
}
|
||||
img.style.top= "50%";
|
||||
img.style.left= "50%";
|
||||
window.scroll(0, 0);
|
||||
}
|
||||
};
|
||||
}else if(width > window_width){
|
||||
img.width = window_width;
|
||||
img.height = window_width / width * height;
|
||||
img.style.cursor = zoom_in_cursor;
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "-"+img.height/2+"px";
|
||||
img.style.marginLeft = "-"+img.width/2+"px";
|
||||
}
|
||||
img.onclick=function(e){
|
||||
var event = e || window.event;
|
||||
if(img.style.cursor == zoom_in_cursor){
|
||||
var cursor_x = event.clientX;
|
||||
var cursor_y = event.clientY;
|
||||
img.height = height;
|
||||
img.width = width;
|
||||
img.style.cursor = zoom_out_cursor;
|
||||
document.getElementsByTagName('html')[0].style.overflow = "";
|
||||
img.style.transform= "none";
|
||||
img.style["-ms-transform"]= "none";
|
||||
img.style["-moz-transform"]= "none";
|
||||
img.style.top= "0";
|
||||
img.style.left= "0";
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "0";
|
||||
img.style.marginLeft = "0";
|
||||
}
|
||||
window.scroll( ((cursor_x * height / window_height) - window_width / 2),
|
||||
(((cursor_y - (window_height - window_width / width * height)/2) * height / window_height) - window_height / 2)
|
||||
);
|
||||
}else{
|
||||
img.width = window_width;
|
||||
img.height = window_width / width * height;
|
||||
img.style.cursor = zoom_in_cursor;
|
||||
document.getElementsByTagName('html')[0].style.overflow = "hidden";
|
||||
if(IE_ver <= 8){
|
||||
img.style.marginTop = "-"+img.height/2+"px";
|
||||
img.style.marginLeft = "-"+img.width/2+"px";
|
||||
}else{
|
||||
img.style.transform= "translate(-50%, -50%)";
|
||||
img.style["-ms-transform"]= "translate(-50%, -50%)";
|
||||
img.style["-moz-transform"]= "translate(-50%, -50%)";
|
||||
}
|
||||
img.style.top= "50%";
|
||||
img.style.left= "50%";
|
||||
window.scroll(0, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
</body>
|
||||
</html>
|
||||
<% end %>
|
||||
|
|
@ -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
|
||||
|
|
@ -1,31 +1,31 @@
|
|||
<%= render_view %>
|
||||
<script>
|
||||
let audio;
|
||||
$(".voice-player").on("click", function(){
|
||||
let status = $(this).attr('status');
|
||||
if (audio) {
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
}
|
||||
|
||||
if (status == 'playing') {
|
||||
$(this).attr('status', '');
|
||||
$(this).find('i').removeClass('fa-pause');
|
||||
$(this).find('i').addClass('fa-play');
|
||||
} else {
|
||||
let mp3_url = $(this).attr('data-content');
|
||||
let _this = $(this);
|
||||
audio = new Audio(mp3_url);
|
||||
audio.play();
|
||||
audio.onended = function() {
|
||||
_this.attr('status', '');
|
||||
_this.find('i').removeClass('fa-pause');
|
||||
_this.find('i').addClass('fa-play');
|
||||
};
|
||||
$(this).find('i').removeClass('fa-play');
|
||||
$(this).find('i').addClass('fa-pause');
|
||||
$(this).attr('status', 'playing');
|
||||
}
|
||||
return false;
|
||||
})
|
||||
<%= render_view %>
|
||||
<script>
|
||||
let audio;
|
||||
$(".voice-player").on("click", function(){
|
||||
let status = $(this).attr('status');
|
||||
if (audio) {
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
}
|
||||
|
||||
if (status == 'playing') {
|
||||
$(this).attr('status', '');
|
||||
$(this).find('i').removeClass('fa-pause');
|
||||
$(this).find('i').addClass('fa-play');
|
||||
} else {
|
||||
let mp3_url = $(this).attr('data-content');
|
||||
let _this = $(this);
|
||||
audio = new Audio(mp3_url);
|
||||
audio.play();
|
||||
audio.onended = function() {
|
||||
_this.attr('status', '');
|
||||
_this.find('i').removeClass('fa-pause');
|
||||
_this.find('i').addClass('fa-play');
|
||||
};
|
||||
$(this).find('i').removeClass('fa-play');
|
||||
$(this).find('i').addClass('fa-pause');
|
||||
$(this).attr('status', 'playing');
|
||||
}
|
||||
return false;
|
||||
})
|
||||
</script>
|
||||
|
|
@ -1,64 +1,64 @@
|
|||
<%
|
||||
data = action_data
|
||||
OrbitHelper.render_css_in_head(["mind_map/mindmap"])
|
||||
%>
|
||||
|
||||
<h2><%= data["title"] %></h2>
|
||||
<div id="jsmind_container"></div>
|
||||
<script type="module">
|
||||
import '/assets/mind_map/utils/custom.overrides.js'
|
||||
import '/assets/mind_map/jsmind/plugins/jsmind.draggable-node.js'
|
||||
import { initJsmind, getJsmindData } from '/assets/mind_map/utils/custom.main.js'
|
||||
import { INITIAL_MIND } from '/assets/mind_map/utils/custom.config.js'
|
||||
|
||||
// 操控心智圖是否可編輯
|
||||
// Control whether the mind map is editable
|
||||
let isEditable = false
|
||||
|
||||
// 心智圖實例
|
||||
// Mind map instance
|
||||
let jm
|
||||
|
||||
// 心智圖初始數據
|
||||
// Initial mind map data
|
||||
let mind = {
|
||||
meta: {},
|
||||
format: 'node_array',
|
||||
data: <%= raw data["mind_map_data"].to_json %>
|
||||
}
|
||||
|
||||
// 心智圖自訂選項(可參考 jsmind 官方文檔)
|
||||
// Custom options for the mind map (refer to the jsmind official documentation)
|
||||
const options = {
|
||||
container: 'jsmind_container',
|
||||
support_html: true,
|
||||
editable: isEditable,
|
||||
theme: 'primary',
|
||||
mode: 'full',
|
||||
tableUID: '',
|
||||
text: {
|
||||
addNode: "<%= t("universal_table.add_node") %>",
|
||||
deleteNode: "<%= t("universal_table.delete_node") %>",
|
||||
strokeColor: "<%= t("universal_table.stroke_color") %>",
|
||||
bgColor: "<%= t("universal_table.bg_color") %>",
|
||||
textColor: "<%= t("universal_table.text_color") %>"
|
||||
},
|
||||
view: {
|
||||
engine: 'svg',
|
||||
draggable: true,
|
||||
node_overflow: 'wrap',
|
||||
},
|
||||
shortcut: {
|
||||
mapping: {
|
||||
// 避免與 Toolbar 按下 Enter 事件衝突
|
||||
// Avoid conflicts with the Enter key event in the Toolbar
|
||||
addbrother: 2048 + 13,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化心智圖並掛載實例
|
||||
// Initialize the mind map and attach the instance
|
||||
jm = initJsmind(mind, options, isEditable)
|
||||
|
||||
<%
|
||||
data = action_data
|
||||
OrbitHelper.render_css_in_head(["mind_map/mindmap"])
|
||||
%>
|
||||
|
||||
<h2><%= data["title"] %></h2>
|
||||
<div id="jsmind_container"></div>
|
||||
<script type="module">
|
||||
import '/assets/mind_map/utils/custom.overrides.js'
|
||||
import '/assets/mind_map/jsmind/plugins/jsmind.draggable-node.js'
|
||||
import { initJsmind, getJsmindData } from '/assets/mind_map/utils/custom.main.js'
|
||||
import { INITIAL_MIND } from '/assets/mind_map/utils/custom.config.js'
|
||||
|
||||
// 操控心智圖是否可編輯
|
||||
// Control whether the mind map is editable
|
||||
let isEditable = false
|
||||
|
||||
// 心智圖實例
|
||||
// Mind map instance
|
||||
let jm
|
||||
|
||||
// 心智圖初始數據
|
||||
// Initial mind map data
|
||||
let mind = {
|
||||
meta: {},
|
||||
format: 'node_array',
|
||||
data: <%= raw data["mind_map_data"].to_json %>
|
||||
}
|
||||
|
||||
// 心智圖自訂選項(可參考 jsmind 官方文檔)
|
||||
// Custom options for the mind map (refer to the jsmind official documentation)
|
||||
const options = {
|
||||
container: 'jsmind_container',
|
||||
support_html: true,
|
||||
editable: isEditable,
|
||||
theme: 'primary',
|
||||
mode: 'full',
|
||||
tableUID: '',
|
||||
text: {
|
||||
addNode: "<%= t("universal_table.add_node") %>",
|
||||
deleteNode: "<%= t("universal_table.delete_node") %>",
|
||||
strokeColor: "<%= t("universal_table.stroke_color") %>",
|
||||
bgColor: "<%= t("universal_table.bg_color") %>",
|
||||
textColor: "<%= t("universal_table.text_color") %>"
|
||||
},
|
||||
view: {
|
||||
engine: 'svg',
|
||||
draggable: true,
|
||||
node_overflow: 'wrap',
|
||||
},
|
||||
shortcut: {
|
||||
mapping: {
|
||||
// 避免與 Toolbar 按下 Enter 事件衝突
|
||||
// Avoid conflicts with the Enter key event in the Toolbar
|
||||
addbrother: 2048 + 13,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化心智圖並掛載實例
|
||||
// Initialize the mind map and attach the instance
|
||||
jm = initJsmind(mind, options, isEditable)
|
||||
|
||||
</script>
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
<html lang="<%= I18n.locale.to_s%>">
|
||||
<head>
|
||||
<title><%=@filename%></title>
|
||||
<link href="/assets/archive/download_file.css" rel="stylesheet" media="all">
|
||||
</head>
|
||||
<body style="background: #fff;">
|
||||
<div class="wrap_block">
|
||||
<h1 style="display: none;"><%=@filename%></h1>
|
||||
<% download_text = t('download') + " " + @filename %>
|
||||
<h2 align="center"><a href="<%=@url%>" target="blank" download="<%=@filename%>" title="<%=download_text%>"><%=download_text%></a></h2>
|
||||
<p align="center">
|
||||
<a href="javascript:window.close();" title="<%=t('close')%>"><%=t('close')%></a>
|
||||
</p>
|
||||
</div>
|
||||
<script>window.location.href="<%=@url%>";</script>
|
||||
</body>
|
||||
<html lang="<%= I18n.locale.to_s%>">
|
||||
<head>
|
||||
<title><%=@filename%></title>
|
||||
<link href="/assets/archive/download_file.css" rel="stylesheet" media="all">
|
||||
</head>
|
||||
<body style="background: #fff;">
|
||||
<div class="wrap_block">
|
||||
<h1 style="display: none;"><%=@filename%></h1>
|
||||
<% download_text = t('download') + " " + @filename %>
|
||||
<h2 align="center"><a href="<%=@url%>" target="blank" download="<%=@filename%>" title="<%=download_text%>"><%=download_text%></a></h2>
|
||||
<p align="center">
|
||||
<a href="javascript:window.close();" title="<%=t('close')%>"><%=t('close')%></a>
|
||||
</p>
|
||||
</div>
|
||||
<script>window.location.href="<%=@url%>";</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,31 +1,31 @@
|
|||
<%= render_view %>
|
||||
<script>
|
||||
let audio;
|
||||
$(".voice-player").on("click", function(){
|
||||
let status = $(this).attr('status');
|
||||
if (audio) {
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
}
|
||||
|
||||
if (status == 'playing') {
|
||||
$(this).attr('status', '');
|
||||
$(this).find('i').removeClass('fa-pause');
|
||||
$(this).find('i').addClass('fa-play');
|
||||
} else {
|
||||
let mp3_url = $(this).attr('data-content');
|
||||
let _this = $(this);
|
||||
audio = new Audio(mp3_url);
|
||||
audio.play();
|
||||
audio.onended = function() {
|
||||
_this.attr('status', '');
|
||||
_this.find('i').removeClass('fa-pause');
|
||||
_this.find('i').addClass('fa-play');
|
||||
};
|
||||
$(this).find('i').removeClass('fa-play');
|
||||
$(this).find('i').addClass('fa-pause');
|
||||
$(this).attr('status', 'playing');
|
||||
}
|
||||
return false;
|
||||
})
|
||||
<%= render_view %>
|
||||
<script>
|
||||
let audio;
|
||||
$(".voice-player").on("click", function(){
|
||||
let status = $(this).attr('status');
|
||||
if (audio) {
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
}
|
||||
|
||||
if (status == 'playing') {
|
||||
$(this).attr('status', '');
|
||||
$(this).find('i').removeClass('fa-pause');
|
||||
$(this).find('i').addClass('fa-play');
|
||||
} else {
|
||||
let mp3_url = $(this).attr('data-content');
|
||||
let _this = $(this);
|
||||
audio = new Audio(mp3_url);
|
||||
audio.play();
|
||||
audio.onended = function() {
|
||||
_this.attr('status', '');
|
||||
_this.find('i').removeClass('fa-pause');
|
||||
_this.find('i').addClass('fa-play');
|
||||
};
|
||||
$(this).find('i').removeClass('fa-play');
|
||||
$(this).find('i').addClass('fa-pause');
|
||||
$(this).attr('status', 'playing');
|
||||
}
|
||||
return false;
|
||||
})
|
||||
</script>
|
||||
|
|
@ -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
|
||||
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
|
||||
|
|
|
|||
24
bin/rails
24
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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
require "universal_table/engine"
|
||||
|
||||
module UniversalTable
|
||||
end
|
||||
require "universal_table/engine"
|
||||
|
||||
module UniversalTable
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
module UniversalTable
|
||||
VERSION = "0.0.1"
|
||||
end
|
||||
module UniversalTable
|
||||
VERSION = "0.0.1"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,111 +1,111 @@
|
|||
<style>
|
||||
.universal-dropdown-menu {
|
||||
padding: 15px 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.universal-th-text {
|
||||
padding: 8px 0 0 0;
|
||||
display: inline;
|
||||
margin-right: 5px;
|
||||
color: #888;
|
||||
}
|
||||
.universal-dropdown {
|
||||
display: inline-block;
|
||||
}
|
||||
a.universal-btn {
|
||||
vertical-align: baseline;
|
||||
color: #fff;
|
||||
}
|
||||
.universal-table-index {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #eee;
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.universal-table-index h3 {
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
.universal-table-index.table td{
|
||||
padding: 15px 18px;
|
||||
}
|
||||
.universal-table-index thead th:last-child .dropdown-menu {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.universal-th-icon {
|
||||
border: 1px solid #eee;
|
||||
padding: 5px 8px;
|
||||
margin-right: 5px;
|
||||
color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.universal-th-text.no-sort.no-search {
|
||||
position: relative;
|
||||
top: -6px;
|
||||
}
|
||||
.image-preview {
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|
||||
<form class="form-inline universal-form-inline" action="{{url}}" method="get">
|
||||
<table class="table table-hover table-striped universal-table-index">
|
||||
<caption>
|
||||
<h3>{{table-name}}</h3>
|
||||
<a href="{{url}}" class="universal-btn btn btn-info pull-right {{reset}}"><i class="fa fa-refresh"></i> Reset</a>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr data-list="head-columns" data-level="0">
|
||||
<th class="col-md-3">
|
||||
<a href="{{sort-url}}" class="{{sort}}"><i class="universal-th-icon fa fa-{{sort-class}}"></i></a>
|
||||
<div class="universal-th-text {{title-class}}">{{title}}</div>
|
||||
<div class="dropdown universal-dropdown {{search}}">
|
||||
<button class="btn btn-sm" id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-search"></i>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu universal-dropdown-menu" aria-labelledby="dLabel">
|
||||
<div class="form-group">
|
||||
{{form-field}}
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit" class="btn btn-default">Go</button>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-level="0" data-list="rows">
|
||||
<tr data-level="1" data-list="columns">
|
||||
<td>{{text}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<div>{{total_entries}}</div>
|
||||
<div>{{export_button}}</div>
|
||||
{{pagination_goes_here}}
|
||||
<script type="text/javascript">
|
||||
$('.universal-table-index th').eq(1).attr('class', 'desktop tablet-l tablet-p');
|
||||
$('.universal-table-index th').filter(':gt(1)').attr('class', 'desktop tablet-l tablet-p mobile-l');
|
||||
$('.universal-table-index').each(function(){
|
||||
if($(this).find('thead').length!=0 && $(this).find('td').length!=0 && !$(this).hasClass('dataTable')){
|
||||
$(this).DataTable({
|
||||
searching: false,
|
||||
paging: false,
|
||||
ordering: false,
|
||||
info: false,
|
||||
order: false,
|
||||
autoWidth: false,
|
||||
responsive: true
|
||||
});
|
||||
}
|
||||
});
|
||||
$(document).on('click', '.universal-table-index .dropdown-menu', function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.universal-table-index.dtr-inline.collapsed td.dtr-control{
|
||||
vertical-align: middle;
|
||||
}
|
||||
<style>
|
||||
.universal-dropdown-menu {
|
||||
padding: 15px 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.universal-th-text {
|
||||
padding: 8px 0 0 0;
|
||||
display: inline;
|
||||
margin-right: 5px;
|
||||
color: #888;
|
||||
}
|
||||
.universal-dropdown {
|
||||
display: inline-block;
|
||||
}
|
||||
a.universal-btn {
|
||||
vertical-align: baseline;
|
||||
color: #fff;
|
||||
}
|
||||
.universal-table-index {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #eee;
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.universal-table-index h3 {
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
.universal-table-index.table td{
|
||||
padding: 15px 18px;
|
||||
}
|
||||
.universal-table-index thead th:last-child .dropdown-menu {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.universal-th-icon {
|
||||
border: 1px solid #eee;
|
||||
padding: 5px 8px;
|
||||
margin-right: 5px;
|
||||
color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.universal-th-text.no-sort.no-search {
|
||||
position: relative;
|
||||
top: -6px;
|
||||
}
|
||||
.image-preview {
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|
||||
<form class="form-inline universal-form-inline" action="{{url}}" method="get">
|
||||
<table class="table table-hover table-striped universal-table-index">
|
||||
<caption>
|
||||
<h3>{{table-name}}</h3>
|
||||
<a href="{{url}}" class="universal-btn btn btn-info pull-right {{reset}}"><i class="fa fa-refresh"></i> Reset</a>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr data-list="head-columns" data-level="0">
|
||||
<th class="col-md-3">
|
||||
<a href="{{sort-url}}" class="{{sort}}"><i class="universal-th-icon fa fa-{{sort-class}}"></i></a>
|
||||
<div class="universal-th-text {{title-class}}">{{title}}</div>
|
||||
<div class="dropdown universal-dropdown {{search}}">
|
||||
<button class="btn btn-sm" id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-search"></i>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu universal-dropdown-menu" aria-labelledby="dLabel">
|
||||
<div class="form-group">
|
||||
{{form-field}}
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit" class="btn btn-default">Go</button>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-level="0" data-list="rows">
|
||||
<tr data-level="1" data-list="columns">
|
||||
<td>{{text}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<div>{{total_entries}}</div>
|
||||
<div>{{export_button}}</div>
|
||||
{{pagination_goes_here}}
|
||||
<script type="text/javascript">
|
||||
$('.universal-table-index th').eq(1).attr('class', 'desktop tablet-l tablet-p');
|
||||
$('.universal-table-index th').filter(':gt(1)').attr('class', 'desktop tablet-l tablet-p mobile-l');
|
||||
$('.universal-table-index').each(function(){
|
||||
if($(this).find('thead').length!=0 && $(this).find('td').length!=0 && !$(this).hasClass('dataTable')){
|
||||
$(this).DataTable({
|
||||
searching: false,
|
||||
paging: false,
|
||||
ordering: false,
|
||||
info: false,
|
||||
order: false,
|
||||
autoWidth: false,
|
||||
responsive: true
|
||||
});
|
||||
}
|
||||
});
|
||||
$(document).on('click', '.universal-table-index .dropdown-menu', function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.universal-table-index.dtr-inline.collapsed td.dtr-control{
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,99 +1,99 @@
|
|||
<style>
|
||||
.universal-dropdown-menu {
|
||||
padding: 15px 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.universal-th-text {
|
||||
padding: 8px 0 0 0;
|
||||
display: inline;
|
||||
margin-right: 5px;
|
||||
color: #fff;
|
||||
}
|
||||
.universal-dropdown {
|
||||
display: inline-block;
|
||||
color: gray;
|
||||
}
|
||||
a.universal-btn {
|
||||
vertical-align: baseline;
|
||||
color: #fff;
|
||||
}
|
||||
.universal-table-index {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #eee;
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.universal-table-index h3 {
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
.universal-table-index.table td{
|
||||
padding: 15px 18px;
|
||||
}
|
||||
.universal-table-index thead th:last-child .dropdown-menu {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.universal-table-index tbody {
|
||||
counter-reset: item;
|
||||
}
|
||||
.universal-table-index thead > tr > th:first-child {
|
||||
width: 4em;
|
||||
}
|
||||
.universal-th-icon {
|
||||
border: 1px solid #eee;
|
||||
padding: 5px 8px;
|
||||
margin-right: 5px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.universal-th-text.no-sort.no-search {
|
||||
position: relative;
|
||||
top: -6px;
|
||||
}
|
||||
.image-preview {
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|
||||
<form class="form-inline universal-form-inline" action="{{url}}" method="get">
|
||||
<table class="table table-hover table-striped universal-table-index">
|
||||
<caption>
|
||||
<h3>{{table-name}}</h3>
|
||||
<a href="{{url}}" class="universal-btn btn btn-info pull-right {{reset}}"><i class="fa fa-refresh"></i> Reset</a>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr data-list="head-columns" data-level="0">
|
||||
<th class="col-md-3">
|
||||
<a href="{{sort-url}}" class="{{sort}}"><i class="universal-th-icon fa fa-{{sort-class}}"></i></a>
|
||||
<div class="universal-th-text {{title-class}}">{{title}}</div>
|
||||
<div class="dropdown universal-dropdown {{search}}">
|
||||
<button class="btn btn-sm" id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-search"></i>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu universal-dropdown-menu" aria-labelledby="dLabel">
|
||||
<div class="form-group">
|
||||
{{form-field}}
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit" class="btn btn-default">Go</button>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-level="0" data-list="rows">
|
||||
<tr data-level="1" data-list="columns">
|
||||
<td>{{text}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<div>{{total_entries}}</div>
|
||||
<div>{{export_button}}</div>
|
||||
{{pagination_goes_here}}
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).on('click', '.universal-table-index .dropdown-menu', function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
<style>
|
||||
.universal-dropdown-menu {
|
||||
padding: 15px 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.universal-th-text {
|
||||
padding: 8px 0 0 0;
|
||||
display: inline;
|
||||
margin-right: 5px;
|
||||
color: #fff;
|
||||
}
|
||||
.universal-dropdown {
|
||||
display: inline-block;
|
||||
color: gray;
|
||||
}
|
||||
a.universal-btn {
|
||||
vertical-align: baseline;
|
||||
color: #fff;
|
||||
}
|
||||
.universal-table-index {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #eee;
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.universal-table-index h3 {
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
.universal-table-index.table td{
|
||||
padding: 15px 18px;
|
||||
}
|
||||
.universal-table-index thead th:last-child .dropdown-menu {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.universal-table-index tbody {
|
||||
counter-reset: item;
|
||||
}
|
||||
.universal-table-index thead > tr > th:first-child {
|
||||
width: 4em;
|
||||
}
|
||||
.universal-th-icon {
|
||||
border: 1px solid #eee;
|
||||
padding: 5px 8px;
|
||||
margin-right: 5px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.universal-th-text.no-sort.no-search {
|
||||
position: relative;
|
||||
top: -6px;
|
||||
}
|
||||
.image-preview {
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|
||||
<form class="form-inline universal-form-inline" action="{{url}}" method="get">
|
||||
<table class="table table-hover table-striped universal-table-index">
|
||||
<caption>
|
||||
<h3>{{table-name}}</h3>
|
||||
<a href="{{url}}" class="universal-btn btn btn-info pull-right {{reset}}"><i class="fa fa-refresh"></i> Reset</a>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr data-list="head-columns" data-level="0">
|
||||
<th class="col-md-3">
|
||||
<a href="{{sort-url}}" class="{{sort}}"><i class="universal-th-icon fa fa-{{sort-class}}"></i></a>
|
||||
<div class="universal-th-text {{title-class}}">{{title}}</div>
|
||||
<div class="dropdown universal-dropdown {{search}}">
|
||||
<button class="btn btn-sm" id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-search"></i>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu universal-dropdown-menu" aria-labelledby="dLabel">
|
||||
<div class="form-group">
|
||||
{{form-field}}
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit" class="btn btn-default">Go</button>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-level="0" data-list="rows">
|
||||
<tr data-level="1" data-list="columns">
|
||||
<td>{{text}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<div>{{total_entries}}</div>
|
||||
<div>{{export_button}}</div>
|
||||
{{pagination_goes_here}}
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).on('click', '.universal-table-index .dropdown-menu', function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -1,118 +1,118 @@
|
|||
<style>
|
||||
.universal-dropdown-menu {
|
||||
padding: 15px 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.universal-th-text {
|
||||
padding: 8px 0 0 0;
|
||||
display: inline;
|
||||
margin-right: 5px;
|
||||
color: #888;
|
||||
}
|
||||
.universal-dropdown {
|
||||
display: inline-block;
|
||||
}
|
||||
a.universal-btn {
|
||||
vertical-align: baseline;
|
||||
color: #fff;
|
||||
}
|
||||
.universal-table-index {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #eee;
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.universal-table-index h3 {
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
.universal-table-index.table td{
|
||||
padding: 15px 18px;
|
||||
}
|
||||
.universal-table-index thead th:last-child .dropdown-menu {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.universal-table-index tbody {
|
||||
counter-reset: item;
|
||||
}
|
||||
.universal-th-icon {
|
||||
border: 1px solid #eee;
|
||||
padding: 5px 8px;
|
||||
margin-right: 5px;
|
||||
color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.universal-th-text.no-sort.no-search {
|
||||
position: relative;
|
||||
top: -6px;
|
||||
}
|
||||
.image-preview {
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|
||||
<form class="form-inline universal-form-inline" action="{{url}}" method="get">
|
||||
<table class="table table-hover table-striped universal-table-index">
|
||||
<div class="searchbtn">
|
||||
<div class="ken-click">
|
||||
<div class="searchbtn2 pull-right"><i class="fa-solid fa-magnifying-glass"></i>查詢</div>
|
||||
<a href="{{url}}" class="universal-btn btn btn-info pull-right {{reset}}"><i class="fa fa-refresh"></i> Reset</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbox">
|
||||
<div class="theadsearch2">
|
||||
<div class="row col-md-11 col-xs-12" data-list="searchable-columns" data-level="0">
|
||||
<div class="{{col-class}}">
|
||||
<a href="{{sort-url}}" class="{{sort}}"><i class="universal-th-icon fa fa-{{sort-class}}"></i></a>
|
||||
<div class="universal-th-text {{title-class}}">{{title}}</div>
|
||||
<div class="dropdown universal-dropdown {{search}}">
|
||||
<div class="dropdown-menu universal-dropdown-menu" aria-labelledby="dLabel">
|
||||
<div class="form-group">
|
||||
{{form-field}}
|
||||
<input type="hidden" value="{{key}}" name="column" >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1 col-xs-12 submit-btn-wrap">
|
||||
<button class="btn btn-primary pull-right" type="submit" class="btn btn-default">Go</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<caption>
|
||||
<h3>{{table-name}}</h3>
|
||||
</caption>
|
||||
<thead class="theadsearch">
|
||||
<tr data-list="head-columns" data-level="0">
|
||||
<th class="col-md-3">
|
||||
<a href="{{sort-url}}" class="{{sort}}"><i class="universal-th-icon fa fa-{{sort-class}}"></i></a>
|
||||
<div class="universal-th-text {{title-class}}">{{title}}</div>
|
||||
<div class="dropdown universal-dropdown {{search}}">
|
||||
<button class="btn btn-md" id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-search"></i>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu universal-dropdown-menu" aria-labelledby="dLabel">
|
||||
<form class="form-inline universal-form-inline" action="{{url}}" method="get">
|
||||
<div class="form-group">
|
||||
{{form-field}}
|
||||
<input type="hidden" value="{{key}}" name="column" >
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit" class="btn btn-default">Go</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-level="0" data-list="rows">
|
||||
<tr data-level="1" data-list="columns">
|
||||
<td>{{text}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<div>{{total_entries}}</div>
|
||||
<div>{{export_button}}</div>
|
||||
{{pagination_goes_here}}
|
||||
<style>
|
||||
.universal-dropdown-menu {
|
||||
padding: 15px 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.universal-th-text {
|
||||
padding: 8px 0 0 0;
|
||||
display: inline;
|
||||
margin-right: 5px;
|
||||
color: #888;
|
||||
}
|
||||
.universal-dropdown {
|
||||
display: inline-block;
|
||||
}
|
||||
a.universal-btn {
|
||||
vertical-align: baseline;
|
||||
color: #fff;
|
||||
}
|
||||
.universal-table-index {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #eee;
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.universal-table-index h3 {
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
.universal-table-index.table td{
|
||||
padding: 15px 18px;
|
||||
}
|
||||
.universal-table-index thead th:last-child .dropdown-menu {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.universal-table-index tbody {
|
||||
counter-reset: item;
|
||||
}
|
||||
.universal-th-icon {
|
||||
border: 1px solid #eee;
|
||||
padding: 5px 8px;
|
||||
margin-right: 5px;
|
||||
color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.universal-th-text.no-sort.no-search {
|
||||
position: relative;
|
||||
top: -6px;
|
||||
}
|
||||
.image-preview {
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|
||||
<form class="form-inline universal-form-inline" action="{{url}}" method="get">
|
||||
<table class="table table-hover table-striped universal-table-index">
|
||||
<div class="searchbtn">
|
||||
<div class="ken-click">
|
||||
<div class="searchbtn2 pull-right"><i class="fa-solid fa-magnifying-glass"></i>查詢</div>
|
||||
<a href="{{url}}" class="universal-btn btn btn-info pull-right {{reset}}"><i class="fa fa-refresh"></i> Reset</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="searchbox">
|
||||
<div class="theadsearch2">
|
||||
<div class="row col-md-11 col-xs-12" data-list="searchable-columns" data-level="0">
|
||||
<div class="{{col-class}}">
|
||||
<a href="{{sort-url}}" class="{{sort}}"><i class="universal-th-icon fa fa-{{sort-class}}"></i></a>
|
||||
<div class="universal-th-text {{title-class}}">{{title}}</div>
|
||||
<div class="dropdown universal-dropdown {{search}}">
|
||||
<div class="dropdown-menu universal-dropdown-menu" aria-labelledby="dLabel">
|
||||
<div class="form-group">
|
||||
{{form-field}}
|
||||
<input type="hidden" value="{{key}}" name="column" >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1 col-xs-12 submit-btn-wrap">
|
||||
<button class="btn btn-primary pull-right" type="submit" class="btn btn-default">Go</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<caption>
|
||||
<h3>{{table-name}}</h3>
|
||||
</caption>
|
||||
<thead class="theadsearch">
|
||||
<tr data-list="head-columns" data-level="0">
|
||||
<th class="col-md-3">
|
||||
<a href="{{sort-url}}" class="{{sort}}"><i class="universal-th-icon fa fa-{{sort-class}}"></i></a>
|
||||
<div class="universal-th-text {{title-class}}">{{title}}</div>
|
||||
<div class="dropdown universal-dropdown {{search}}">
|
||||
<button class="btn btn-md" id="dLabel" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fa fa-search"></i>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu universal-dropdown-menu" aria-labelledby="dLabel">
|
||||
<form class="form-inline universal-form-inline" action="{{url}}" method="get">
|
||||
<div class="form-group">
|
||||
{{form-field}}
|
||||
<input type="hidden" value="{{key}}" name="column" >
|
||||
</div>
|
||||
<button class="btn btn-primary" type="submit" class="btn btn-default">Go</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-level="0" data-list="rows">
|
||||
<tr data-level="1" data-list="columns">
|
||||
<td>{{text}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<div>{{total_entries}}</div>
|
||||
<div>{{export_button}}</div>
|
||||
{{pagination_goes_here}}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,107 +1,107 @@
|
|||
<style>
|
||||
tr>th:first-child{
|
||||
display: none!important;
|
||||
}
|
||||
/* tr>td:first-child{
|
||||
display: none!important;
|
||||
} */
|
||||
.universal-table-index3{
|
||||
table-layout: auto!important;
|
||||
}
|
||||
.universal-dropdown-menu {
|
||||
padding: 15px 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.universal-th-text{
|
||||
white-space: pre!important;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
white-space: normal;
|
||||
}
|
||||
.universal-dropdown-menu {
|
||||
padding: 15px 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.universal-th-text {
|
||||
padding: 8px 0 0 0;
|
||||
display: inline;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.universal-dropdown {
|
||||
display: inline-block;
|
||||
}
|
||||
a.universal-btn {
|
||||
vertical-align: baseline;
|
||||
color: #fff;
|
||||
}
|
||||
.universal-table-index {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #eee;
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.universal-table-index h3 {
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
.universal-table-index.table td{
|
||||
padding: 15px 18px;
|
||||
}
|
||||
.universal-table-index thead th:last-child .dropdown-menu {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
/* .universal-table-index tbody {
|
||||
counter-reset: item;
|
||||
} */
|
||||
.universal-th-icon {
|
||||
border: 1px solid #eee;
|
||||
padding: 5px 8px;
|
||||
margin-right: 5px;
|
||||
color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.universal-th-text.no-sort.no-search {
|
||||
position: relative;
|
||||
top: -6px;
|
||||
}
|
||||
.image-preview {
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|
||||
<form class="form-inline universal-form-inline universal-form-inline5" action="{{url}}" method="get">
|
||||
<table class="table table-hover table-striped universal-table-index universal-table-index3">
|
||||
<caption>
|
||||
<h3>{{table-name}}</h3>
|
||||
</caption>
|
||||
<tbody>
|
||||
<tr class="tdken" data-level="0" data-list="mindmaps">
|
||||
<td><a href="{{url}}">{{title}}</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
{{pagination_goes_here}}
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('.tdken>td:nth-child(4)').prepend($('.col-ken:nth-child(4)>.universal-th-text '));
|
||||
$('.tdken>td:nth-child(3)').prepend($('.col-ken:nth-child(3)>.universal-th-text '));
|
||||
$(".universal-th-text").append(" :");
|
||||
$(".tdken").append('<i class="fa-solid fa-thumbtack"></i>');
|
||||
});
|
||||
// $(document).ready(function(){
|
||||
// $("tr>th:first-child").removeClass("col-md-3");
|
||||
// $("tr>th:first-child").addClass("col-md-1");
|
||||
// $("tr>th:nth-child(2)").removeClass("col-md-3");
|
||||
// $("tr>th:nth-child(2)").addClass("col-md-6");
|
||||
// $("tr>th:nth-child(3)").removeClass("col-md-3");
|
||||
// $("tr>th:nth-child(3)").addClass("col-md-3");
|
||||
// $("tr>th:nth-child(4)").removeClass("col-md-3");
|
||||
// $("tr>th:nth-child(4)").addClass("col-md-2");
|
||||
// $("tr>th:nth-child(5)").removeClass("col-md-3");
|
||||
// $("tr>th:nth-child(5)").addClass("col-md-3");
|
||||
// });
|
||||
// $('.universal-table-index thead tr').prepend('<th></th>')
|
||||
// $('.universal-table-index tbody tr').prepend('<td></td>')
|
||||
</script>
|
||||
<style>
|
||||
tr>th:first-child{
|
||||
display: none!important;
|
||||
}
|
||||
/* tr>td:first-child{
|
||||
display: none!important;
|
||||
} */
|
||||
.universal-table-index3{
|
||||
table-layout: auto!important;
|
||||
}
|
||||
.universal-dropdown-menu {
|
||||
padding: 15px 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.universal-th-text{
|
||||
white-space: pre!important;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
white-space: normal;
|
||||
}
|
||||
.universal-dropdown-menu {
|
||||
padding: 15px 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.universal-th-text {
|
||||
padding: 8px 0 0 0;
|
||||
display: inline;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.universal-dropdown {
|
||||
display: inline-block;
|
||||
}
|
||||
a.universal-btn {
|
||||
vertical-align: baseline;
|
||||
color: #fff;
|
||||
}
|
||||
.universal-table-index {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #eee;
|
||||
table-layout: fixed;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.universal-table-index h3 {
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
.universal-table-index.table td{
|
||||
padding: 15px 18px;
|
||||
}
|
||||
.universal-table-index thead th:last-child .dropdown-menu {
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
/* .universal-table-index tbody {
|
||||
counter-reset: item;
|
||||
} */
|
||||
.universal-th-icon {
|
||||
border: 1px solid #eee;
|
||||
padding: 5px 8px;
|
||||
margin-right: 5px;
|
||||
color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.universal-th-text.no-sort.no-search {
|
||||
position: relative;
|
||||
top: -6px;
|
||||
}
|
||||
.image-preview {
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|
||||
<form class="form-inline universal-form-inline universal-form-inline5" action="{{url}}" method="get">
|
||||
<table class="table table-hover table-striped universal-table-index universal-table-index3">
|
||||
<caption>
|
||||
<h3>{{table-name}}</h3>
|
||||
</caption>
|
||||
<tbody>
|
||||
<tr class="tdken" data-level="0" data-list="mindmaps">
|
||||
<td><a href="{{url}}">{{title}}</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
{{pagination_goes_here}}
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('.tdken>td:nth-child(4)').prepend($('.col-ken:nth-child(4)>.universal-th-text '));
|
||||
$('.tdken>td:nth-child(3)').prepend($('.col-ken:nth-child(3)>.universal-th-text '));
|
||||
$(".universal-th-text").append(" :");
|
||||
$(".tdken").append('<i class="fa-solid fa-thumbtack"></i>');
|
||||
});
|
||||
// $(document).ready(function(){
|
||||
// $("tr>th:first-child").removeClass("col-md-3");
|
||||
// $("tr>th:first-child").addClass("col-md-1");
|
||||
// $("tr>th:nth-child(2)").removeClass("col-md-3");
|
||||
// $("tr>th:nth-child(2)").addClass("col-md-6");
|
||||
// $("tr>th:nth-child(3)").removeClass("col-md-3");
|
||||
// $("tr>th:nth-child(3)").addClass("col-md-3");
|
||||
// $("tr>th:nth-child(4)").removeClass("col-md-3");
|
||||
// $("tr>th:nth-child(4)").addClass("col-md-2");
|
||||
// $("tr>th:nth-child(5)").removeClass("col-md-3");
|
||||
// $("tr>th:nth-child(5)").addClass("col-md-3");
|
||||
// });
|
||||
// $('.universal-table-index thead tr').prepend('<th></th>')
|
||||
// $('.universal-table-index tbody tr').prepend('<td></td>')
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
<style>
|
||||
.universal-table-show {
|
||||
border: 1px solid #eee;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.universal-table-show.table td{
|
||||
padding: 15px 18px;
|
||||
}
|
||||
.table-title {
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
|
||||
</style>
|
||||
<table class="table table-striped universal-table-show">
|
||||
<tbody data-level="0" data-list="entry">
|
||||
<tr>
|
||||
<td class="col-md-2 table-title">{{title}}</td>
|
||||
<td>{{text}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="view_count pull-right">
|
||||
<i class="fa fa-eye">{{view_count_head}}:</i>
|
||||
<span class="view-count">{{view_count}}</span>
|
||||
</div>
|
||||
|
||||
<div data-list="related_entries" data-level="0">
|
||||
<tbody data-level="1" data-list="related_entry">
|
||||
<tr>
|
||||
<td>{{text}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</div>
|
||||
<style>
|
||||
.universal-table-show {
|
||||
border: 1px solid #eee;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.universal-table-show.table td{
|
||||
padding: 15px 18px;
|
||||
}
|
||||
.table-title {
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
|
||||
</style>
|
||||
<table class="table table-striped universal-table-show">
|
||||
<tbody data-level="0" data-list="entry">
|
||||
<tr>
|
||||
<td class="col-md-2 table-title">{{title}}</td>
|
||||
<td>{{text}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="view_count pull-right">
|
||||
<i class="fa fa-eye">{{view_count_head}}:</i>
|
||||
<span class="view-count">{{view_count}}</span>
|
||||
</div>
|
||||
|
||||
<div data-list="related_entries" data-level="0">
|
||||
<tbody data-level="1" data-list="related_entry">
|
||||
<tr>
|
||||
<td>{{text}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</div>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
|
@ -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
|
||||
<tt>rake doc:app</tt>.
|
||||
== 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
|
||||
<tt>rake doc:app</tt>.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 .
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue