Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • oss/minerva/minerva-app
1 result
Show changes
Commits on Source (3)
Showing
with 2815 additions and 66 deletions
/development/build/
node_modules/
development/temp/
development/log/
/application/source/server/web/node_modules/
##################### IDE / Tools ##
# @see https://github.com/github/gitignore/
# PHPUnit
.phpunit.result.cache
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.cache
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Cloud9 IDE - http://c9.io
.c9revisions
.c9
##Exclipse
.metadata
#bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
#local.properties
.settings/
.loadpath
.recommenders
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
.apt_generated_test/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
# Uncomment this line if you wish to ignore the project description file.
# Typically, this file would be tracked if it contains build/dependency configurations:
#.project
##Kate
# Swap Files #
.*.kate-swp
.swp.*
## KDEDevelop
*.kdev4
.kdev4/
## LibreOffice
# LibreOffice locks
.~lock.*#
### LINUX
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
## Patches
*.orig
*.rej
# Private key
*.ppk
## Sublime
# Cache files for Sublime Text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# Workspace files are user-specific
*.sublime-workspace
# Project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using Sublime Text
# *.sublime-project
# SFTP configuration file
sftp-config.json
sftp-config-alt*.json
# Package control specific files
Package Control.last-run
Package Control.ca-list
Package Control.ca-bundle
Package Control.system-ca-bundle
Package Control.cache/
Package Control.ca-certs/
Package Control.merged-ca-bundle
Package Control.user-ca-bundle
oscrypto-ca-bundle.crt
bh_unicode_properties.cache
# Sublime-github package stores a github token in this file
# https://packagecontrol.io/packages/sublime-github
GitHub.sublime-settings
## VIM
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# gitignore template for AWS Serverless Application Model project
# website: https://docs.aws.amazon.com/serverless-application-model
# Ignore build folder
.aws-sam/
# Netbeans
**/nbproject/private/
**/nbproject/Makefile-*.mk
**/nbproject/Package-*.bash
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
## GOLANG
go.work
......@@ -3,7 +3,17 @@
<component name="Go" enabled="true" />
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/application/source" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/application/source/command" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/application/source/config" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/application/source/server" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/application/source/server/web" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/application/source/server/web/app" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/application/source/utils" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/development/examples" type="java-resource" />
<excludeFolder url="file://$MODULE_DIR$/development/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
......
......@@ -2,7 +2,7 @@
<configuration default="false" name="minerva serve" type="GoApplicationRunConfiguration" factoryName="Go Application">
<module name="minerva" />
<working_directory value="$PROJECT_DIR$/application/source" />
<parameters value="serve --config=../../development/examples/theme1/config.yaml" />
<parameters value="serve --config=../../development/examples/theme1/config.yaml --path=../../development/examples/theme1/" />
<kind value="DIRECTORY" />
<directory value="$PROJECT_DIR$/application/source" />
<filePath value="$PROJECT_DIR$" />
......
......@@ -30,4 +30,10 @@ func intiParser() {
config.InitializeConfigFromFile(def.Config)
}
cfg := config.GetConfiguration()
if def.Serve.Path != "" {
cfg.Server.Path = def.Serve.Path
}
}
......@@ -5,24 +5,6 @@ type options struct {
Version struct {
} `command:"version"`
Serve struct {
Path string `short:"p" long:"path" description:"Path to the project files"`
} `command:"serve"`
}
//// DebugLevel logs are typically voluminous, and are usually disabled in production.
// DebugLevel = zapcore.DebugLevel
//// InfoLevel is the default logging priority.
// InfoLevel = zapcore.InfoLevel
//// WarnLevel logs are more important than Info, but don't need individual
//// human review.
// WarnLevel = zapcore.WarnLevel
//// ErrorLevel logs are high-priority. If an application is running smoothly,
//// it shouldn't generate any error-level logs.
// ErrorLevel = zapcore.ErrorLevel
//// DPanicLevel logs are particularly important errors. In development the
//// logger panics after writing the message.
// DPanicLevel = zapcore.DPanicLevel
//// PanicLevel logs a message, then panics.
// PanicLevel = zapcore.PanicLevel
//// FatalLevel logs a message, then calls os.Exit(1).
// FatalLevel = zapcore.FatalLevel
// ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF
......@@ -14,6 +14,7 @@ func Do() {
printVersion()
case "serve":
serve(active)
}
......
package config
import (
"github.com/creasty/defaults"
"github.com/kelseyhightower/envconfig"
"gitlab.schukai.com/oss/minerva/utils"
"gopkg.in/yaml.v3"
......@@ -8,24 +9,44 @@ import (
"path"
)
func GetServerPort() string {
return configuration.Server.Port
}
type Configuration struct {
//LogLevel string `yaml:"logLevel" envconfig:"LOG_LEVEL"`
Server struct {
Port string `yaml:"Port" envconfig:"SERVER_PORT" default:"80"`
Port int `yaml:"Port" envconfig:"SERVER_PORT" default:"8080"`
Path string `yaml:"Path" envconfig:"SERVER_PATH" default:""`
} `yaml:"Server"`
}
Paths struct {
Web string `yaml:"Web" envconfig:"PATH_WEB" default:"/srv/web/"`
} `yaml:"Paths"`
// SetDefaults implements defaults.Setter interface
func (s *Configuration) SetDefaults() {
if defaults.CanUpdate(s.Server.Path) {
path, err := os.Getwd()
if err != nil {
utils.PrintErrorAndExit("something went wrong: %s", err.Error())
}
s.Server.Path = path
}
}
func GetServerPort() int {
return configuration.Server.Port
}
func GetServerPath() string {
return configuration.Server.Path
}
var configuration *Configuration
func newConfiguration() *Configuration {
config := &Configuration{}
if err := defaults.Set(config); err != nil {
utils.PrintErrorAndExit("we have a problem: %s", err.Error())
}
return config
}
func GetConfiguration() *Configuration {
if configuration == nil {
......@@ -63,7 +84,7 @@ func intiConfiguration() {
return
}
configuration = &Configuration{}
configuration = newConfiguration()
}
......@@ -98,10 +119,10 @@ func readFile(filename string) {
}
}()
c := Configuration{}
err = yaml.Unmarshal([]byte(data), &c)
c := newConfiguration()
err = yaml.Unmarshal([]byte(data), c)
configuration = &c
configuration = c
if err != nil {
utils.PrintErrorAndExit(err.Error())
......
......@@ -13,6 +13,7 @@ require (
)
require (
github.com/creasty/defaults v1.6.0 // indirect
github.com/go-chi/chi/v5 v5.0.7 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
......
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/creasty/defaults v1.6.0 h1:ltuE9cfphUtlrBeomuu8PEyISTXnxqkBIoQfXgv7BSc=
github.com/creasty/defaults v1.6.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
......
package server
import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"net/http"
)
func appHandler() http.Handler {
r := chi.NewRouter()
r.Use(middleware.NoCache)
r.Get("/editor", renderEditor)
r.Handle("/web/node_modules/*",
http.StripPrefix("/app", http.FileServer(http.FS(embeddedFiles))))
//r.Get("/node_modules/*", serveNodeModules)
//r.Route("/node_modules/*", http.StripPrefix("/assets/",
// http.FileServer(http.Dir("assets/"))))
//
//http.Handle("/assets/", http.StripPrefix("/assets/",
// http.FileServer(http.Dir("assets/"))))
//r.Post("/convert", doV1Convert)
//r.Post("/create", doV1Create)
return r
}
package server
import (
"net/http"
)
func serveNodeModules(w http.ResponseWriter, r *http.Request) {
content := lookUp(r.URL.Path)
if content != nil {
//w.Header().Add("Content-Type", "application/json")
//mimeType := ""
//
//ext := filepath.Ext(r.URL.Path)
//switch ext {
//case ".htm", ".html":
// return "text/html"
//case ".css":
// return "text/css"
//case ".js":
// return "application/javascript"
//
//
//
//filetype := http.DetectContentType(content)
//fmt.Println(filetype)
w.Write(content)
return
}
w.WriteHeader(404)
}
func renderEditor(w http.ResponseWriter, r *http.Request) {
content, err := embeddedFiles.ReadFile("web/app/editor.html")
if err != nil {
logError("something went wrong: %s", err.Error())
w.WriteHeader(500)
return
}
w.Write(content)
}
package server
import (
"net/http"
"path"
"strings"
)
//// FSHandler404 provides the function signature for passing to the FileServerWith404
//type handler404 = func(w http.ResponseWriter, r *http.Request) (doDefaultFileServe bool)
func fileServer(root string) http.Handler {
fs := http.FileServer(http.Dir(root))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//make sure the url path starts with /
upath := r.URL.Path
if !strings.HasPrefix(upath, "/") {
upath = "/" + upath
r.URL.Path = upath
}
upath = path.Clean(upath)
//
////// attempt to open the file via the http.FileSystem
////f, err := root.Open(upath)
////// close if successfully opened
////if f != nil {
//// defer f.Close()
////}
//if !utils.DirectoryExists(root) {
// if os.IsNotExist(err) {
// // call handler
// if h404 != nil {
// doDefault := h404(w, r)
// if !doDefault {
// return
// }
// }
// }
//}
//
//s, err := f.Stat()
//
//if err != nil {
// if os.IsNotExist(err) {
// // call handler
// if h404 != nil {
// doDefault := h404(w, r)
// if !doDefault {
// return
// }
// }
// }
//}
//
//if s.IsDir() && false {
// return
//}
// default serve
fs.ServeHTTP(w, r)
})
}
......@@ -8,6 +8,10 @@ import (
"github.com/go-chi/chi/v5/middleware"
"gitlab.schukai.com/oss/minerva/config"
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"sync"
)
......@@ -17,26 +21,45 @@ var (
embeddedFiles embed.FS
)
func lookUp(path string) (content []byte) {
// Zuerst im Verzeichnis /web/asserts schauen (wird beim build inkludiert)
c, _ := embeddedFiles.ReadFile("web" + path)
func lookUp(p string) (content []byte) {
c, _ := embeddedFiles.ReadFile("web" + p)
if len(c) > 0 {
return c
}
base, err := filepath.Abs(config.GetServerPath())
if err != nil {
logError("%s", err.Error())
return nil
}
filename := path.Clean(path.Join(base, p))
data, _ := os.ReadFile(filename)
if len(data) > 0 {
return data
}
return nil
}
func serveStaticFiles(next http.Handler) func(http.ResponseWriter, *http.Request) {
//func serveStaticFiles() func(http.ResponseWriter, *http.Request) {
// return func(w http.ResponseWriter, r *http.Request) {
//
// content := lookUp(r.URL.Path)
// if content != nil {
// w.Write(content)
// return
// }
//
// w.WriteHeader(404)
//
// }
//}
func redirectToApp() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
content := lookUp(r.URL.Path)
if len(content) > 0 {
w.Write(content)
return
}
next.ServeHTTP(w, r)
http.Redirect(w, r, "app/editor", http.StatusTemporaryRedirect)
}
}
......@@ -57,21 +80,21 @@ func initRouting() *chi.Mux {
/** x509: certificate signed by unknown authority */
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
mux := chi.NewRouter()
router := chi.NewRouter()
mux.Use(waitGroupMiddleware())
router.Use(waitGroupMiddleware())
mux.Use(RequestIDMiddleware)
mux.Use(middleware.RealIP)
mux.Use(middleware.Heartbeat("/ping"))
router.Use(RequestIDMiddleware)
router.Use(middleware.RealIP)
router.Use(middleware.Heartbeat("/ping"))
mux.Use(middleware.Compress(5))
mux.Use(middleware.AllowContentType("application/json", "text/html"))
mux.Use(loggerMiddleware)
router.Use(middleware.Compress(5))
router.Use(middleware.AllowContentType("application/json", "text/html"))
router.Use(loggerMiddleware)
// mux.Mount("/api/v1", v1.V1Api(cfg))
router.Mount("/app", appHandler())
//mux.Get("/", func(w http.ResponseWriter, r *http.Request) {
//router.Get("/", func(w http.ResponseWriter, r *http.Request) {
// if negotiator.New(r.Header).Type("text/html") == "text/html" {
// http.Redirect(w, r, "https://www.alvine.cloud/en/products/media-services/juno/", http.StatusPermanentRedirect)
// } else {
......@@ -92,17 +115,30 @@ func initRouting() *chi.Mux {
// return nil
//}
//router.Get("/*", redirectToApp())
//
//router.Path("/whatever").Handler(func(writer http.ResponseWriter, req *http.Request) {
// http.Redirect(writer, req, "localhost:8080/whatever", http.StatusMovedPermanently)
// ))
//
//d := http.Dir(p)
//mux.Get("/*",
// serveStaticFiles(fileserver.FileServerWith404(d, func(w http.ResponseWriter, r *http.Request) (doDefaultFileServe bool) {
// error2.StandardErrorResponse(w, r, error2.ErrorResponse{
// Status: 404,
// })
// return false
// router.Get("/*",
//serveStaticFiles(
// func(w http.ResponseWriter, r *http.Request) (doDefaultFileServe bool) {
//
// }
//
// })))
// )
// //serveStaticFiles(fileServerWith404("/tmp", func(w http.ResponseWriter, r *http.Request) (doDefaultFileServe bool) {
// // error2.StandardErrorResponse(w, r, error2.ErrorResponse{
// // Status: 404,
// // })
// // return false
// //
// //})))
return mux
return router
}
......@@ -113,7 +149,7 @@ func Serve() {
c := config.GetConfiguration()
address := ":" + c.Server.Port
address := ":" + strconv.FormatInt(int64(c.Server.Port), 10)
logInfo("server listen on address %s", address)
server := http.Server{
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/app/web/node_modules/grapesjs/dist/css/grapes.min.css">
<link rel="stylesheet" href="/app/web/node_modules/grapesjs-preset-webpage/dist/grapesjs-preset-webpage.min.css">
<script src="/app/web/node_modules/grapesjs/dist/grapes.min.js"></script>
<script src="/app/web/node_modules/grapesjs-preset-webpage/dist/grapesjs-preset-webpage.min.js"></script>
<style>
body,
html {<
height: 100%;
margin: 0;
overflow: hidden;
}
#toast-container {
font-size: 13px;
font-weight: lighter;
}
#toast-container > div {
opacity: 0.95;
}
#toast-container > div,
#toast-container > div:hover {
box-shadow: 0 0 12px rgba(0, 0, 0, 0.1);
font-family: Helvetica, sans-serif;
}
/* LOGO VERSION */
.gjs-pn-commands .gjs-pn-buttons,
#gjs-pn-commands .gjs-pn-buttons {
display: none;
}
.gjs-logo {
height: 25px;
}
.gjs-logo-cont {
position: relative;
display: inline-block;
top: 3px;
}
.gjs-logo-version {
position: absolute;
font-size: 10px;
padding: 1px 7px;
border-radius: 15px;
bottom: 2px;
right: -43px;
}
/* INFO PANEL */
.gjs-mdl-dialog-sm {
width: 300px;
}
#info-panel {
line-height: 17px;
}
.info-panel-logo {
display: block;
height: 90px;
margin: 0 auto;
width: 90px;
}
.info-panel-logo path {
stroke: #eee !important;
stroke-width: 8 !important;
}
.info-panel-label {
margin-bottom: 10px;
font-size: 13px;
}
.info-panel-link {
text-decoration: none;
}
/* ADS */
.gjs-pn-panel#gjs-pn-views-container,
.gjs-pn-panel.gjs-pn-views-container {
height: calc(100% - 150px);
}
.ad-cont {
position: absolute;
right: 0;
bottom: 0;
z-index: 2;
width: 15%;
height: 150px;
}
#carbonads {
font: caption;
padding: 20px 10px;
}
.carbon-link {
text-decoration: none;
font: caption;
}
.carbon-img {
float: right;
margin-left: 10px;
}
.carbon-img img {
border-radius: 3px;
max-width: 100px !important;
max-height: 77px;
}
.carbon-text {
color: rgba(255, 255, 255, 0.75);
text-decoration: none;
font-weight: lighter;
}
.carbon-poweredby {
color: rgba(255, 255, 255, 0.55);
text-decoration: none;
float: right;
}
.carbon-cta-c {
text-align: right;
padding-top: 5px;
}
.carbon-cta {
display: inline-block;
padding: 4px 10px;
border-radius: 3px;
font-weight: bold;
font-size: 12px;
}
.gjs-block-label svg,
.gjs-block__media svg {
width: 54px;
}
/* Temporary fix #2490 */
.gjs-clm-tag-status,
.gjs-clm-tag-close {
width: 12px;
height: 12px;
}
.gjs-clm-tags-btn {
width: 24px;
}
</style>
</head>
<body>
<div id="gjs"></div>
<div id="blocks"></div>
<script type="text/javascript">
const editor = grapesjs.init({
// Indicate where to init the editor. You can also pass an HTMLElement
container: '#gjs',
// Get the content for the canvas directly from the element
// As an alternative we could use: `components: '<h1>Hello World Component!</h1>'`,
fromElement: true,
// Size of the editor
height: '300px',
width: 'auto',
// Disable the storage manager for the moment
storageManager: false,
// Avoid any default panel
panels: {defaults: []},
blockManager: {
appendTo: '#blocks',
blocks: [
{
id: 'section', // id is mandatory
label: '<b>Section</b>', // You can use HTML/SVG inside labels
attributes: {class: 'gjs-block-section'},
content: `<section>
<h1>This is a simple title</h1>
<div>This is just a Lorem text: Lorem ipsum dolor sit amet</div>
</section>`,
}, {
id: 'text',
label: 'Text',
content: '<div data-gjs-type="text">Insert your text here</div>',
}, {
id: 'image',
label: 'Image',
// Select the component once it's dropped
select: true,
// You can pass components as a JSON instead of a simple HTML string,
// in this case we also use a defined component type `image`
content: {type: 'image'},
// This triggers `active` event on dropped components and the `image`
// reacts by opening the AssetManager
activate: true,
}
]
},
});
</script>
</body>
</html>
{
"name": "minerva",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"node_modules/backbone": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/backbone/-/backbone-1.3.3.tgz",
"integrity": "sha1-TMgOp8sWMaxHSInOQPL4vGg7KZk=",
"dependencies": {
"underscore": ">=1.8.3"
}
},
"node_modules/backbone-undo": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/backbone-undo/-/backbone-undo-0.2.5.tgz",
"integrity": "sha1-VbJSMPkDGcpiJGXomoAki4k8LOI=",
"dependencies": {
"backbone": "1.0.0 - 1.2.1",
"underscore": "1.4.4 - 1.8.3"
}
},
"node_modules/backbone-undo/node_modules/backbone": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/backbone/-/backbone-1.2.1.tgz",
"integrity": "sha1-1yGcXtSeXhMdv/ryXJbW0sw8oD4=",
"dependencies": {
"underscore": ">=1.7.0"
}
},
"node_modules/backbone-undo/node_modules/underscore": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
},
"node_modules/codemirror": {
"version": "5.65.3",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.3.tgz",
"integrity": "sha512-kCC0iwGZOVZXHEKW3NDTObvM7pTIyowjty4BUqeREROc/3I6bWbgZDA3fGDwlA+rbgRjvnRnfqs9SfXynel1AQ=="
},
"node_modules/codemirror-formatting": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/codemirror-formatting/-/codemirror-formatting-1.0.0.tgz",
"integrity": "sha1-h5zB/dkBg0PB1VEXac5TYNcF6/I="
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/file-saver": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz",
"integrity": "sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg=="
},
"node_modules/grapesjs": {
"version": "0.18.4",
"resolved": "https://registry.npmjs.org/grapesjs/-/grapesjs-0.18.4.tgz",
"integrity": "sha512-mOkmOc7Q9OyuW4H8h4NDhFK1cxBnw/En+sD60enOKQrNy5lfua6Lh5I0oTm93RU6ahQy2QPzn1ZbZWAQaDCKQw==",
"dependencies": {
"backbone": "1.3.3",
"backbone-undo": "^0.2.5",
"codemirror": "^5.63.0",
"codemirror-formatting": "^1.0.0",
"promise-polyfill": "^8.1.3",
"underscore": "^1.13.1"
}
},
"node_modules/grapesjs-aviary": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/grapesjs-aviary/-/grapesjs-aviary-0.1.2.tgz",
"integrity": "sha512-F7sYQWj5s9eyUqoOX6q1z2J06AqgXhQqdjQhM8b5BptxlbDsvDTCbqSRmLGkEQKaIF1ca5o9FFi9eKBeUAfLEw==",
"peerDependencies": {
"grapesjs": "0.x"
}
},
"node_modules/grapesjs-blocks-basic": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/grapesjs-blocks-basic/-/grapesjs-blocks-basic-0.1.8.tgz",
"integrity": "sha512-Iy5M4qGkEE2llb5ZFxitDolRJ3CrQkcK1SQIjRRlkWIAC99On3T/09iFR7NkpTBfsgfdNwcwnSVOMOhSnVLDtw==",
"peerDependencies": {
"grapesjs": "0.x"
}
},
"node_modules/grapesjs-component-countdown": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/grapesjs-component-countdown/-/grapesjs-component-countdown-0.1.3.tgz",
"integrity": "sha512-TmxDETedUDv1jGqNLn6E768iyp+xAK/XcTId5WpLrmKPFB6aCuWqfTowhtSvyRJJs83OAFMiQIwIu+tEtSWOeA==",
"peerDependencies": {
"grapesjs": "0.x"
}
},
"node_modules/grapesjs-navbar": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/grapesjs-navbar/-/grapesjs-navbar-0.1.5.tgz",
"integrity": "sha512-tVKPtzlTUNCpzk3lHW69II2NsSzLNQd3K32CXssWCP3M1V8fvc9jG/MQECBkd/gMS+DjldD4/4b00ri9F3tKSg==",
"peerDependencies": {
"grapesjs": "0.x"
}
},
"node_modules/grapesjs-plugin-export": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/grapesjs-plugin-export/-/grapesjs-plugin-export-0.1.5.tgz",
"integrity": "sha512-BgvPY68vIDxVv0Z3v1TTikHseMxbfvUvhk5qUHq3a9kFmc2sFfNA6f5NHP9A4v3Tnd4kuwOd2aIn+QNutZrLRg==",
"dependencies": {
"file-saver": "^1.3.2",
"jszip": "^3.0.0"
},
"peerDependencies": {
"grapesjs": "0.x"
}
},
"node_modules/grapesjs-plugin-filestack": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/grapesjs-plugin-filestack/-/grapesjs-plugin-filestack-0.1.1.tgz",
"integrity": "sha512-zRwgHUZd7qDVk92dtizOHxnPiCjBBmsA2BHm0J86tz8uiXf6ou0OoQiB4x4UDewb9ipTkBI6X5nGh/0YET6WLw==",
"peerDependencies": {
"grapesjs": "0.x"
}
},
"node_modules/grapesjs-plugin-forms": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/grapesjs-plugin-forms/-/grapesjs-plugin-forms-0.3.6.tgz",
"integrity": "sha512-JGIxky3if7FLlXBDuX2T2UoWQnvm55PcNp5P2qoX64zlA0+2npz9zbPcj9g1PUHXVPlTXwTr2qu5x3U/pb+xdw==",
"peerDependencies": {
"grapesjs": "0.x"
}
},
"node_modules/grapesjs-preset-webpage": {
"version": "0.1.11",
"resolved": "https://registry.npmjs.org/grapesjs-preset-webpage/-/grapesjs-preset-webpage-0.1.11.tgz",
"integrity": "sha512-36SuSPwu3a09R6YR31Sklmowz/LvzxzGM74WcPd8KPwbs1mAQ4fFuqWKkX/wv8+GhY58Se9EO42yQVMwsZzELA==",
"dependencies": {
"grapesjs-aviary": "^0.1.2",
"grapesjs-blocks-basic": "^0.1.7",
"grapesjs-component-countdown": "^0.1.2",
"grapesjs-navbar": "^0.1.5",
"grapesjs-plugin-export": "^0.1.5",
"grapesjs-plugin-filestack": "^0.1.1",
"grapesjs-plugin-forms": "^0.3.5"
},
"peerDependencies": {
"grapesjs": "0.x"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"node_modules/jszip": {
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.9.1.tgz",
"integrity": "sha512-H9A60xPqJ1CuC4Ka6qxzXZeU8aNmgOeP5IFqwJbQQwtu2EUYxota3LdsiZWplF7Wgd9tkAd0mdu36nceSaPuYw==",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"set-immediate-shim": "~1.0.1"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"node_modules/promise-polyfill": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.3.tgz",
"integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg=="
},
"node_modules/readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/set-immediate-shim": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/underscore": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.3.tgz",
"integrity": "sha512-QvjkYpiD+dJJraRA8+dGAU4i7aBbb2s0S3jA45TFOvg2VgqvdCDd/3N6CqA8gluk1W91GLoXg5enMUx560QzuA=="
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
}
}
}
.DS_Store
node_modules
\ No newline at end of file
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>UndoManager Tests</title>
<link rel="stylesheet" href="resources/qunit.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="resources/jquery-1.9.1.js"></script>
<script src="resources/underscore.js"></script>
<script src="resources/backbone.js"></script>
<script src="../Backbone.Undo.js"></script>
<script src="resources/qunit.js"></script>
<script src="Backbone.Undo.Tests.js"></script>
</body>
</html>
\ No newline at end of file
test("Register and unregister", function () {
var UndoManager = new Backbone.UndoManager;
var model = new Backbone.Model,
collection = new Backbone.Collection;
UndoManager.register(model, collection);
function getRegisteredObjects () {
return UndoManager.objectRegistry.get();
}
strictEqual(getRegisteredObjects().length, 2, "Registering objects with the register method was successful");
UndoManager.unregister(model);
deepEqual(getRegisteredObjects(), [collection], "Unregistering an object with the unregister method was successful");
UndoManager.register(model);
UndoManager.unregisterAll();
strictEqual(getRegisteredObjects().length, 0, "The unregisterAll function worked properly");
var u1 = new Backbone.UndoManager({
register: model
}),
u2 = new Backbone.UndoManager({
register: collection
}),
u3 = new Backbone.UndoManager({
register: [model, collection]
});
deepEqual(u1.objectRegistry.get(), [model], "Registering a single model over the 'register' attribute on instantiation was successful");
deepEqual(u2.objectRegistry.get(), [collection], "Registering a single collection over the 'register' attribute on instantiation was successful");
deepEqual(u3.objectRegistry.get(), [model, collection], "Registering multiple objects over the 'register' attribute on instantiation was successful");
})
test("Start and stop tracking", function () {
var model = new Backbone.Model({
"foo": "bar"
})
var collection = new Backbone.Collection([{"a": "b"}, {"c": "d"}]);
var UndoManager = new Backbone.UndoManager({
register: [model, collection]
});
var before = UndoManager.stack.length;
model.set("hello", "world");
collection.add({"e": "f"});
strictEqual(UndoManager.stack.length, before, "Actions weren't added to the stack, because tracking hasn't started yet");
ok(!UndoManager.isTracking(), "Tracking has not started yet");
UndoManager.startTracking();
ok(UndoManager.isTracking(), "Tracking has now started");
model.set("hello", "you");
collection.remove(collection.last());
var after = UndoManager.stack.length;
strictEqual(after, 2, "Two actions have been added to the stack, because tracking has started");
UndoManager.stopTracking();
model.set("hello", "kitty");
collection.add({"e": "f"});
model.set("hello", "you");
collection.remove(collection.last());
UndoManager.startTracking();
strictEqual(UndoManager.stack.length, after, "No actions were added, because tracking was paused");
UndoManager.unregisterAll();
UndoManager = new Backbone.UndoManager({track: true});
UndoManager.register(model, collection);
model.set("hello", "hello");
collection.add({"g": "h"});
strictEqual(UndoManager.stack.length, 2, "{track:true} on instantiation started tracking at once");
});
test("Undo and redo Model-Changes", function () {
var model = new Backbone.Model({
"t": 1
});
var UndoManager = new Backbone.UndoManager({
track: true,
register: model
});
model.set("t", 2);
UndoManager.undo();
deepEqual(model.toJSON(), {"t": 1}, "Undoing changing a model attribute was successful");
UndoManager.redo();
deepEqual(model.toJSON(), {"t": 2}, "Redoing changing a model attribute was successful");
model.set("x", 1);
UndoManager.undo();
deepEqual(model.toJSON(), {"t": 2}, "Undoing setting a model attribute was successful");
UndoManager.redo();
deepEqual(model.toJSON(), {"t": 2, "x": 1}, "Redoing setting a model attribute was successful");
model.set({
"y": 1,
"x": 2
})
UndoManager.undo();
deepEqual(model.toJSON(), {"t": 2, "x": 1}, "Undoing multiple changes at once was successful");
UndoManager.redo();
deepEqual(model.toJSON(), {"t": 2, "x": 2, "y": 1}, "Redoing multiple changes at once was successful");
UndoManager.undo();
UndoManager.undo();
UndoManager.undo();
deepEqual(model.toJSON(), {"t": 1}, "Calling undo consecutively several times was successful");
UndoManager.undo();
deepEqual(model.toJSON(), {"t": 1}, "Additional undo calls that would be out of the stack's bounds were successfully ignored");
UndoManager.redo();
UndoManager.redo();
UndoManager.redo();
deepEqual(model.toJSON(), {"t": 2, "x": 2, "y": 1}, "Calling redo consecutively several times was successful");
UndoManager.redo();
deepEqual(model.toJSON(), {"t": 2, "x": 2, "y": 1}, "Additional redo calls that would be out of the stack's bounds were successfully ignored");
})
test("Undo and redo Collection-changes", function () {
var collection = new Backbone.Collection([{"t": 1}, {"t": 2}, {"t": 3}]),
UndoManager = new Backbone.UndoManager({
track: true,
register: collection
});
collection.add({"t": 4});
UndoManager.undo();
deepEqual(collection.toJSON(), [{"t": 1}, {"t": 2}, {"t": 3}], "Adding a model to the collection was successfully undone");
UndoManager.redo();
deepEqual(collection.toJSON(), [{"t": 1}, {"t": 2}, {"t": 3}, {"t": 4}], "Adding a model to the collection was successfully redone");
collection.remove(collection.first());
UndoManager.undo();
deepEqual(collection.toJSON(), [{"t": 1}, {"t": 2}, {"t": 3}, {"t": 4}], "Removing a model from the collection was successfully undone");
UndoManager.redo();
deepEqual(collection.toJSON(), [{"t": 2}, {"t": 3}, {"t": 4}], "Removing a model from the collection was successfully redone");
collection.reset([{"a": 1}, {"a": 2}, {"a": 3}]);
UndoManager.undo();
deepEqual(collection.toJSON(), [{"t": 2}, {"t": 3}, {"t": 4}], "Resetting the collection was successfully undone");
UndoManager.redo();
deepEqual(collection.toJSON(), [{"a": 1}, {"a": 2}, {"a": 3}], "Resetting the collection was successfully redone");
collection.first().destroy();
UndoManager.undo();
deepEqual(collection.toJSON(), [{"a": 1}, {"a": 2}, {"a": 3}], "Destroying a model in the collection was successfully undone");
UndoManager.redo();
deepEqual(collection.toJSON(), [{"a": 2}, {"a": 3}], "Destroying a model in the collection was successfully redone");
})
test("ObjectRegistry", function () {
var model = new Backbone.Model,
collection = new Backbone.Collection,
nonBackboneObject = {"something":"else"},
UndoManager = new Backbone.UndoManager,
objectRegistry = UndoManager.objectRegistry;
function compare (arr1, arr2) {
return _.all(arr1, function (v) {
return _.contains(arr2, v);
});
}
objectRegistry.register(model);
ok(objectRegistry.isRegistered(model), "The isRegistered method returns true");
objectRegistry.register(collection);
ok(compare(objectRegistry.get(), [model, collection]), "The get method rightfully returns a list of the registered objects");
objectRegistry.register(model);
objectRegistry.register(collection);
equal(objectRegistry.get().length, 2, "Redundant registrations are correctly ignored");
objectRegistry.register(nonBackboneObject);
ok(compare(objectRegistry.get(), [model, collection, nonBackboneObject]), "Non-Backbone objects are also correctly registered");
objectRegistry.unregister(model);
objectRegistry.unregister(collection);
objectRegistry.unregister(nonBackboneObject);
equal(objectRegistry.get().length, 0, "Unregistering objects works properly");
})
test("Merging UndoManagers", 2, function () {
var main = new Backbone.UndoManager({track:true}),
special = new Backbone.UndoManager({track:true}),
model1 = new Backbone.Model({
"t": 1
}),
model2 = new Backbone.Model,
obj = {
object: model1,
before: {"a": 1},
after: {"b": 1}
};
main.id = main.stack.id = "main";
special.id = special.stack.id = "special";
special.changeUndoType("change", {
"on": function () {
return obj;
}
});
special.register(model1);
main.register(model2);
main.merge(special);
model1.set("t", 2); // Here we're triggering a change event
// Now, the stack-length of main must have been changed
deepEqual(main.stack.at(0).toJSON(), obj, "The action data was manipulated by the changed undotype")
model2.set("t", 2); // Here we're checking if the main undoManager can still write on its stack
deepEqual(main.stack.at(1).toJSON().after, {"t": 2}, "The main undomanager can still write on its own stack")
})
test("Clearing all actions", function () {
var model = new Backbone.Model({
"t": 1
});
var UndoManager = new Backbone.UndoManager({
track: true,
register: model
});
model.set("t", 2);
model.set("t", 3);
model.set("t", 4);
UndoManager.clear();
UndoManager.undo();
deepEqual(model.toJSON(), {"t": 4}, "Clearing actions before undoing was successful");
model.set("t", 2);
model.set("t", 3);
model.set("t", 4);
UndoManager.undo();
UndoManager.undo();
UndoManager.clear();
UndoManager.redo();
deepEqual(model.toJSON(), {"t": 2}, "Clearing actions before redoing was successful");
})
test("Undoing all actions", function () {
var model = new Backbone.Model({
"t": 1
});
var UndoManager = new Backbone.UndoManager({
track: true,
register: model
});
model.set("t", 2);
model.set("t", 3);
model.set("t", 4);
UndoManager.undoAll();
deepEqual(model.toJSON(), {"t": 1}, "Calling undoAll was successful");
UndoManager.redoAll();
deepEqual(model.toJSON(), {"t": 4}, "Calling redoAll was successful");
UndoManager.undo();
UndoManager.undoAll();
UndoManager.undo(true);
deepEqual(model.toJSON(), {"t": 1}, "Mixing undoAll with undo doesn't cause any problems");
UndoManager.undoAll(); // back to the stack's beginning
UndoManager.redo();
UndoManager.redoAll();
UndoManager.redo(true);
deepEqual(model.toJSON(), {"t": 4}, "Mixing redoAll with redo doesn't cause any problems");
UndoManager.undoAll();
UndoManager.undoAll();
UndoManager.redoAll();
UndoManager.redoAll();
deepEqual(model.toJSON(), {"t": 4}, "Calling undoAll and redoAll multiple times doesn't cause any problems");
})
/**
* Async tests for magic fusion
*/
var deferQueue = [];
function defer(fn) {
var args = [].slice.call(arguments);
deferQueue.push(fn, args);
}
function flushDeferQueue() {
_.defer(function () {
var fn = deferQueue.shift(),
args = deferQueue.shift();
if (fn) {
fn.apply(null, args);
flushDeferQueue();
}
})
}
asyncTest("Magic Fusion", 4, function () {
var model = new Backbone.Model({
"t": 1
}), collection = new Backbone.Collection([{"a": 3}]),
UndoManager = new Backbone.UndoManager({
track: true,
register: [model, collection]
}), i;
defer(function () {
start();
// Undo / Redo several changes
for (i = 3; i < 10; i++) {
model.set("t", i);
}
UndoManager.undo(true);
equal(model.get("t"), 1, "Undoing all changes that happened on a model at once succeeded");
UndoManager.redo(true);
equal(model.get("t"), 9, "Redoing all changes that happened on a model at once succeeded");
})
defer(function () {
stop();
collection.add([{"a": 4}, {"a": 5}]);
UndoManager.undo(true);
equal(collection.length, 1, "Undoing adding several models to a collection succeeded");
UndoManager.redo(true);
equal(collection.length, 3, "Redoing adding several models to a collection succeeded");
start();
})
flushDeferQueue();
})