├── .npmignore ├── Procfile ├── views ├── _includes │ ├── copyright.jade │ ├── heads-up.jade │ ├── fork-me-rt.jade │ ├── navbar.jade │ └── syntaxhighlighter.jade ├── static │ └── google1710e155181bbf53.html ├── _layouts │ ├── footer.styl │ └── default.jade ├── reference.styl ├── 404.jade ├── try.styl ├── api.styl ├── common.styl ├── try.jade └── index.styl ├── examples ├── You-get-JUMLY.jm ├── think.jm ├── webserver.jm ├── webapp.jm ├── alt-loop-note-ref.jm ├── apply-css.jm ├── example.html ├── bundle.html └── TLS.html ├── lib ├── ext │ ├── chrome-image-clipper │ │ ├── bg.js │ │ ├── bg.html │ │ ├── manifest.json │ │ ├── style.less │ │ ├── popup.html │ │ ├── page.js │ │ └── popup.js │ └── img-conv │ │ ├── loadJUMLY.coffee │ │ └── img-conv.coffee ├── js │ ├── DiagramLayout.coffee │ ├── NoteElement.coffee │ ├── SequenceLifeline.coffee │ ├── SequenceDiagram.coffee │ ├── RobustnessDiagram.coffee │ ├── DiagramBuilder.coffee │ ├── ClassDiagramBuilder.coffee │ ├── Diagram.coffee │ ├── jumly.coffee │ ├── RobustnessDiagramLayout.coffee │ ├── HTMLElement.coffee │ ├── Class.coffee │ ├── g2d.coffee │ ├── position.coffee │ ├── ClassDiagram.coffee │ ├── RobustnessDiagramBuilder.coffee │ ├── SequenceParticipant.coffee │ ├── SequenceFragment.coffee │ ├── SequenceRef.coffee │ ├── core.coffee │ ├── Relationship.coffee │ ├── api.coffee │ ├── SequenceOccurrence.coffee │ ├── SequenceInteraction.coffee │ ├── SequenceMessage.coffee │ ├── UsecaseDiagram.coffee │ ├── IconElement.coffee │ ├── SequenceDiagramLayout.coffee │ └── SequenceDiagramBuilder.coffee ├── css │ ├── jumly.styl │ ├── commons.styl │ ├── spacing.styl │ ├── robustness.styl │ ├── object.styl │ ├── icon.styl │ ├── sequence.styl │ └── candidate.styl └── entry.js ├── public ├── images │ ├── google.png │ ├── zerply.png │ ├── facebook.png │ ├── linkedin.png │ ├── github_alt.png │ └── twitter_alt.png ├── js │ ├── disqus.js │ └── _ga.js ├── examples │ ├── simple.html │ ├── robustness.html │ ├── styling-arrow.html │ ├── api-scan.html │ └── APNS.html ├── css │ ├── skin.simple.css │ └── skin.fancy.css └── syntaxhighlighter │ ├── MIT-LICENSE │ ├── scripts │ ├── shBrushJScript.js │ ├── shBrushXml.js │ ├── shBrushJava.js │ ├── shBrushRuby.js │ ├── shBrushPython.js │ ├── shBrushBash.js │ ├── shBrushGroovy.js │ ├── shBrushCss.js │ └── shBrushAppleScript.js │ └── styles │ ├── shThemeDefault.css │ └── shCore.css ├── routes ├── index.coffee └── api.coffee ├── .gitignore ├── .gitmodules ├── .env ├── spec ├── ClassDiagramSpec.coffee ├── DiagramSpec.coffee ├── entry.js ├── jasmine-utils.css ├── HTMLElementSpec.coffee ├── SequenceNoteSpec.coffee ├── coreSpec.coffee ├── jasmine-utils.coffee ├── RobustnessDiagramBuilderSpec.coffee ├── index.html ├── SequenceParticipantSpec.coffee ├── SequenceDiagramSpec.coffee ├── issuesSpec.coffee └── apiSpec.coffee ├── Vagrantfile ├── .travis.yml ├── bin └── jumly ├── .heroku.js ├── webpack.config.js ├── Gruntfile.coffee ├── package.json ├── Makefile ├── karma.conf.js ├── app.coffee └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node .heroku.js 2 | -------------------------------------------------------------------------------- /views/_includes/copyright.jade: -------------------------------------------------------------------------------- 1 | © 2012 Tomotaka Sakuma 2 | -------------------------------------------------------------------------------- /examples/You-get-JUMLY.jm: -------------------------------------------------------------------------------- 1 | @found "You", -> 2 | @message "get", "JUMLY" 3 | -------------------------------------------------------------------------------- /lib/ext/chrome-image-clipper/bg.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | console.log("bg.js"); 3 | }); 4 | -------------------------------------------------------------------------------- /views/static/google1710e155181bbf53.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google1710e155181bbf53.html -------------------------------------------------------------------------------- /public/images/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmtk75/jumly/HEAD/public/images/google.png -------------------------------------------------------------------------------- /public/images/zerply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmtk75/jumly/HEAD/public/images/zerply.png -------------------------------------------------------------------------------- /public/images/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmtk75/jumly/HEAD/public/images/facebook.png -------------------------------------------------------------------------------- /public/images/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmtk75/jumly/HEAD/public/images/linkedin.png -------------------------------------------------------------------------------- /public/images/github_alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmtk75/jumly/HEAD/public/images/github_alt.png -------------------------------------------------------------------------------- /public/images/twitter_alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmtk75/jumly/HEAD/public/images/twitter_alt.png -------------------------------------------------------------------------------- /routes/index.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (ctx)-> 2 | html: (name)-> 3 | (req, res)-> res.render name, ctx 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .*.swp 3 | .*.un~ 4 | views/static/*.css 5 | build 6 | dist 7 | vendor 8 | .vagrant 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "public/bootstrap"] 2 | path = public/bootstrap 3 | url = https://github.com/twbs/bootstrap 4 | -------------------------------------------------------------------------------- /examples/think.jm: -------------------------------------------------------------------------------- 1 | @found "You", -> 2 | @message "Think", -> 3 | @message "Write your idea", "JUMLY", -> 4 | @create "Diagram" 5 | -------------------------------------------------------------------------------- /lib/js/DiagramLayout.coffee: -------------------------------------------------------------------------------- 1 | class DiagramLayout 2 | 3 | DiagramLayout::layout = (diagram)-> 4 | @diagram = diagram 5 | @_layout?() 6 | 7 | module.exports = DiagramLayout 8 | -------------------------------------------------------------------------------- /lib/css/jumly.styl: -------------------------------------------------------------------------------- 1 | @import "./commons" 2 | @import "./object" 3 | @import "./icon" 4 | @import "./sequence" 5 | @import "./spacing" 6 | @import "./candidate" 7 | @import "./robustness" 8 | -------------------------------------------------------------------------------- /lib/css/commons.styl: -------------------------------------------------------------------------------- 1 | .centering 2 | margin-left: auto 3 | margin-right: auto 4 | 5 | .unimportant 6 | z-index: 0 7 | 8 | .primary_border 9 | border: 2px solid #808080 10 | 11 | -------------------------------------------------------------------------------- /lib/css/spacing.styl: -------------------------------------------------------------------------------- 1 | hspace = 40px 2 | hbw = 1px 3 | 4 | .sequence-diagram .participant:not(:first-child) 5 | margin-left hspace - hbw*2 6 | 7 | .sequence-diagram .participant:first-child 8 | margin-left 0 9 | -------------------------------------------------------------------------------- /views/_includes/heads-up.jade: -------------------------------------------------------------------------------- 1 | .alert.alert-success 2 | strong Heads up! 3 | span  REST API to generate image is available now! 4 | | GET http://goo.gl/HRYFy 5 | -------------------------------------------------------------------------------- /lib/css/robustness.styl: -------------------------------------------------------------------------------- 1 | .robustness-diagram 2 | position relative 3 | 4 | svg 5 | circle, path 6 | stroke gray 7 | stroke-width 1.5 8 | path 9 | fill none 10 | circle 11 | fill white 12 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | nvm use 0.12 2 | PATH=$PATH:`pwd`/node_modules/.bin 3 | PATH=$PATH:`pwd`/node_modules/karma/bin 4 | export CHROME_BIN="/opt/homebrew-cask/Caskroom/google-chrome/latest/Google Chrome.app/Contents/MacOS/Google Chrome" \ 5 | -------------------------------------------------------------------------------- /spec/ClassDiagramSpec.coffee: -------------------------------------------------------------------------------- 1 | ClassDiagram = require "ClassDiagram.coffee" 2 | 3 | describe "ClassDiagram", -> 4 | 5 | it "should have data() of jQuery", -> 6 | diag = new ClassDiagram 7 | expect(diag.data).not.toBeUndefined() 8 | -------------------------------------------------------------------------------- /lib/ext/chrome-image-clipper/bg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bg 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/webserver.jm: -------------------------------------------------------------------------------- 1 | @found "User", -> 2 | @message "search", "Browser", -> 3 | @create asynchronous:"connection", "Web Server" 4 | @message "GET", "Web Server", -> 5 | @message "find the resource", -> @reply "" 6 | @reply "", "User" 7 | -------------------------------------------------------------------------------- /views/_includes/fork-me-rt.jade: -------------------------------------------------------------------------------- 1 | .fork-me 2 | a(href="//github.com/tmtk75/jumly", target='_blank') 3 | img(style="position: absolute; top: 0; right: 0; border: 0;",src="//s3.amazonaws.com/github/ribbons/forkme_right_green_007200.png",alt="Fork me on GitHub") 4 | -------------------------------------------------------------------------------- /examples/webapp.jm: -------------------------------------------------------------------------------- 1 | @found "Browser", -> 2 | @message "http request", "HTTP Server", -> 3 | @create "HTTP Session", -> 4 | @message "init" 5 | @message "aquire lock", "Database" 6 | @message "do something" 7 | @message "release lock", "Database" 8 | @reply "", "Browser" 9 | -------------------------------------------------------------------------------- /examples/alt-loop-note-ref.jm: -------------------------------------------------------------------------------- 1 | @found "You", -> 2 | @alt 3 | "[found]": -> 4 | @loop -> 5 | @message "request", "HTTP Server" 6 | @note "NOTE: This doesn't make sense :)" 7 | "[missing]": -> 8 | @message "new", "HTTP Session" 9 | @ref "respond resource" 10 | -------------------------------------------------------------------------------- /lib/js/NoteElement.coffee: -------------------------------------------------------------------------------- 1 | HTMLElement = require "HTMLElement.coffee" 2 | 3 | class NoteElement extends HTMLElement 4 | constructor: (args, attrs)-> 5 | super args, (me)-> 6 | me.append($("
").addClass("name")) 7 | @css attrs.css if attrs 8 | 9 | module.exports = NoteElement 10 | -------------------------------------------------------------------------------- /lib/entry.js: -------------------------------------------------------------------------------- 1 | require("SequenceDiagramBuilder.coffee"); 2 | require("api.coffee") 3 | 4 | //require("jumly.styl") 5 | require("commons.styl"); 6 | require("candidate.styl"); 7 | require("icon.styl"); 8 | require("object.styl"); 9 | require("sequence.styl"); 10 | require("robustness.styl"); 11 | require("spacing.styl"); 12 | 13 | -------------------------------------------------------------------------------- /lib/js/SequenceLifeline.coffee: -------------------------------------------------------------------------------- 1 | HTMLElement = require "HTMLElement.coffee" 2 | 3 | class SequenceLifeline extends HTMLElement 4 | constructor: (@_object)-> 5 | self = this 6 | super null, (me)-> 7 | me.append($("
").addClass "line") 8 | .width self._object.width() 9 | 10 | module.exports = SequenceLifeline 11 | -------------------------------------------------------------------------------- /views/_includes/navbar.jade: -------------------------------------------------------------------------------- 1 | .navbar 2 | .navbar-inner 3 | .brand(href="#") 4 | a.logo(href="/") JUMLY 5 | ul.nav 6 | li: a(href="/reference.html") Reference Manual 7 | li: a(href="/api.html") API Document 8 | li: a(href="/try.html") TryJUMLY 9 | li: a(href="https://tapioca.herokuapp.com/") tapioca 10 | -------------------------------------------------------------------------------- /spec/DiagramSpec.coffee: -------------------------------------------------------------------------------- 1 | utils = require "./jasmine-utils.coffee" 2 | Diagram = require "Diagram.coffee" 3 | 4 | describe "Diagram", -> 5 | beforeEach -> 6 | @diagram = new Diagram 7 | utils.matchers this 8 | 9 | it "has data() of jQuery", -> 10 | expect(@diagram.data).toBeDefined() 11 | 12 | it "has .diagram", -> 13 | expect(@diagram).haveClass "diagram" 14 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | # 4 | # https://dev.windows.com/en-us/microsoft-edge/tools/vms/mac/ 5 | # 6 | Vagrant.configure(2) do |config| 7 | config.vm.box = "win7" 8 | #config.vm.box = "win10" 9 | config.vm.provider "virtualbox" do |vb| 10 | vb.gui = true 11 | end 12 | config.vm.boot_timeout = 1 13 | config.vm.synced_folder ".", "shared" 14 | end 15 | -------------------------------------------------------------------------------- /public/js/disqus.js: -------------------------------------------------------------------------------- 1 | var disqus_shortname = 'jumly'; // required: replace example with your forum shortname 2 | (function() { 3 | var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; 4 | dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js'; 5 | (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); 6 | })(); 7 | -------------------------------------------------------------------------------- /examples/apply-css.jm: -------------------------------------------------------------------------------- 1 | @found "Browser", -> 2 | @alt { 3 | "[200]": -> @message "GET href resources", "HTTP Server" 4 | "[301]": -> @ref "GET the moved page" 5 | "[404]": -> @ref "show NOT FOUND" 6 | } 7 | @find(".ref").css(width:256, "padding-bottom":4) 8 | .find(".tag").css float:"left" 9 | get_the_moved_page.css "background-color":"#80c080" 10 | show_not_found.css "background-color":"#f0b0b0" 11 | -------------------------------------------------------------------------------- /lib/js/SequenceDiagram.coffee: -------------------------------------------------------------------------------- 1 | HTMLElement = require "HTMLElement.coffee" 2 | Diagram = require "Diagram.coffee" 3 | 4 | class SequenceDiagram extends Diagram 5 | constructor: -> 6 | super() 7 | 8 | #JUMLY.def ".sequence-diagram", SequenceDiagram 9 | 10 | SequenceDiagram::gives = (query)-> 11 | e = @find(query) 12 | f = jumly.lang._of e, query 13 | {of: f} 14 | 15 | module.exports = SequenceDiagram 16 | -------------------------------------------------------------------------------- /public/examples/simple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /views/_includes/syntaxhighlighter.jade: -------------------------------------------------------------------------------- 1 | script(src='/public/syntaxhighlighter/scripts/shCore.js') 2 | script(src='/public/syntaxhighlighter/scripts/shBrushJScript.js') 3 | script(src='/public/syntaxhighlighter/scripts/shBrushXml.js') 4 | :coffeescript 5 | $ -> 6 | #SyntaxHighlighter.config.clipboardSwf = '/public/syntaxhighlighter/scripts/clipboard.swf' 7 | SyntaxHighlighter.config.toolbar = false 8 | SyntaxHighlighter.all() 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.12" 5 | 6 | script: npm run test 7 | 8 | before_install: 9 | - export CHROME_BIN=chromium-browser 10 | - export DISPLAY=:99.0 11 | - sh -e /etc/init.d/xvfb start 12 | 13 | - export KARMA_OPTIONS="--browsers ChromeTravis" 14 | 15 | install: 16 | - npm install 17 | - make dist/jumly.css vendor/coffee-script.js 18 | 19 | branches: 20 | only: 21 | - master 22 | -------------------------------------------------------------------------------- /lib/js/RobustnessDiagram.coffee: -------------------------------------------------------------------------------- 1 | core = require "core.coffee" 2 | Diagram = require "Diagram.coffee" 3 | IconElement = require "IconElement.coffee" 4 | 5 | class RobustnessDiagram extends Diagram 6 | 7 | RobustnessDiagram::_node_of = (n, k)-> 8 | id = core._to_id n 9 | ref = core._to_ref id 10 | return this[ref] if this[ref] 11 | 12 | e = new IconElement n, kind:k 13 | @_reg_by_ref id, e 14 | e 15 | 16 | 17 | module.exports = RobustnessDiagram 18 | -------------------------------------------------------------------------------- /spec/entry.js: -------------------------------------------------------------------------------- 1 | require("ClassDiagramSpec.coffee"); 2 | require("DiagramSpec.coffee"); 3 | require("HTMLElementSpec.coffee"); 4 | require("RobustnessDiagramBuilderSpec.coffee"); 5 | require("SequenceDiagramBuilderSpec.coffee"); 6 | require("SequenceDiagramLayoutSpec.coffee"); 7 | require("SequenceDiagramSpec.coffee"); 8 | require("SequenceParticipantSpec.coffee"); 9 | require("SequenceNoteSpec.coffee"); 10 | require("apiSpec.coffee"); 11 | require("coreSpec.coffee"); 12 | require("issuesSpec.coffee"); 13 | -------------------------------------------------------------------------------- /public/examples/robustness.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /lib/js/DiagramBuilder.coffee: -------------------------------------------------------------------------------- 1 | class DiagramBuilder 2 | 3 | core = require "core.coffee" 4 | CoffeeScript = require "coffee-script" 5 | 6 | DiagramBuilder::build = (script)-> 7 | (-> eval CoffeeScript.compile script).apply this, [] 8 | @_diagram 9 | 10 | DiagramBuilder::diagram = -> 11 | @_diagram 12 | 13 | DiagramBuilder::_refer = (ref, adv)-> 14 | id = core._normalize(adv.by).id 15 | @_diagram._reg_by_ref id, ref 16 | r = core._to_ref id 17 | @_diagram._var r, ref 18 | 19 | 20 | module.exports = DiagramBuilder 21 | -------------------------------------------------------------------------------- /examples/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /spec/jasmine-utils.css: -------------------------------------------------------------------------------- 1 | #diagram-containers { 2 | position: absolute; 3 | min-width: 192px; 4 | max-width: 386px; 5 | right: 8px; 6 | padding: 8px; 7 | border-radius: 3px; 8 | background-color: #DDD; 9 | opacity: 0.85; 10 | } 11 | 12 | #diagram-containers .description { 13 | font-size: 11; 14 | font-family: Monaco,mono,monospace; 15 | text-align: right; 16 | } 17 | 18 | #diagram-containers .spec-diagram-container { 19 | padding: 4px; 20 | border-radius: 3px; 21 | border: solid 1px gray; 22 | margin-bottom: 2px; 23 | } 24 | -------------------------------------------------------------------------------- /lib/ext/chrome-image-clipper/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "JUMLY image clipper", 4 | "version": "0.0.1", 5 | "description": "JUMLY", 6 | "content_scripts": [ 7 | { 8 | "matches": ["http://*/*", "https://*/*"], 9 | "css": [], 10 | "js": ["jquery.js", "page.js"] 11 | } 12 | ], 13 | "permissions": [ 14 | "https://*/*", 15 | "http://*/*", 16 | "tabs" 17 | ], 18 | "background": {"page": "bg.html"}, 19 | "browser_action": { 20 | "default_popup": "popup.html" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/js/ClassDiagramBuilder.coffee: -------------------------------------------------------------------------------- 1 | DiagramBuilder = require "DiagramBuilder.coffee" 2 | core = require "core.coffee" 3 | 4 | class ClassDiagramBuilder extends DiagramBuilder 5 | constructor: (@diagram) -> 6 | 7 | ClassDiagramBuilder::def = (props)-> 8 | @diagram.declare core_.normalize props 9 | 10 | ##Deprecated 11 | ClassDiagramBuilder::start = (acts)-> acts.apply this, [] 12 | 13 | DSL = -> 14 | DSL type:".class-diagram", compileScript: (script) -> 15 | b = new ClassDiagramBuilder 16 | b.build script.html() 17 | 18 | 19 | module.exports = ClassDiagramBuilder 20 | -------------------------------------------------------------------------------- /public/css/skin.simple.css: -------------------------------------------------------------------------------- 1 | .simple .sequence-diagram .participant, 2 | .simple .sequence-diagram .occurrence { 3 | box-shadow: none; 4 | -webkit-box-shadow: none; 5 | -moz-box-shadow: none; 6 | -o-box-shadow: none; 7 | } 8 | .simple .sequence-diagram .participant { 9 | background-color: transparent; 10 | } 11 | .simple .sequence-diagram .participant { 12 | border-top-color: transparent; 13 | border-right-color: transparent; 14 | border-left-color: transparent; 15 | background-color: transparent; 16 | } 17 | .simple .sequence-diagram .occurrence { 18 | background-color: #fff; 19 | } 20 | -------------------------------------------------------------------------------- /views/_layouts/footer.styl: -------------------------------------------------------------------------------- 1 | 2 | footer 3 | padding-top 30px 4 | padding-bottom 60px 5 | THICK-DARK() 6 | 7 | .copyright 8 | text-align center 9 | font-size smaller 10 | 11 | _A_BG() 12 | background-color #ddd 13 | border 1px solid rgba(0,0,0,0.2) 14 | margin-top 2px 15 | border-radius 5px 16 | padding 10px 20px 17 | position relative 18 | 19 | _L_HEADER() 20 | background-color #333 21 | color white 22 | padding 8px 10px 23 | border 1px solid #111 24 | border-radius 4px 25 | 26 | _M_HEADER() 27 | background-color #690 28 | color white 29 | padding 8px 10px 30 | border-radius 4px 31 | -------------------------------------------------------------------------------- /views/reference.styl: -------------------------------------------------------------------------------- 1 | @import common 2 | @import "_layouts/footer" 3 | 4 | h1, h2, h3 5 | margin-top 0 6 | font-family Russo One 7 | 8 | ul 9 | font-family mplus-1p,verdana 10 | 11 | .menu 12 | ul 13 | list-style none 14 | > ul 15 | margin-left 0 16 | 17 | #_directives 18 | margin-top 3em 19 | 20 | .subsection 21 | margin-bottom 5em 22 | .sequence-diagram:not(:last-child) 23 | margin-bottom 2em 24 | .occurrence.compact 25 | padding-bottom 2px 26 | 27 | h3 28 | _L_HEADER() 29 | 30 | .subsection 31 | h4 32 | _M_HEADER() 33 | 34 | section 35 | margin-bottom 5em 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/bundle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /lib/css/object.styl: -------------------------------------------------------------------------------- 1 | .centering 2 | margin-left: auto 3 | margin-right: auto 4 | 5 | .unimportant 6 | z-index: 0 7 | 8 | .primary_border 9 | border: 2px solid #808080 10 | 11 | .diagram 12 | position: relative 13 | font-size: 10pt 14 | 15 | .diagram .participant.iconified 16 | text-align: center 17 | 18 | .diagram .participant.iconified .name 19 | height: auto 20 | padding-top: .4em 21 | padding-bottom: .4em 22 | 23 | .diagram .participant.iconified .icon-container 24 | position: relative 25 | margin-bottom: 5px 26 | 27 | .diagram .participant.iconified canvas.icon 28 | border: 0 29 | padding-top: 2px 30 | padding-bottom: 2px 31 | 32 | -------------------------------------------------------------------------------- /lib/js/Diagram.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | HTMLElement = require "HTMLElement.coffee" 3 | core = require "core.coffee" 4 | 5 | class Diagram extends HTMLElement 6 | constructor: -> 7 | super() 8 | @addClass "diagram" 9 | 10 | ## Enable var with given name 11 | Diagram::_var = (varname, e)-> 12 | eval "#{varname} = e" 13 | 14 | ## Enable ref name from id 15 | Diagram::_reg_by_ref = (id, obj)-> 16 | exists = (id, diag)-> $("##{id}").length > 0 17 | ref = core._to_ref id 18 | throw new Error("Already exists for '#{ref}'") if this[ref] 19 | throw new Error("Element which has same ID(#{id}) already exists in the document.") if exists id, this 20 | this[ref] = obj 21 | ref 22 | 23 | 24 | module.exports = Diagram 25 | -------------------------------------------------------------------------------- /lib/js/jumly.coffee: -------------------------------------------------------------------------------- 1 | #= require core 2 | #= require g2d 3 | #= require position 4 | #= require HTMLElement 5 | #= require Diagram 6 | #= require DiagramBuilder 7 | #= require DiagramLayout 8 | #= require NoteElement 9 | #= require Relationship 10 | #= require SequenceLifeline 11 | #= require SequenceMessage 12 | #= require SequenceInteraction 13 | #= require SequenceOccurrence 14 | #= require SequenceParticipant 15 | #= require SequenceFragment 16 | #= require SequenceRef 17 | #= require SequenceDiagram 18 | #= require SequenceDiagramBuilder 19 | #= require SequenceDiagramLayout 20 | #= require IconElement 21 | #= require RobustnessDiagram 22 | #= require RobustnessDiagramBuilder 23 | #= require RobustnessDiagramLayout 24 | #= require api 25 | -------------------------------------------------------------------------------- /bin/jumly: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | script_path=$1 3 | format=$2 4 | encoding=$3 5 | 6 | if [ -z "$script_path" ]; then 7 | cat< [format] [encoding] 9 | script-path ./examples/You-get-JUMLY.jm 10 | format png | jpg 11 | encoding image | base64 | html 12 | 13 | ex) 14 | `basename $0` examples/You-get-JUMLY.jm png 15 | `basename $0` examples/You-get-JUMLY.jm png base64 16 | 17 | EOF 18 | exit 19 | fi 20 | 21 | here=`pwd` 22 | cwd=$(dirname `\ls -l $0 | awk '{if ($11) {print $11} else {print $9}}'`)/.. 23 | cd $cwd 24 | 25 | npm_bin=$cwd/node_modules/.bin 26 | $npm_bin/phantomjs \ 27 | $cwd/lib/ext/img-conv/img-conv.coffee \ 28 | $script_path \ 29 | $format \ 30 | $encoding 31 | -------------------------------------------------------------------------------- /.heroku.js: -------------------------------------------------------------------------------- 1 | //require('newrelic'); 2 | // $ heroku create --stack cedar 3 | // $ heroku config:add NODE_ENV=heroku 4 | 5 | /* 6 | var cluster = require('cluster'); 7 | var prefork_count = require('os').cpus().length; 8 | 9 | if (cluster.isMaster) { 10 | console.log("prefork-count:", prefork_count); 11 | for (var i = 0; i < prefork_count; i++) { 12 | cluster.fork(); 13 | } 14 | } else { 15 | var coffee = require('coffee-script'); 16 | var fs = require('fs'); 17 | var app = coffee.compile(fs.readFileSync('./app.coffee').toString()); 18 | eval(app); 19 | } 20 | */ 21 | var coffee = require('coffee-script'); 22 | var fs = require('fs'); 23 | require('coffee-script/register'); 24 | var app = coffee.compile(fs.readFileSync('./app.coffee').toString()); 25 | eval(app); 26 | -------------------------------------------------------------------------------- /views/404.jade: -------------------------------------------------------------------------------- 1 | extends _layouts/default 2 | 3 | block styles 4 | :stylus 5 | @import common 6 | link(rel='stylesheet', href="/public/bootstrap/docs/assets/css/bootstrap-responsive.css") 7 | style 8 | .diagram { 9 | margin-left: auto; 10 | margin-right: auto; 11 | } 12 | .diagram-container { margin-top: 1em; } 13 | .back-to-home { 14 | text-align: center; 15 | margin-top: 1em; 16 | } 17 | 18 | block content 19 | .container 20 | .row 21 | .span12 22 | .diagram-container 23 | :jumly(type=sequence) 24 | @found "You", -> 25 | @message "GET #{path}", "JUMLY", -> 26 | @reply "404" 27 | 28 | .span12 29 | .back-to-home 30 | |Not Found: #{path}
31 | a(href="/") Back to home 32 | -------------------------------------------------------------------------------- /public/js/_ga.js: -------------------------------------------------------------------------------- 1 | var _gaq = _gaq || []; 2 | var pluginUrl = '//www.google-analytics.com/plugins/ga/inpage_linkid.js'; 3 | _gaq.push(['_require', 'inpage_linkid', pluginUrl]); 4 | _gaq.push(['_setAccount', 'UA-27755043-3']); 5 | _gaq.push(['_trackPageview']); 6 | 7 | var now = new Date; 8 | function zp(n) {return n/10 >= 1 ? n : "0" + n} 9 | var val = now.getFullYear() + "-" + zp(now.getMonth() + 1) + "-" + (Math.floor(now.getDate()/10) + 1) 10 | _gaq.push(['_setCustomVar', 3, 'cohort', val, 1]); // val should be "2013-01-3" 11 | 12 | (function() { 13 | var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; 14 | ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 15 | var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); 16 | })(); 17 | -------------------------------------------------------------------------------- /lib/js/RobustnessDiagramLayout.coffee: -------------------------------------------------------------------------------- 1 | DiagramLayout = require "DiagramLayout.coffee" 2 | pos = require "position.coffee" 3 | 4 | class RobustnessDiagramLayout extends DiagramLayout 5 | 6 | RobustnessDiagramLayout::_layout = -> 7 | elems = @diagram.find(".element") 8 | p = @diagram.offset() 9 | p.left += (parseInt @diagram.css "padding-left")*2 10 | p.top += (parseInt @diagram.css "padding-top")*2 11 | elems.each (i, e)-> 12 | $(e).css(position:"absolute") 13 | .offset 14 | left:p.left + (i % 3) * 120 15 | top:p.top + (i / 3) * 100 16 | 17 | mlr = pos.mostLeftRight(elems, true) 18 | mtb = pos.mostTopBottom(elems, true) 19 | @diagram.width(mlr.width()) 20 | .height(mtb.height()) 21 | 22 | @diagram.find(".relationship").each (i, e)-> $(e).data("_self").render() 23 | 24 | 25 | module.exports = RobustnessDiagramLayout 26 | -------------------------------------------------------------------------------- /public/examples/styling-arrow.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 22 | 23 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /lib/js/HTMLElement.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | 3 | _to_fname = (ctor)-> 4 | if ctor.name 5 | return ctor.name 6 | a = (ctor + "").match(/function ([a-zA-Z_-]+)/) 7 | if a 8 | return a[1] 9 | console.error "cannot ensure class name", ctor 10 | 11 | class HTMLElement 12 | constructor: (args, f)-> 13 | cls = HTMLElement.to_css_name (_to_fname @constructor) 14 | me = $.extend this, root = $("
").addClass cls 15 | f? me 16 | me.find(".name").text args if args 17 | me.data "_self", me 18 | 19 | @to_css_name: (s)-> 20 | (if s.match /Diagram$/ 21 | s.replace(/Diagram$/, "-Diagram") 22 | else if s.match /NoteElement/ 23 | s.replace(/Element$/, "") 24 | else 25 | s.replace(/^[A-Z][a-z]+/, "")) 26 | .toLowerCase() 27 | 28 | HTMLElement::preferred_width = -> 29 | @find("> *:eq(0)").outerWidth() ## w/o margin 30 | 31 | module.exports = HTMLElement 32 | -------------------------------------------------------------------------------- /spec/HTMLElementSpec.coffee: -------------------------------------------------------------------------------- 1 | HTMLElement = require "HTMLElement.coffee" 2 | $ = require "jquery" 3 | 4 | describe "HTMLElement", -> 5 | 6 | it "should have data() of jQuery", -> 7 | elem = new HTMLElement 8 | expect(elem.data).not.toBeUndefined() 9 | expect(elem.data).toEqual $("
").data 10 | 11 | describe "to_css_name", -> 12 | f = HTMLElement.to_css_name 13 | 14 | it "returns css class name for given function", -> 15 | expect(f "SequenceParticipant").toBe "participant" 16 | expect(f "SequenceInteraction").toBe "interaction" 17 | expect(f "SequenceOccurrence").toBe "occurrence" 18 | 19 | expect(f "HTMLElement").toBe "htmlelement" 20 | 21 | it "has Diagram get a hyphenated suffixthe", -> 22 | expect(f "SequenceDiagram").toBe "sequence-diagram" 23 | 24 | it "returns .note for NoteElement", -> 25 | expect(f "NoteElement").toBe "note" 26 | 27 | -------------------------------------------------------------------------------- /spec/SequenceNoteSpec.coffee: -------------------------------------------------------------------------------- 1 | core = require "core.coffee" 2 | utils = require "./jasmine-utils.coffee" 3 | SequenceDiagramLayout = require "SequenceDiagramLayout.coffee" 4 | SequenceDiagramBuilder = require "SequenceDiagramBuilder.coffee" 5 | 6 | describe "SequenceNote", -> 7 | 8 | div = utils.div this 9 | 10 | beforeEach -> 11 | @layout = new SequenceDiagramLayout 12 | @builder = new SequenceDiagramBuilder 13 | 14 | describe "issue#18", -> 15 | beforeEach -> 16 | @diagram = @builder.build """ 17 | @found "App User", -> 18 | @note "Product ID & price" 19 | @create "A" 20 | """ 21 | div.append @diagram 22 | @layout.layout @diagram 23 | 24 | it "is a more than 2px gap between bottom of note and top of participant", -> 25 | n = @diagram.find(".note:eq(0)") 26 | p = @diagram.find(".participant:eq(1)") 27 | expect(utils.top p).toBeGreaterThan (utils.bottom n) + 2 28 | -------------------------------------------------------------------------------- /examples/TLS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/ext/chrome-image-clipper/style.less: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0px; 3 | font-size: 14px; 4 | line-height: 20px; 5 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | 7 | border-top: solid 6px #8CC84B; 8 | background-color: #eee; 9 | min-width: 386px; 10 | } 11 | header { 12 | h2 { 13 | text-align: center; 14 | font-family: Coda,Tahoma,sans-serif; 15 | font-size: 18px; 16 | text-shadow: -1px 1px 0 rgba(255,255,255,1); 17 | } 18 | } 19 | #notification { 20 | text-align: center; 21 | font-weight: bold; 22 | &.succeeded { 23 | color: darken(#8CC84B, 20%); 24 | } 25 | &.failed { 26 | color: #ff6000; 27 | } 28 | } 29 | .copyright { 30 | background-color: #33342d; 31 | color: #d2d8ba; 32 | border-top: solid 1px darken(white, 33%); 33 | border-bottom: solid 1px darken(#33342d, 7%); 34 | text-align: center; 35 | font-size: smaller; 36 | margin-top: 0.5em; 37 | padding-top: 0.5em; 38 | padding-bottom: 1em; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /spec/coreSpec.coffee: -------------------------------------------------------------------------------- 1 | core = require "core.coffee" 2 | 3 | describe "core", -> 4 | 5 | describe "_normalize", -> 6 | 7 | describe "string", -> 8 | 9 | it "returns object has id and name", -> 10 | expect(core._normalize "foo").toEqual id:"foo", name:"foo" 11 | 12 | it "pyphenates unacceptable characters to '-' for id", -> 13 | expect(core._normalize "a 1").toEqual id:"a-1", name:"a 1" 14 | expect(core._normalize "a-1").toEqual id:"a-1", name:"a-1" 15 | expect(core._normalize "a_1").toEqual id:"a_1", name:"a_1" 16 | 17 | it "starts with 0 and a space", -> 18 | expect(core._normalize "0 a").toEqual id:"0-a", name:"0 a" 19 | 20 | it "starts with 0 and two spaces", -> 21 | expect(core._normalize "0 a b").toEqual id:"0-a-b", name:"0 a b" 22 | 23 | describe "_to_ref", -> 24 | 25 | describe "string", -> 26 | 27 | it "starts with 0 and hyphens", -> 28 | expect(core._to_ref "0-a-b").toEqual "_0_a_b" 29 | 30 | -------------------------------------------------------------------------------- /lib/js/Class.coffee: -------------------------------------------------------------------------------- 1 | ### 2 |
3 | abstract 4 | UMLObject 5 |
    6 |
  • name
  • 7 |
  • stereotypes
  • 8 |
9 |
    10 |
  • activate
  • 11 |
  • isLeftAt(a)
  • 12 |
  • isRightAt(a)
  • 13 |
  • iconify(fixture, styles)
  • 14 |
  • lost
  • 15 |
16 |
17 | ### 18 | 19 | HTMLElement = require "HTMLElement.coffee" 20 | 21 | class Class extends HTMLElement 22 | 23 | Class::_build_ = (div)-> 24 | icon = $("
") 25 | .addClass("icon") 26 | .append($("
").addClass "stereotype") 27 | .append($("
").addClass "name") 28 | .append($("
    ").addClass "attrs") 29 | .append($("
      ").addClass "methods") 30 | div.addClass("object") 31 | .append(icon) 32 | 33 | def = -> 34 | #def ".class-diagram", ClassDiagram 35 | def ".class", Class 36 | 37 | module.exports = Class 38 | -------------------------------------------------------------------------------- /views/try.styl: -------------------------------------------------------------------------------- 1 | @import common 2 | @import "_layouts/footer" 3 | 4 | textarea 5 | width 100% 6 | font-family "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important 7 | line-height 16px 8 | margin-bottom 0 9 | 10 | .logo 11 | font-size 24px 12 | margin-top 0 13 | .desc 14 | margin-left 10px 15 | font-weight normal 16 | font-family "Helvetica Neue", Helvetica, Arial, sans-serif 17 | color #888 18 | font-size 16px 19 | 20 | #diagram_container 21 | padding 2px 22 | .diagram 23 | margin-left auto 24 | margin-right auto 25 | 26 | .failed 27 | background-color #ffd0e0 28 | 29 | #notification 30 | height 30px 31 | color #ff4000 32 | 33 | #open-snippet 34 | width 100% 35 | margin-top 1px 36 | padding-left 8px - 2px 37 | padding-right 8px - 2px 38 | L-EMBOSS() 39 | 40 | #snippet 41 | pre 42 | font-size 12px 43 | line-height 14px 44 | 45 | #download_as_png 46 | font-size 12px 47 | font-weight bold 48 | width 80px * 6 - 20px - 6px * 2 49 | 50 | 51 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | 3 | module.exports = { 4 | entry: { 5 | lib: "./lib/entry.js", 6 | spec: "./spec/entry.js", 7 | }, 8 | output: { 9 | path: path.join(__dirname, './dist'), 10 | path: "./dist", 11 | publicPath: '.', 12 | filename: 'bundle.[name].js', 13 | //chunkFilename: '[chunkhash].js' 14 | }, 15 | externals: { 16 | // require("jquery") & require("coffee-script") are external and available on the global 17 | "jquery": "jQuery", 18 | "coffee-script": "CoffeeScript" 19 | }, 20 | module: { 21 | loaders: [ 22 | { test: /\.css$/, loader: "style!css" }, 23 | { test: /\.styl$/, loader: "style-loader!css-loader!stylus-loader" }, 24 | { test: /\.coffee$/, loader: "coffee-loader" }, 25 | ] 26 | }, 27 | resolve: { 28 | modulesDirectories: [ 29 | "lib/js", 30 | "lib/css", 31 | "node_modules", 32 | "spec", 33 | ] 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /lib/css/icon.styl: -------------------------------------------------------------------------------- 1 | .centering 2 | margin-left: auto 3 | margin-right: auto 4 | 5 | .unimportant 6 | z-index: 0 7 | 8 | .primary_border 9 | border: 2px solid #808080 10 | 11 | .icon-squaresize 12 | width: 24px 13 | height: 24px 14 | 15 | .icon .square 16 | width: 24px 17 | height: 24px 18 | margin-left: 6px 19 | position: relative 20 | background-color: #808080 21 | -moz-border-radius: 2px 22 | -webkit-border-radius: 2px 23 | -o-border-radius: 2px 24 | border-radius: 2px 25 | 26 | .icon .square.cross 27 | width: 4px 28 | margin-left: 4px 29 | 30 | .icon .square::before 31 | content: "" 32 | display: block 33 | width: 100% 34 | height: 100% 35 | -moz-transform: rotate(90deg) 36 | -webkit-transform: rotate(90deg) 37 | -o-transform: rotate(90deg) 38 | transform: rotate(90deg) 39 | background-color: #808080 40 | 41 | .icon .stop 42 | width: 24px 43 | height: 24px 44 | 45 | .icon .stop .square.cross 46 | -moz-transform: rotate(45deg) 47 | -webkit-transform: rotate(45deg) 48 | -o-transform: rotate(45deg) 49 | transform: rotate(45deg) 50 | 51 | -------------------------------------------------------------------------------- /lib/ext/chrome-image-clipper/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JUMLY image clipper 5 | 6 | 10 | 11 | 12 | 13 | 14 |
      15 |

      JUMLY image clipper

      16 |
      17 |
      18 | 21 | 22 |
      23 | 26 |
      27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/examples/api-scan.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | experimental for jumly 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
      @found "a1"
      15 |
      @found "a2"
      16 | 28 | 29 | -------------------------------------------------------------------------------- /views/_layouts/default.jade: -------------------------------------------------------------------------------- 1 | doctype 5 2 | html(lang="en") 3 | head 4 | meta(charset='utf-8') 5 | meta(name="viewport", content="width=device-width,initial-scale=1.0") 6 | meta(name="keywords", content="js,uml,sequence,diagram,javascript,coffee-script,css,svg,jquer,css3,html5") 7 | - var __description = ""; 8 | block title 9 | block description 10 | meta(name="description", content="#{__description}") 11 | block styles 12 | body 13 | block navbar 14 | block content 15 | script(src="//d3nslu0hdya83q.cloudfront.net/dist/1.0/raven.min.js") 16 | :coffeescript 17 | Raven.config('https://c2e92a5a2b1c4f5392c8246abe963478@app.getsentry.com/14921', {whitelistUrls: [/example\.com/]}).install() 18 | script(src='//cdnjs.cloudflare.com/ajax/libs/coffee-script/1.6.3/coffee-script.min.js') 19 | script(src='//code.jquery.com/jquery-2.1.0.min.js') 20 | script(src='//knockoutjs.com/downloads/knockout-3.0.0.js') 21 | include ../_includes/syntaxhighlighter 22 | script(src='/public/jumly.min.js') 23 | block scripts 24 | script(src='/public/js/_ga.js') 25 | -------------------------------------------------------------------------------- /lib/js/g2d.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | * Convert to polar coordinate system from Cartesian coordinate system. 3 | * @return { 4 | * gradients : 5 | * radius : 6 | * quadrants : {x, y} x:1 or -1, y:1 or -1 7 | * declination: 8 | * offset : 9 | * } 10 | ### 11 | to_polar_from_cartesian = (src, dst)-> 12 | dx = dst.left - src.left 13 | dy = dst.top - src.top 14 | offset: 15 | left: src.left 16 | top : src.top 17 | radius : Math.sqrt dx*dx + dy*dy 18 | declination: Math.atan dy/dx 19 | quadrants : 20 | x: if dx != 0 then dx/Math.abs(dx) else 1 21 | y: if dy != 0 then dy/Math.abs(dy) else 1 22 | 23 | SVG_NS = "http://www.w3.org/2000/svg" 24 | 25 | g2d = 26 | svg: 27 | create: (tagname)-> 28 | if typeof document is "undefined" 29 | $("<#{tagname}>")[0] 30 | else 31 | document.createElementNS SVG_NS, tagname 32 | attrs: (n, attrs)-> 33 | for p of attrs 34 | n.setAttribute p, attrs[p] 35 | n 36 | new: (tagname, attrs)-> 37 | e = @create tagname 38 | @attrs e, attrs 39 | 40 | module.exports = g2d 41 | -------------------------------------------------------------------------------- /lib/ext/chrome-image-clipper/page.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | chrome.extension.onRequest.addListener(function(request, sender, respond) { 3 | if (request.type != "JUMLY" || request.action != "request-dimension") { 4 | return; 5 | } 6 | 7 | var diag = $(".diagram", document.body); 8 | if (diag.length == 0) { 9 | return respond({}); 10 | } 11 | 12 | var a = $.extend({}, diag.offset(), {width: diag.outerWidth(), height: diag.outerHeight()}); 13 | 14 | var body = $("body") 15 | a.left += parseInt(body.css("border-left-width")); 16 | a.top += parseInt(body.css("border-top-width")); 17 | 18 | var m = diag.find(".participant .name").css("box-shadow").match(/.*([0-9]+px [0-9]+px [0-9]+px [0-9]+px)/); 19 | var pxs = m[1].split("px"); 20 | var extw = parseInt(pxs[0]); 21 | var exth = parseInt(pxs[1]); 22 | 23 | a.width += extw; 24 | if (exth >= 16) { 25 | //WORKAROUND: 16 is a literal number for lifeline in SequenceDiagramLayout 26 | //console.log("calibrate for box-shadow"); 27 | a.height += exth - 16; 28 | } 29 | 30 | respond(a); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2003, 2004 Jim Weirich 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/js/position.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | 3 | _outerBottom = ($e)-> $e.offset().top + $e.outerHeight() - 1 4 | 5 | _choose = (nodes, ef, cmpf)-> $.map(nodes, ef).sort(cmpf)[0] 6 | 7 | position = 8 | max: (nodes, ef)-> _choose(nodes, ef, (a, b)-> b - a) 9 | min: (nodes, ef)-> _choose(nodes, ef, (a, b)-> a - b) 10 | 11 | mostLeftRight: (objs, margin)-> 12 | left : @min objs, (e)-> $(e).offset().left - (if margin then (parseInt $(e).css("margin-left")) else 0) 13 | right: @max objs, (e)-> 14 | t = $(e).offset().left + $(e).outerWidth() + (if margin then (parseInt $(e).css("margin-right")) else 0) 15 | if t - 1 < 0 then 0 else t - 1 16 | width: -> if @right? and @left? then @right - @left + 1 else 0 17 | 18 | mostTopBottom: (objs, margin)-> 19 | top : @min objs, (e)-> $(e).offset().top - (if margin then (parseInt $(e).css("margin-top")) else 0) 20 | bottom: @max objs, (e)-> 21 | t = $(e).offset().top + $(e).outerHeight() + (if margin then (parseInt $(e).css("margin-bottom")) else 0) 22 | if t - 1 < 0 then 0 else t - 1 23 | height:-> if @top? and @bottom? then @bottom - @top + 1 else 0 24 | 25 | outerBottom: _outerBottom 26 | 27 | module.exports = position 28 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt)-> 2 | 3 | pkg = grunt.file.readJSON('package.json') 4 | 5 | grunt.initConfig 6 | pkg: pkg 7 | 8 | uglify: 9 | options: 10 | ## About format, see http://blog.stevenlevithan.com/archives/date-time-format 11 | banner: """/* <%= pkg.name %>-<%= pkg.version %> <%=grunt.template.today('UTC:yyyy-mm-dd"T"HH:MM:ss"Z"')%> */\n""" 12 | mangle: false ## if true, jumly.min.js is corrupted 13 | build: 14 | src: 'dist/bundle.lib.js' 15 | dest: 'public/<%= pkg.name %>.min.js' 16 | 17 | stylus: 18 | compile: 19 | files: 20 | "dist/<%= pkg.name %>.css": "lib/css/jumly.styl" 21 | 22 | cssmin: 23 | compress: 24 | options: 25 | banner: """/* <%= pkg.name %>-<%= pkg.version %> <%=grunt.template.today('UTC:yyyy-mm-dd"T"HH:MM:ss"Z"')%> */""" 26 | files: 27 | 'public/<%= pkg.name %>.min.css': [ "dist/<%= pkg.name %>.css" ] 28 | 29 | 30 | grunt.loadNpmTasks 'grunt-contrib-uglify' 31 | grunt.loadNpmTasks 'grunt-contrib-stylus' 32 | grunt.loadNpmTasks 'grunt-contrib-cssmin' 33 | 34 | grunt.registerTask 'default', ['minify'] 35 | grunt.registerTask 'minify', ['uglify', 'cssmin'] 36 | -------------------------------------------------------------------------------- /public/examples/APNS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 30 | 31 | -------------------------------------------------------------------------------- /lib/js/ClassDiagram.coffee: -------------------------------------------------------------------------------- 1 | Diagram = require "Diagram.coffee" 2 | pos = require "position.coffee" 3 | 4 | class ClassDiagram extends Diagram 5 | 6 | ClassDiagram::member = (kind, clz, normval)-> 7 | holder = clz.find(".#{kind}s") 8 | $(normval["#{kind}s"]).each (i, e)-> 9 | id = "#{normval.id}-#{kind}-#{e}" 10 | throw new Error("Already exists #{e}") if holder.find(".#{e}").length > 0 11 | holder.append $("
    • ").addClass(e).attr("id", id).html e 12 | 13 | ClassDiagram::declare = (normval) -> 14 | clz = $.jumly ".class", normval 15 | if normval.stereotype 16 | clz.find(".stereotype").html normval.stereotype 17 | else 18 | clz.find(".stereotype").hide() 19 | 20 | @member(kind, clz, normval) for kind in ["attr", "method"] 21 | 22 | ref = @_regByRef_ normval.id, clz 23 | eval "#{ref} = clz" 24 | @append clz 25 | 26 | ClassDiagram::preferredWidth = -> 27 | pos.mostLeftRight(@find(".class .icon")).width() + 16 ##WORKAROUND: 16 is magic number. 28 | 29 | ClassDiagram::preferredHeight = -> 30 | @find(".class .icon").mostTopBottom().height() 31 | 32 | ClassDiagram::compose = -> 33 | ## Resize for looks 34 | @find(".class .icon").each (i, e) -> 35 | e = $ e 36 | return null if e.width() > e.height() 37 | e.width e.height() * (1 + Math.sqrt 2)/2 38 | @width @preferredWidth() 39 | @height @preferredHeight() 40 | this 41 | 42 | 43 | module.exports = ClassDiagram 44 | -------------------------------------------------------------------------------- /spec/jasmine-utils.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | core = require "core.coffee" 3 | HTMLElement = require "HTMLElement.coffee" 4 | L = require "SequenceDiagramLayout.coffee" 5 | 6 | root = 7 | matchers: (suite)-> 8 | jasmine.addMatchers 9 | haveClass: (util, customEqualityTesters)-> 10 | compare: (actual, expected)-> 11 | b = actual.hasClass expected 12 | pass: b, message: b ? "have" : "doesn't have" 13 | 14 | div: (self)-> 15 | klass = HTMLElement.to_css_name self.description 16 | div = $("
      ").attr("id", klass + "-container") 17 | .addClass("spec-diagram-container") 18 | .prepend($("
      ").addClass("description").text self.description) 19 | cont = $("body > #diagram-containers") 20 | #indow.document.write "hello" 21 | if cont.length is 0 22 | cont = $("
      ").attr("id", "diagram-containers") 23 | $("body").append cont 24 | cont.append div 25 | div 26 | 27 | glance: (diag)-> 28 | $("body").prepend diag 29 | new L().layout diag 30 | 31 | ua: (opts)-> 32 | for e of opts 33 | return opts[e] if $("html").hasClass "ua-#{e}" 34 | opts["webkit"] 35 | 36 | bottom: (e)-> Math.round e.offset().top + e.outerHeight() - 1 37 | 38 | top: (e)-> Math.round e.offset().top 39 | 40 | right: (e)-> Math.round e.offset().left + e.outerWidth() - 1 41 | 42 | left: (e)-> Math.round e.offset().left 43 | 44 | module.exports = root 45 | -------------------------------------------------------------------------------- /lib/ext/img-conv/loadJUMLY.coffee: -------------------------------------------------------------------------------- 1 | system = require "system" 2 | fs = require "fs" 3 | 4 | module.exports = 5 | loadJUMLY: (jumly_code, callback)-> 6 | suffix = "#{system.pid}-#{new Date().getTime()}" 7 | 8 | tmp_html = "./.#{suffix}.html" 9 | 10 | rootdir = fs.workingDirectory 11 | 12 | fs.write tmp_html, """ 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | """, "w" 26 | 27 | page = new WebPage 28 | page.onConsoleMessage = (msg)-> console.log msg 29 | 30 | page.open tmp_html, -> 31 | rect = page.evaluate -> 32 | $src = $("
      ").html window._jumly_code 33 | $("body").width 65535 34 | JUMLY.eval $src, into:"body" 35 | diag = $(".diagram") 36 | shadow_blur_w = 2 # NOTE: to include blur of shadow 37 | left:diag.offset().left, top:diag.offset().top, width:diag.outerWidth() + shadow_blur_w, height:diag.outerHeight() 38 | 39 | page.viewportSize = rect 40 | page.clipRect = rect 41 | 42 | fs.remove tmp_html 43 | 44 | callback? page 45 | -------------------------------------------------------------------------------- /lib/js/RobustnessDiagramBuilder.coffee: -------------------------------------------------------------------------------- 1 | DiagramBuilder = require "DiagramBuilder.coffee" 2 | RobustnessDiagram = require "RobustnessDiagram.coffee" 3 | IconElement = require "IconElement.coffee" 4 | Relationship = require "Relationship.coffee" 5 | 6 | class RobustnessDiagramBuilder extends DiagramBuilder 7 | constructor: (@_diagram)-> 8 | super() 9 | @_diagram ?= new RobustnessDiagram 10 | 11 | RobustnessDiagramBuilder::build = (src)-> 12 | if src.data 13 | src.find("*[data-kind]").each (e)=> 14 | e = $(e) 15 | @_diagram.append new IconElement(e.text(), kind:$(e).data("kind")) 16 | else 17 | super src 18 | @_diagram 19 | 20 | 21 | RobustnessDiagramBuilder::_node = (opt, kind)-> 22 | if typeof opt is "string" 23 | @_diagram.append (a = @_diagram._node_of opt, kind) 24 | return a 25 | else if typeof opt is "object" 26 | for k of opt 27 | if typeof (f = opt[k]) is "function" 28 | a = @_diagram._node_of k, kind 29 | b = f.apply this, [] 30 | @_diagram.append(a).append(b) 31 | @_diagram.append new Relationship("", src:a, dst:b) 32 | return a 33 | throw "unexpected: " + typeof opt 34 | 35 | RobustnessDiagramBuilder::actor = (opt)-> @_node opt, "actor" 36 | 37 | RobustnessDiagramBuilder::view = (opt)-> @_node opt, "view" 38 | 39 | RobustnessDiagramBuilder::controller= (opt)-> @_node opt, "controller" 40 | 41 | RobustnessDiagramBuilder::entity = (opt)-> @_node opt, "entity" 42 | 43 | module.exports = RobustnessDiagramBuilder 44 | -------------------------------------------------------------------------------- /public/css/skin.fancy.css: -------------------------------------------------------------------------------- 1 | .fancy .sequence-diagram .participant { 2 | background-color: transparent; 3 | } 4 | .fancy .sequence-diagram .participant:nth-child(1) { 5 | border-color: #007089; 6 | background-color: #0095b6; 7 | color: #004a5b; 8 | } 9 | .fancy .sequence-diagram .participant:nth-child(2) { 10 | border-color: #bf8b00; 11 | background-color: #ffba00; 12 | color: #805d00; 13 | width: 160px; 14 | text-align: center; 15 | margin-left: 8px; 16 | } 17 | .fancy .sequence-diagram .participant:nth-child(3) { 18 | border-color: #e6c52f; 19 | background-color: #f0dc82; 20 | color: #a58b14; 21 | margin-left: 8px; 22 | } 23 | .fancy .sequence-diagram .participant { 24 | border-radius: 4px; 25 | font-weight: bold; 26 | } 27 | .fancy .sequence-diagram .occurrence { 28 | border-radius: 4px; 29 | background-color: #e6e6e6; 30 | border-color: #bfbfbf; 31 | } 32 | .fancy .sequence-diagram .lifeline .line { 33 | margin-left: 49%; 34 | border-left: dotted 5px #8fbf00; 35 | } 36 | .fancy .sequence-diagram .message svg.arrow line, 37 | .fancy .sequence-diagram .message svg.arrow polyline { 38 | stroke-width: 3px; 39 | } 40 | .fancy .sequence-diagram .message svg.arrow line, 41 | .fancy .sequence-diagram .message svg.arrow polyline { 42 | stroke: #8cc84b; 43 | } 44 | .fancy .sequence-diagram .message svg.arrow polyline.head, 45 | .fancy .sequence-diagram .message svg.arrow polyline.closed, 46 | .fancy .sequence-diagram .message.self svg.arrow polyline.head { 47 | stroke: #8cc84b; 48 | fill: #8cc84b; 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jumly", 3 | "version": "0.2.5", 4 | "author": "Tomotaka Sakuma", 5 | "description": "Library to render UML diagrams", 6 | "engines": { 7 | "node": "0.12.7", 8 | "npm": "2.11.3" 9 | }, 10 | "dependencies": { 11 | "coffee-script": "1.8.0", 12 | "express": "3.1.2", 13 | "jade": "0.28.2", 14 | "jquery": "2.1.3", 15 | "nib": "~1.0.4", 16 | "phantomjs": "^1.9.13", 17 | "stylus": "^0.49.3", 18 | "tmp": "0.0.24", 19 | "underscore": "^1.7.0" 20 | }, 21 | "devDependencies": { 22 | "coffee-loader": "^0.7.2", 23 | "css-loader": "^0.9.0", 24 | "grunt": "^0.4.5", 25 | "grunt-cli": "^0.1.13", 26 | "grunt-contrib-cssmin": "^0.10.0", 27 | "grunt-contrib-stylus": "^0.20.0", 28 | "grunt-contrib-uglify": "^0.6.0", 29 | "jasmine": "^2.3.1", 30 | "jasmine-core": "^2.3.4", 31 | "jasmine-node": "^1.14.5", 32 | "karma": "^0.12.28", 33 | "karma-chrome-launcher": "^0.1.7", 34 | "karma-coffee-preprocessor": "^0.2.1", 35 | "karma-jasmine": "^0.3.2", 36 | "karma-phantomjs-launcher": "^0.1.4", 37 | "karma-webpack": "1.3.1", 38 | "style-loader": "^0.8.2", 39 | "stylus-loader": "^0.5.0", 40 | "webpack": "^1.4.13", 41 | "webpack-dev-server": "^1.6.6" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git://github.com/tmtk75/jumly.git" 46 | }, 47 | "directories": { 48 | "lib": "./lib", 49 | "bin": "./bin", 50 | "example": "./examples" 51 | }, 52 | "scripts": { 53 | "start": "./app.coffee", 54 | "test": "./node_modules/karma/bin/karma start karma.conf.js $KARMA_OPTIONS", 55 | "prepublish": "npm prune" 56 | }, 57 | "license": "MIT" 58 | } 59 | -------------------------------------------------------------------------------- /spec/RobustnessDiagramBuilderSpec.coffee: -------------------------------------------------------------------------------- 1 | RobustnessDiagramBuilder = require "RobustnessDiagramBuilder.coffee" 2 | 3 | describe "RobustnessDiagramBuilder", -> 4 | 5 | describe "actor", -> 6 | 7 | describe "without dst", -> 8 | it "creates an actor having .actor", -> 9 | diag = new RobustnessDiagramBuilder().build """ @actor "a" """ 10 | expect(diag.find(".actor").length).toBe 1 11 | 12 | describe "with a dst", -> 13 | it "creates two actors", -> 14 | diag = new RobustnessDiagramBuilder().build """ @actor "a" :-> @actor "b" """ 15 | expect(diag.find(".actor").length).toBe 2 16 | 17 | 18 | describe "examples", -> 19 | 20 | describe "scripting", -> 21 | 22 | beforeEach -> 23 | @diagram = new RobustnessDiagramBuilder().build """ 24 | @actor "browser": -> @view "HTTP" 25 | @view "HTTP": -> @controller "webapp" 26 | @controller "webapp": -> @entity "DB" 27 | """ 28 | 29 | it "has two elements", -> 30 | expect(@diagram.find(".element").length).toBe 4 31 | expect(@diagram.find(".relationship").length).toBe 3 32 | 33 | 34 | xdescribe "markup", -> 35 | 36 | beforeEach -> 37 | html = $ """ 38 |
    • An user opens Yahoo with his browser.
    • 39 | """ 40 | d = new RobustnessDiagramBuilder() 41 | @diagram = d.build $(html) 42 | 43 | it "has two elements", -> 44 | expect(@diagram.find(".element").length).toBe 2 45 | 46 | 47 | xdescribe "corner cases", -> 48 | 49 | describe "empty string", -> 50 | 51 | it "works well", -> 52 | diag = new RobustnessDiagramBuilder().build "" 53 | -------------------------------------------------------------------------------- /lib/js/SequenceParticipant.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | HTMLElement = require "HTMLElement.coffee" 3 | SequenceOccurrence = require "SequenceOccurrence.coffee" 4 | SequenceInteraction = require "SequenceInteraction.coffee" 5 | 6 | class SequenceParticipant extends HTMLElement 7 | constructor: (args)-> 8 | super args, (me)-> 9 | me.append($("
      ").addClass("name")) 10 | 11 | SequenceParticipant::preferred_width = -> 12 | @outerWidth() 13 | 14 | SequenceParticipant::activate = -> 15 | occurr = new SequenceOccurrence this 16 | iact = new SequenceInteraction null, occurr 17 | iact.addClass "activated" 18 | iact.find(".message").remove() 19 | iact.append(occurr) 20 | @parent().append(iact) 21 | occurr 22 | 23 | SequenceParticipant::isLeftAt = (a)-> @offset().left < a.offset().left 24 | 25 | SequenceParticipant::isRightAt = (a)-> (a.offset().left + a.width()) < @offset().left 26 | 27 | SequenceParticipant::iconify = (fixture, styles)-> 28 | unless typeof fixture is "function" 29 | fixture = $.jumly.icon["." + fixture] || $.jumly.icon[".actor"] 30 | canvas = $("").addClass("icon") 31 | container = $("
      ").addClass("icon-container") 32 | @addClass("iconified").prepend(container.append canvas) 33 | 34 | {size, styles} = fixture canvas[0], styles 35 | container.css height:size.height #, width:size.width ##FIXME: Way to decide the width. 36 | render = => 37 | name = @find(".name") 38 | styles.fillStyle = name.css("background-color") 39 | styles.strokeStyle = name.css("border-top-color") 40 | fixture canvas[0], styles 41 | this.renderIcon = -> render() 42 | this 43 | 44 | SequenceParticipant::lost =-> @activate().interact(null, {stereotype:".lost"}) 45 | 46 | module.exports = SequenceParticipant 47 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: test 2 | 3 | build: public/jumly.min.js \ 4 | public/jumly.min.css 5 | 6 | public/jumly.min.js public/jumly.min.css: \ 7 | dist/bundle.lib.js \ 8 | dist/jumly.css \ 9 | package.json 10 | ./node_modules/.bin/grunt minify 11 | 12 | dist/jumly.css: lib/css/*.styl 13 | ./node_modules/.bin/grunt stylus 14 | 15 | dist/bundle.lib.js dist/bundle.spec.js: \ 16 | node_modules/.bin/webpack \ 17 | lib/js/*.coffee \ 18 | lib/css/*.styl \ 19 | spec/*.coffee 20 | ./node_modules/.bin/webpack 21 | 22 | vendor/coffee-script.js: 23 | mkdir -p vendor && cd vendor; \ 24 | curl -OL http://coffeescript.org/extras/coffee-script.js 25 | 26 | node_modules/jquery/dist/jquery.js: 27 | npm install 28 | 29 | vendor/jasmine/lib/jasmine-2.3.4/jasmine.js: 30 | mkdir -p vendor/jasmine && cd vendor/jasmine; \ 31 | curl -OL 'https://github.com/jasmine/jasmine/releases/download/v2.3.4/jasmine-standalone-2.3.4.zip'; \ 32 | unzip -o jasmine-standalone-2.3.4.zip; \ 33 | touch lib/jasmine-2.3.4/jasmine.js 34 | 35 | .PHONY: dev test example karma api clean 36 | 37 | dev: 38 | webpack -w 39 | 40 | test: dist/bundle.lib.js dist/bundle.spec.js \ 41 | vendor/coffee-script.js \ 42 | vendor/jasmine/lib/jasmine-2.3.4/jasmine.js 43 | open spec/index.html 44 | 45 | example: dist/bundle.lib.js 46 | open examples/bundle.html 47 | 48 | karma: vendor/coffee-script.js \ 49 | node_modules/jquery/dist/jquery.js \ 50 | dist/jumly.css 51 | karma start karma.conf.js 52 | 53 | api: 54 | open "http://localhost:3000/api/diagrams?data=%40found%20%22You%22%2C%20-%3E%0A%20%20%40message%20%22Think%22%2C%20-%3E%0A%20%20%20%20%40message%20%22Write%20your%20idea%22%2C%20%22JUMLY%22%2C%20-%3E%0A%20%20%20%20%20%20%40create%20%22Diagram%22%0Ajumly.css%20%22background-color%22%3A%22%238CC84B%22" 55 | 56 | clean: 57 | rm -rf build dist vendor 58 | -------------------------------------------------------------------------------- /spec/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 27 | 28 | 49 | 50 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/scripts/shBrushJScript.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | ;(function() 18 | { 19 | // CommonJS 20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; 21 | 22 | function Brush() 23 | { 24 | var keywords = 'break case catch continue ' + 25 | 'default delete do else false ' + 26 | 'for function if in instanceof ' + 27 | 'new null return super switch ' + 28 | 'this throw true try typeof var while with' 29 | ; 30 | 31 | var r = SyntaxHighlighter.regexLib; 32 | 33 | this.regexList = [ 34 | { regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings 35 | { regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings 36 | { regex: r.singleLineCComments, css: 'comments' }, // one line comments 37 | { regex: r.multiLineCComments, css: 'comments' }, // multiline comments 38 | { regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion 39 | { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keywords 40 | ]; 41 | 42 | this.forHtmlScript(r.scriptScriptTags); 43 | }; 44 | 45 | Brush.prototype = new SyntaxHighlighter.Highlighter(); 46 | Brush.aliases = ['js', 'jscript', 'javascript']; 47 | 48 | SyntaxHighlighter.brushes.JScript = Brush; 49 | 50 | // CommonJS 51 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null; 52 | })(); 53 | -------------------------------------------------------------------------------- /lib/js/SequenceFragment.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | HTMLElement = require "HTMLElement.coffee" 3 | 4 | class SequenceFragment extends HTMLElement 5 | constructor: (args)-> 6 | super args, (me)-> 7 | me.append($("
      ").addClass("header") 8 | .append($("
      ").addClass("name")) 9 | .append($("
      ").addClass("condition"))) 10 | 11 | ## This is wrap feature keeping own instance, jQuery.wrap makes child node duplicated. 12 | swallow = ($e, _, f) -> 13 | f = f or $.fn.append 14 | if _.length is 1 15 | if _.index() is 0 then _.parent().prepend $e else $e.insertAfter _.prev() 16 | else 17 | #NOTE: In order to solve the case for object-lane. You use closure if you want flexibility. 18 | if _.index() is 0 then $e.prependTo $(_[0]).parent() else $e.insertBefore _[0] 19 | $e.append _.detach() 20 | $e 21 | 22 | SequenceFragment::enclose = (_) -> 23 | if not _? or _.length is 0 24 | throw "SequenceFragment::enclose arguments are empty." 25 | if _.length > 1 # pre-condition: all nodes have same parent. 26 | a = $(_[0]).parent()[0] 27 | for i in [1 .. _.length - 1] 28 | b = $(_[i]).parent()[0] 29 | unless a is b 30 | throw {message:"different parent", nodes:[a, b]} 31 | if _.parent is undefined 32 | return this 33 | swallow(this, _) 34 | this 35 | 36 | SequenceFragment::alter = (occurr, acts) -> 37 | alt = this 38 | alt.addClass("alt") 39 | .find(".condition").remove() 40 | occurr.append alt 41 | for name of acts 42 | nodes = acts[name]() 43 | continue if nodes.length is 0 44 | alt.append($("
      ").addClass("condition").html name) 45 | alt.append(nodes) 46 | alt.append $("
      ").addClass("divider") 47 | alt.find(".divider:last").remove() 48 | alt 49 | 50 | module.exports = SequenceFragment 51 | -------------------------------------------------------------------------------- /lib/ext/img-conv/img-conv.coffee: -------------------------------------------------------------------------------- 1 | system = require "system" 2 | fs = require "fs" 3 | 4 | ### usage ### 5 | if system.args.length < 2 6 | console.log """ 7 | usage: #{system.args[0]} [format] [encoding] 8 | script-path meet-you.jm 9 | format png | jpg 10 | encoding image | base64 | html 11 | 12 | ex) 13 | #{system.args[0]} meet-you.jm jpg 14 | #{system.args[0]} meet-you.jm png base64 15 | 16 | """ 17 | phantom.exit 1 18 | 19 | 20 | ### arguments ### 21 | script_path = system.args[1] 22 | format = system.args[2] or "png" 23 | encoding = system.args[3] or "image" 24 | 25 | regex_ext = /\.[^.]+$/ 26 | ext = if format.match /^(png|gif|jpg|jpeg)$/i then format else "png" 27 | img_path = if script_path.match regex_ext 28 | script_path.replace regex_ext, "." + ext 29 | else 30 | script_path + "." + ext 31 | html_path = img_path.replace regex_ext, ".html" 32 | 33 | ## 34 | phantom.onError = (msg, trace)-> 35 | console.error 'PHANTOM ERROR:', msg 36 | phantom.exit 1 37 | 38 | ### read script ### 39 | body = fs.read script_path 40 | #console.log script_path, img_path, html_path 41 | 42 | 43 | ### run ### 44 | require("./loadJUMLY").loadJUMLY body, (page)-> 45 | ## create a file as .png 46 | if encoding.match /image/i 47 | page.render img_path 48 | console.log img_path 49 | 50 | ## print base64 to stdout 51 | console.log page.renderBase64 ext if encoding.match /^base64$/i 52 | 53 | ## print html to stdout 54 | if encoding.match /^html$/i 55 | console.log '' 56 | console.log page.evaluate -> $("body").html() 57 | 58 | ## create a file to show above it 59 | ### 60 | fs.write html_path, """ 61 | 62 | 63 | 64 | """, "w" 65 | ### 66 | phantom.exit() 67 | -------------------------------------------------------------------------------- /lib/js/SequenceRef.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | HTMLElement = require "HTMLElement.coffee" 3 | pos = require "position.coffee" 4 | 5 | class SequenceRef extends HTMLElement 6 | constructor: (args)-> 7 | super args, (div)-> 8 | div.append($("
      ").addClass("header") 9 | .append($("
      ").addClass("tag") 10 | .html "ref")) 11 | .append $("
      ").addClass "name" 12 | 13 | SequenceRef::preferred_left_and_width = -> 14 | occurr = @parents(".occurrence:eq(0)") 15 | if occurr.length is 1 16 | w = occurr.outerWidth() 17 | right = occurr.offset().left + w 18 | return left:right - w/2 19 | 20 | ## NOTE: forllowing is not optimized and a bit adhoc. 21 | diag = @parents(".sequence-diagram:eq(0)") 22 | iact = @prevAll(".interaction:eq(0)") 23 | 24 | if iact.length is 0 25 | lines = $(".lifeline .line", diag) 26 | most = pos.mostLeftRight(lines) 27 | most.width = most.width() 28 | return most 29 | 30 | objs = diag.find(".participant") 31 | if objs.length is 0 32 | return {} 33 | 34 | if objs.length is 1 35 | it = objs.filter(":eq(0)") 36 | w = parseInt (@css("min-width") or @css("max-width") or @css("width")) 37 | l = it.offset().left - (w - it.outerWidth())/2 38 | if (dl = l - it.offset().left) < 0 39 | @css "margin-left":dl 40 | diag.css "margin-left":-dl 41 | return left:"auto" 42 | 43 | if (alt = @parents(".alt:eq(0)")).length is 1 44 | left = alt.parents(".occurrence") 45 | l = left.offset().left + left.outerWidth() - 1 46 | r = pos.max @parent().find(".occurrence"), (e)-> $(e).offset().left + $(e).outerWidth()/2 47 | d = left.outerWidth()/2 - 1 48 | return left:l - d, width:(r - l) 49 | 50 | dh = diag.self() 51 | .find(".occurrence:eq(0)").width() 52 | occurs = iact.find(".occurrence") 53 | most = pos.mostLeftRight(occurs) 54 | most.left -= dh 55 | most.width = most.width() 56 | most 57 | 58 | module.exports = SequenceRef 59 | -------------------------------------------------------------------------------- /spec/SequenceParticipantSpec.coffee: -------------------------------------------------------------------------------- 1 | core = require "core.coffee" 2 | utils = require "./jasmine-utils.coffee" 3 | SequenceDiagramLayout = require "SequenceDiagramLayout.coffee" 4 | SequenceDiagramBuilder = require "SequenceDiagramBuilder.coffee" 5 | 6 | describe "SequenceParticipant", -> 7 | 8 | div = utils.div this 9 | 10 | beforeEach -> 11 | @layout = new SequenceDiagramLayout 12 | @builder = new SequenceDiagramBuilder 13 | 14 | describe "one participant", -> 15 | beforeEach -> 16 | @diagram = @builder.build """ 17 | @found "a" 18 | """ 19 | div.append @diagram 20 | @layout.layout @diagram 21 | 22 | it "is calculated for width", -> 23 | p = @diagram.find(".participant:eq(0)") 24 | expect(p.outerWidth()).toBe 2 + 0 + 4 + 88 + 4 + 0 + 2 25 | 26 | it "is calculated for height", -> 27 | p = @diagram.find(".participant:eq(0)") 28 | n = p.find(".name") 29 | # 1.2 is from https://developer.mozilla.org/en-US/docs/Web/CSS/line-height 30 | lh = Math.round(parseInt(n.css("font-size")) * 1.2) # estimated line-height 31 | 32 | h = 2 + 8 + 0 + lh + 0 + 8 + 2 33 | expect(h - 1 <= p.outerHeight() && p.outerHeight() <= h + 1) 34 | 35 | describe "two participants", -> 36 | beforeEach -> 37 | @diagram = @builder.build """ 38 | @found "0123456789abcdef0123456789abcdef", -> 39 | @message "hi", "You" 40 | """ 41 | div.append @diagram 42 | @layout.layout @diagram 43 | 44 | it "is aligned at bottom", -> 45 | p0 = @diagram.find(".participant:eq(0)") 46 | p1 = @diagram.find(".participant:eq(1)") 47 | 48 | expect(utils.bottom p0).toBe utils.bottom p1 49 | 50 | describe "name starts with digits and has spaces", -> 51 | beforeEach -> 52 | @diagram = @builder.build """ 53 | @found "0 a" 54 | """ 55 | div.append @diagram 56 | @layout.layout @diagram 57 | 58 | it "should work", -> 59 | p = @diagram.find(".participant:eq(0) .name") 60 | expect(p.html()).toBe "0 a" 61 | 62 | -------------------------------------------------------------------------------- /lib/js/core.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | jQuery = $ 3 | core = {} 4 | 5 | core.self = ($e)-> $e.data "_self" 6 | 7 | core._to_id = (that)-> 8 | return that.attr("id") if that.constructor is jQuery 9 | that.toLowerCase().replace /[^a-zA-Z0-9_]/g, "-" 10 | 11 | core._to_ref = (s)-> 12 | s = '_' + s if s.match /^[0-9]/ 13 | s.replace /^[0-9]|-/g, '_' 14 | 15 | core.kindof = (that)-> 16 | return 'Null' if that is null 17 | return 'Undefined' if that is undefined 18 | ctor = that.constructor 19 | toName = (f)-> if 'name' in f then f.name else (''+f).replace(/^function\s+([^\(]*)[\S\s]+$/im, '$1').trim() 20 | if typeof(ctor) is 'function' then toName(ctor) else tc # [object HTMLDocumentConstructor] 21 | 22 | core._normalize = (that)-> 23 | switch core.kindof that 24 | when "String" then return id:core._to_id(that), name:that 25 | when "Object" then ## Through down 26 | else 27 | if that and that.constructor is jQuery 28 | id = core._to_id(that) 29 | name = that.find(".name") 30 | if id? or (name.length > 0) 31 | return id:id, name: (if name.html() then name.html() else undefined) 32 | else 33 | return undefined 34 | console.error "Cannot recognize kind:", that 35 | throw new Error "Cannot recognize kind: '#{core.kindof that}'" 36 | keys = (p for p of that) 37 | return that if keys.length > 1 38 | 39 | id = keys[0] 40 | it = that[keys[0]] 41 | return {id:id, name:it} if core.kindof(it) is "String" 42 | 43 | keys = (p for p of it) 44 | return $.extend {}, it, {id:core._to_id(id), name:id} if keys.length > 1 45 | 46 | name = keys[0] 47 | mods = it[keys[0]] 48 | switch core.kindof(mods) 49 | when "Object" then $.extend {id:id, name:name}, mods 50 | when "Array", "Function" 51 | a = {id:core._to_id(id), name:id} 52 | a[name] = mods 53 | a 54 | 55 | module.exports = core 56 | 57 | # Listen for window load, both in browsers and in IE. 58 | if typeof window is "object" 59 | $(window).on 'DOMContentLoaded', -> 60 | require("api.coffee").scan() 61 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/scripts/shBrushXml.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | ;(function() 18 | { 19 | // CommonJS 20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; 21 | 22 | function Brush() 23 | { 24 | function process(match, regexInfo) 25 | { 26 | var constructor = SyntaxHighlighter.Match, 27 | code = match[0], 28 | tag = new XRegExp('(<|<)[\\s\\/\\?]*(?[:\\w-\\.]+)', 'xg').exec(code), 29 | result = [] 30 | ; 31 | 32 | if (match.attributes != null) 33 | { 34 | var attributes, 35 | regex = new XRegExp('(? [\\w:\\-\\.]+)' + 36 | '\\s*=\\s*' + 37 | '(? ".*?"|\'.*?\'|\\w+)', 38 | 'xg'); 39 | 40 | while ((attributes = regex.exec(code)) != null) 41 | { 42 | result.push(new constructor(attributes.name, match.index + attributes.index, 'color1')); 43 | result.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string')); 44 | } 45 | } 46 | 47 | if (tag != null) 48 | result.push( 49 | new constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword') 50 | ); 51 | 52 | return result; 53 | } 54 | 55 | this.regexList = [ 56 | { regex: new XRegExp('(\\<|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\\>|>)', 'gm'), css: 'color2' }, // 57 | { regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // 58 | { regex: new XRegExp('(<|<)[\\s\\/\\?]*(\\w+)(?.*?)[\\s\\/\\?]*(>|>)', 'sg'), func: process } 59 | ]; 60 | }; 61 | 62 | Brush.prototype = new SyntaxHighlighter.Highlighter(); 63 | Brush.aliases = ['xml', 'xhtml', 'xslt', 'html']; 64 | 65 | SyntaxHighlighter.brushes.Xml = Brush; 66 | 67 | // CommonJS 68 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null; 69 | })(); 70 | -------------------------------------------------------------------------------- /lib/ext/chrome-image-clipper/popup.js: -------------------------------------------------------------------------------- 1 | var mktrans = function(ctx, w, h, newc, is_bg) { 2 | var imgd = ctx.getImageData(0, 0, w, h); 3 | var pix = imgd.data; 4 | 5 | for (var i = 0, n = pix.length; i < n; i += 4) { 6 | var r = pix[i + 0]; 7 | var g = pix[i + 1]; 8 | var b = pix[i + 2]; 9 | var a = pix[i + 3]; 10 | //if (r == 255 && g == 255 && b == 255) { 11 | if (is_bg(r, g, b)) { 12 | pix[i + 0] = newc.r; 13 | pix[i + 1] = newc.g; 14 | pix[i + 2] = newc.b; 15 | pix[i + 3] = newc.a; 16 | } 17 | } 18 | ctx.putImageData(imgd, 0, 0); 19 | } 20 | 21 | var render = function(img, dx, dy, w, h) { 22 | offscreen.width = w; 23 | offscreen.height = h; 24 | var ctx = offscreen.getContext('2d'); 25 | ctx.globalCompositeOperation = "lighter"; 26 | //ctx.globalAlpha = 0.5 27 | ctx.drawImage(img, 28 | dx, dy, w, h, 29 | 0, 0, w, h); 30 | 31 | //var newc = {r:0, g:0, b:0, a:0}; 32 | var newc = {r:255, g:255, b:255, a:255.0}; 33 | var is_bg = function(r, g, b) { 34 | var d = 8; 35 | return (r >= (238 - d) && g >= (238 - d) && b >= (238 - d)) && !(r == 255 && g == 255 && b == 255); 36 | } 37 | mktrans(ctx, w, h, newc, is_bg); 38 | 39 | downloader.data.value = offscreen.toDataURL('png'); 40 | downloader.submit(); 41 | } 42 | 43 | var main = function() { 44 | chrome.tabs.getSelected(null, function(tab) { 45 | chrome.tabs.sendRequest(tab.id, {type:"JUMLY", action:"request-dimension"}, function(args) { 46 | if (!args.width) { 47 | $(notification).addClass("failed").html("Any diagrams not found"); 48 | return; 49 | } 50 | var width = args.width, 51 | height = args.height, 52 | left = args.left, 53 | top = args.top; 54 | 55 | var opts = {format:'png'}; 56 | var f = function(data) { 57 | var img = new Image(); 58 | img.width = width; 59 | img.height = height; 60 | img.src = data; 61 | //NOTE: setTimeout is workaround for invalidating canvas 62 | setTimeout(function() {render(img, left, top, width, height);}, 0); 63 | $(notification).addClass("succeeded").html("Download succeeded"); 64 | } 65 | chrome.tabs.captureVisibleTab(null, opts, f); 66 | }); 67 | }) 68 | } 69 | $(main); 70 | -------------------------------------------------------------------------------- /views/api.styl: -------------------------------------------------------------------------------- 1 | @import common 2 | @import "_layouts/footer" 3 | 4 | h1, h2, h3 5 | margin-top 0 6 | font-family Russo One 7 | 8 | ul 9 | font-family mplus-1p,verdana 10 | 11 | .menu 12 | ul 13 | list-style none 14 | > ul 15 | margin-left 0 16 | 17 | #_core 18 | margin-top 3em 19 | 20 | section 21 | margin-bottom 5em 22 | 23 | #_core 24 | article 25 | _A_BG() 26 | 27 | h3 28 | font-size 1.3em 29 | 30 | .entry-meta, #details .category 31 | float right 32 | background-color #ccc 33 | padding 2px 1em 2px 1em 34 | border-radius 5px 35 | font-size 12px 36 | 37 | #details 38 | article 39 | > .title 40 | _L_HEADER() 41 | //text-shadow 0 -1px 0 rgba(0,0,0,0.5) 42 | font-size 16px 43 | letter-spacing 0 44 | margin-bottom 1px 45 | 46 | > .returns 47 | float right 48 | &:before 49 | content "Returns:" 50 | margin-right 0.5em 51 | 52 | > .description 53 | margin-top 1em 54 | 55 | .signatures 56 | .signature 57 | > .name 58 | _M_HEADER() 59 | //text-shadow 0 -1px 0 transparent 60 | font-size 16px 61 | letter-spacing 0 62 | 63 | background-color #ddd 64 | border-radius 5px 65 | padding-bottom 5px 66 | 67 | ul 68 | list-style none 69 | margin-bottom 10px 70 | 71 | .arg 72 | margin-top 10px 73 | 74 | > .name 75 | L-EMBOSS() 76 | font-weight bold 77 | display inline-block 78 | 79 | > .default 80 | L-EMBOSS() 81 | display inline-block 82 | &:before 83 | margin-left 1em 84 | content "(default: " 85 | &:after 86 | content ")" 87 | 88 | > .type 89 | > .name 90 | L-EMBOSS() 91 | &:before 92 | content "Type: " 93 | 94 | .params, .param, .type, .name 95 | display inline-block 96 | L-EMBOSS() 97 | .params 98 | &:before 99 | content "(" 100 | &:after 101 | content ")" 102 | .param:not(:first-child) 103 | &:before 104 | content "," 105 | margin-right 0.5em 106 | .param .name 107 | margin-left 5px 108 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/scripts/shBrushJava.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | ;(function() 18 | { 19 | // CommonJS 20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; 21 | 22 | function Brush() 23 | { 24 | var keywords = 'abstract assert boolean break byte case catch char class const ' + 25 | 'continue default do double else enum extends ' + 26 | 'false final finally float for goto if implements import ' + 27 | 'instanceof int interface long native new null ' + 28 | 'package private protected public return ' + 29 | 'short static strictfp super switch synchronized this throw throws true ' + 30 | 'transient try void volatile while'; 31 | 32 | this.regexList = [ 33 | { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments 34 | { regex: /\/\*([^\*][\s\S]*)?\*\//gm, css: 'comments' }, // multiline comments 35 | { regex: /\/\*(?!\*\/)\*[\s\S]*?\*\//gm, css: 'preprocessor' }, // documentation comments 36 | { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings 37 | { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings 38 | { regex: /\b([\d]+(\.[\d]+)?|0x[a-f0-9]+)\b/gi, css: 'value' }, // numbers 39 | { regex: /(?!\@interface\b)\@[\$\w]+\b/g, css: 'color1' }, // annotation @anno 40 | { regex: /\@interface\b/g, css: 'color2' }, // @interface keyword 41 | { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // java keyword 42 | ]; 43 | 44 | this.forHtmlScript({ 45 | left : /(<|<)%[@!=]?/g, 46 | right : /%(>|>)/g 47 | }); 48 | }; 49 | 50 | Brush.prototype = new SyntaxHighlighter.Highlighter(); 51 | Brush.aliases = ['java']; 52 | 53 | SyntaxHighlighter.brushes.Java = Brush; 54 | 55 | // CommonJS 56 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null; 57 | })(); 58 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/scripts/shBrushRuby.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | ;(function() 18 | { 19 | // CommonJS 20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; 21 | 22 | function Brush() 23 | { 24 | // Contributed by Erik Peterson. 25 | 26 | var keywords = 'alias and BEGIN begin break case class def define_method defined do each else elsif ' + 27 | 'END end ensure false for if in module new next nil not or raise redo rescue retry return ' + 28 | 'self super then throw true undef unless until when while yield'; 29 | 30 | var builtins = 'Array Bignum Binding Class Continuation Dir Exception FalseClass File::Stat File Fixnum Fload ' + 31 | 'Hash Integer IO MatchData Method Module NilClass Numeric Object Proc Range Regexp String Struct::TMS Symbol ' + 32 | 'ThreadGroup Thread Time TrueClass'; 33 | 34 | this.regexList = [ 35 | { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, // one line comments 36 | { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings 37 | { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings 38 | { regex: /\b[A-Z0-9_]+\b/g, css: 'constants' }, // constants 39 | { regex: /:[a-z][A-Za-z0-9_]*/g, css: 'color2' }, // symbols 40 | { regex: /(\$|@@|@)\w+/g, css: 'variable bold' }, // $global, @instance, and @@class variables 41 | { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords 42 | { regex: new RegExp(this.getKeywords(builtins), 'gm'), css: 'color1' } // builtins 43 | ]; 44 | 45 | this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); 46 | }; 47 | 48 | Brush.prototype = new SyntaxHighlighter.Highlighter(); 49 | Brush.aliases = ['ruby', 'rails', 'ror', 'rb']; 50 | 51 | SyntaxHighlighter.brushes.Ruby = Brush; 52 | 53 | // CommonJS 54 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null; 55 | })(); 56 | -------------------------------------------------------------------------------- /lib/js/Relationship.coffee: -------------------------------------------------------------------------------- 1 | HTMLElement = require "HTMLElement.coffee" 2 | g2d = require "g2d.coffee" 3 | 4 | class Relationship extends HTMLElement 5 | constructor: (args, opts)-> 6 | @src = opts.src 7 | @dst = opts.dst 8 | super args, (me)-> 9 | me.addClass("relationship") 10 | .append(svg = $("").addClass("icon")) 11 | .append($("
      ").addClass("name")) 12 | 13 | svg[0].appendChild g2d.svg.create "line" 14 | me 15 | 16 | MESSAGE_STYLE = 17 | width : 1 18 | base : 6 19 | height : 10 20 | lineWidth : 1.5 21 | shape : "dashed" 22 | pattern : [8, 8] 23 | strokeStyle: 'gray' 24 | fillStyle : 'gray' 25 | lineJoin : 'round' 26 | 27 | Math.sign = (x) -> 28 | if x is 0 29 | return 0 30 | else if x > 0 31 | return 1 32 | return -1 33 | 34 | cssAsInt = (node, name) -> a = node.css(name); if a then parseInt a else 0 35 | 36 | Relationship::_point = (obj)-> 37 | margin_left = cssAsInt $("body"), "margin-left" 38 | margin_top = cssAsInt $("body"), "margin-top" 39 | s = obj.offset() 40 | dh = -(cssAsInt obj, "margin-left") - margin_left 41 | dv = -(cssAsInt obj, "margin-top") - margin_top 42 | left:s.left + obj.outerWidth()/2 + dh 43 | top:s.top + obj.outerHeight()/2 + dv 44 | 45 | Relationship::_rect = (p, q)-> 46 | a = left:Math.min(p.left, q.left), top:Math.min(p.top, q.top) 47 | b = left:Math.max(p.left, q.left), top:Math.max(p.top, q.top) 48 | w = b.left - a.left + 1 49 | h = b.top - a.top + 1 50 | hs = Math.sign(q.left - p.left) 51 | vs = Math.sign(q.top - p.top) 52 | l = Math.sqrt w*w + h*h 53 | left:a.left, top:a.top, width:w, height:h, hsign:hs, vsign:vs, hunit:hs*w/l, vunit:vs*h/l 54 | 55 | Relationship::render = -> 56 | p = @_point @src 57 | q = @_point @dst 58 | r = @_rect p, q 59 | 60 | cr = 2 61 | aa = r.hunit*@dst.outerWidth()/cr 62 | bb = r.vunit*@dst.outerHeight()/cr 63 | cc = r.hunit*@src.outerWidth()/cr 64 | dd = r.vunit*@src.outerHeight()/cr 65 | s = x:p.left - r.left + cc, y:p.top - r.top + dd 66 | t = x:q.left - r.left - aa, y:q.top - r.top - bb 67 | 68 | @width r.width 69 | @height r.height 70 | @offset left:r.left, top:r.top 71 | 72 | @find("svg").attr(width:r.width, height:r.height) 73 | .find("line") 74 | .attr x1:s.x, y1:s.y, x2:t.x, y2:t.y 75 | 76 | module.exports = Relationship 77 | -------------------------------------------------------------------------------- /views/common.styl: -------------------------------------------------------------------------------- 1 | @import url("//fonts.googleapis.com/css?family=Coda:800|Questrial|Michroma") 2 | @import url("//fonts.googleapis.com/css?family=Russo+One") 3 | @import url("/public/bootstrap/docs/assets/css/bootstrap.css") 4 | // @import url("/public/bootstrap/docs/assets/css/bootstrap-responsive.css") 5 | 6 | // syntaxhighlighter 7 | @import url("/public/syntaxhighlighter/styles/shCore.css") 8 | @import url("/public/syntaxhighlighter/styles/shThemeDefault.css") 9 | 10 | .syntaxhighlighter .line.alt1, 11 | .syntaxhighlighter .line.alt2, 12 | .syntaxhighlighter { 13 | background-image: none !important; 14 | background-color: transparent !important; 15 | .toolbar {display:none;} 16 | } 17 | .syntaxhighlighter { 18 | background-color: whiteSmoke !important; 19 | padding: 8.5px; 20 | margin: 0 0 18px 0; 21 | border: 1px solid rgba(0,0,0,0.15); 22 | -webkit-border-radius: 3px; 23 | -moz-border-radius: 3px; 24 | -o-border-radius: 3px; 25 | border-radius: 3px; 26 | .gutter .line { 27 | border-right: 3px solid rgba(0,107,159,0.5) !important; 28 | } 29 | } 30 | 31 | // @import url('//mplus-fonts.sourceforge.jp/webfonts/mplus-1p-thin.ttf') 32 | // @import url('//mplus-fonts.sourceforge.jp/webfonts/mplus-1p-light.ttf') 33 | @import url('//mplus-fonts.sourceforge.jp/webfonts/mplus-1p-regular.ttf') 34 | // @import url('//mplus-fonts.sourceforge.jp/webfonts/mplus-1p-bold.ttf') 35 | // @import url('//mplus-fonts.sourceforge.jp/webfonts/mplus-1p-heavy.ttf') 36 | // @import url('//mplus-fonts.sourceforge.jp/webfonts/mplus-1p-black.ttf') 37 | 38 | L-EMBOSS(a = 1) 39 | text-shadow -1px 1px 0 rgba(255,255,255,a) 40 | 41 | D-EMBOSS(a = 1) 42 | text-shadow 1px -1px 0 rgba(0,0,0,a) 43 | 44 | THICK-DARK() 45 | background-color #33342d 46 | color #d2d8ba 47 | border-top solid 1px darken(white, 33%) 48 | border-bottom solid 1px darken(#33342d, 7%) 49 | 50 | LIGHT-DARK() 51 | background-color #46483e 52 | color #d2d8ba 53 | border-bottom solid 1px darken(#46483e, 7%) 54 | 55 | body 56 | border-top solid 6px #8cc84b 57 | 58 | .syntaxhighlighter 59 | width auto !important 60 | .gutter .line 61 | border-right 3px solid rgba(6*16/2, 9*16/2, 0, 0.5) !important 62 | .string, .string a 63 | color #690 !important 64 | 65 | .logo 66 | font-family Coda,Tahoma,sans-serif 67 | font-weight extra-bold 68 | 69 | a 70 | color #690 71 | 72 | body 73 | background-color #eee 74 | 75 | h1.logo 76 | text-align center 77 | L-EMBOSS() 78 | 79 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/scripts/shBrushPython.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | ;(function() 18 | { 19 | // CommonJS 20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; 21 | 22 | function Brush() 23 | { 24 | // Contributed by Gheorghe Milas and Ahmad Sherif 25 | 26 | var keywords = 'and assert break class continue def del elif else ' + 27 | 'except exec finally for from global if import in is ' + 28 | 'lambda not or pass print raise return try yield while'; 29 | 30 | var funcs = '__import__ abs all any apply basestring bin bool buffer callable ' + 31 | 'chr classmethod cmp coerce compile complex delattr dict dir ' + 32 | 'divmod enumerate eval execfile file filter float format frozenset ' + 33 | 'getattr globals hasattr hash help hex id input int intern ' + 34 | 'isinstance issubclass iter len list locals long map max min next ' + 35 | 'object oct open ord pow print property range raw_input reduce ' + 36 | 'reload repr reversed round set setattr slice sorted staticmethod ' + 37 | 'str sum super tuple type type unichr unicode vars xrange zip'; 38 | 39 | var special = 'None True False self cls class_'; 40 | 41 | this.regexList = [ 42 | { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, 43 | { regex: /^\s*@\w+/gm, css: 'decorator' }, 44 | { regex: /(['\"]{3})([^\1])*?\1/gm, css: 'comments' }, 45 | { regex: /"(?!")(?:\.|\\\"|[^\""\n])*"/gm, css: 'string' }, 46 | { regex: /'(?!')(?:\.|(\\\')|[^\''\n])*'/gm, css: 'string' }, 47 | { regex: /\+|\-|\*|\/|\%|=|==/gm, css: 'keyword' }, 48 | { regex: /\b\d+\.?\w*/g, css: 'value' }, 49 | { regex: new RegExp(this.getKeywords(funcs), 'gmi'), css: 'functions' }, 50 | { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, 51 | { regex: new RegExp(this.getKeywords(special), 'gm'), css: 'color1' } 52 | ]; 53 | 54 | this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); 55 | }; 56 | 57 | Brush.prototype = new SyntaxHighlighter.Highlighter(); 58 | Brush.aliases = ['py', 'python']; 59 | 60 | SyntaxHighlighter.brushes.Python = Brush; 61 | 62 | // CommonJS 63 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null; 64 | })(); 65 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Dec 20 2014 18:15:19 GMT+0900 (JST) 3 | var travis_enabled = process.env["KARMA_OPTIONS"] ? true : false; 4 | 5 | module.exports = function(config) { 6 | config.set({ 7 | 8 | // base path that will be used to resolve all patterns (eg. files, exclude) 9 | basePath: '', 10 | 11 | 12 | // frameworks to use 13 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 14 | frameworks: ['jasmine'], 15 | 16 | 17 | // list of files / patterns to load in the browser 18 | files: [ 19 | 'node_modules/jquery/dist/jquery.js', 20 | 'vendor/coffee-script.js', 21 | 'dist/jumly.css', 22 | 'spec/ClassDiagramSpec.coffee', 23 | 'spec/DiagramSpec.coffee', 24 | 'spec/HTMLElementSpec.coffee', 25 | 'spec/RobustnessDiagramBuilderSpec.coffee', 26 | 'spec/SequenceDiagramBuilderSpec.coffee', 27 | 'spec/SequenceDiagramLayoutSpec.coffee', 28 | 'spec/SequenceDiagramSpec.coffee', 29 | 'spec/SequenceParticipantSpec.coffee', 30 | 'spec/apiSpec.coffee', 31 | 'spec/coreSpec.coffee', 32 | 'spec/issuesSpec.coffee', 33 | ], 34 | 35 | 36 | // list of files to exclude 37 | exclude: [ 38 | ], 39 | 40 | 41 | // preprocess matching files before serving them to the browser 42 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 43 | preprocessors: { 44 | '**/*.coffee': ['webpack'], 45 | }, 46 | 47 | 48 | webpack: function() { 49 | var conf = require("./webpack.config.js") 50 | delete conf.entry.spec; 51 | return conf; 52 | }(), 53 | 54 | 55 | // test results reporter to use 56 | // possible values: 'dots', 'progress' 57 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 58 | reporters: ['progress'], 59 | 60 | 61 | // web server port 62 | port: 9876, 63 | 64 | 65 | // enable / disable colors in the output (reporters and logs) 66 | colors: true, 67 | 68 | 69 | // level of logging 70 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 71 | logLevel: config.LOG_INFO, 72 | 73 | 74 | // enable / disable watching file and executing tests whenever any file changes 75 | autoWatch: !travis_enabled, 76 | 77 | 78 | // start these browsers 79 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 80 | browsers: ['Chrome'], 81 | //browsers: ['PhantomJS'], 82 | 83 | 84 | // Continuous Integration mode 85 | // if true, Karma captures browsers, runs the tests and exits 86 | singleRun: travis_enabled, 87 | 88 | 89 | customLaunchers: { 90 | ChromeTravis: { 91 | base: 'Chrome', 92 | flags: ['--no-sandbox'] 93 | } 94 | }, 95 | }); 96 | }; 97 | -------------------------------------------------------------------------------- /spec/SequenceDiagramSpec.coffee: -------------------------------------------------------------------------------- 1 | utils = require "./jasmine-utils.coffee" 2 | SequenceDiagram = require "SequenceDiagram.coffee" 3 | SequenceParticipant = require "SequenceParticipant.coffee" 4 | SequenceOccurrence = require "SequenceOccurrence.coffee" 5 | SequenceFragment = require "SequenceFragment.coffee" 6 | SequenceRef = require "SequenceRef.coffee" 7 | SequenceDiagramBuilder = require "SequenceDiagramBuilder.coffee" 8 | SequenceDiagramLayout = require "SequenceDiagramLayout.coffee" 9 | 10 | describe "SequenceDiagram", -> 11 | div = utils.div this 12 | 13 | beforeEach -> 14 | @layout = new SequenceDiagramLayout 15 | @builder = new SequenceDiagramBuilder 16 | utils.matchers this 17 | @diagram = new SequenceDiagram "hello" 18 | 19 | it "has .diagram and .sequence-diagram", -> 20 | expect(@diagram).haveClass "diagram" 21 | expect(@diagram).haveClass "sequence-diagram" 22 | 23 | it "has no elements just after creation", -> 24 | expect(@diagram.find("*").length).toBe 0 25 | 26 | describe "SequenceParticipant", -> 27 | beforeEach -> 28 | @object = new SequenceParticipant "user" 29 | 30 | it "has .participant", -> 31 | expect(@object).haveClass "participant" 32 | 33 | it "has name", -> 34 | expect(@object.find(".name").text()).toBe "user" 35 | 36 | it "has size", -> 37 | div.append @diagram 38 | @diagram.append @object 39 | expect(@object.width()).toBeGreaterThan 0 40 | expect(@object.height()).toBeGreaterThan 0 41 | 42 | describe "SequenceOccurrence", -> 43 | 44 | it "has .occurrence", -> 45 | beforeEach -> 46 | @occurr = new SequenceOccurrence 47 | 48 | describe "SequenceFragment", -> 49 | 50 | beforeEach -> 51 | @fragment = new SequenceFragment "treat all" 52 | 53 | it "has .fragment", -> 54 | expect(@fragment).haveClass "fragment" 55 | 56 | it "has given name", -> 57 | expect(@fragment.find(".name").text()).toBe "treat all" 58 | 59 | describe "SequenceRef", -> 60 | 61 | beforeEach -> 62 | @ref = new SequenceRef "another sequence" 63 | 64 | it "has .ref", -> 65 | expect(@ref).haveClass "ref" 66 | 67 | it "has given name", -> 68 | expect(@ref.find(".name").text()).toBe "another sequence" 69 | 70 | describe "width", -> 71 | describe "issue#31", -> 72 | beforeEach -> 73 | @diagram = @builder.build """ 74 | @found "Browser", -> 75 | @alt { 76 | "[200]": -> @message "GET href resources", "HTTP Server" 77 | "[301]": -> @ref "GET the moved page" 78 | "[404]": -> @ref "show NOT FOUND" 79 | } 80 | @find(".ref").css(width:256, "padding-bottom":4) 81 | .find(".tag").css float:"left" 82 | """ 83 | div.append @diagram 84 | @layout.layout @diagram 85 | 86 | it "is extended by .ref", -> 87 | l0 = utils.left @diagram.find(".ref:eq(0)") 88 | l1 = utils.left @diagram 89 | expect(@diagram.outerWidth() >= (l0 - l1) + 256) 90 | 91 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/scripts/shBrushBash.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | ;(function() 18 | { 19 | // CommonJS 20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; 21 | 22 | function Brush() 23 | { 24 | var keywords = 'if fi then elif else for do done until while break continue case function return in eq ne ge le'; 25 | var commands = 'alias apropos awk basename bash bc bg builtin bzip2 cal cat cd cfdisk chgrp chmod chown chroot' + 26 | 'cksum clear cmp comm command cp cron crontab csplit cut date dc dd ddrescue declare df ' + 27 | 'diff diff3 dig dir dircolors dirname dirs du echo egrep eject enable env ethtool eval ' + 28 | 'exec exit expand export expr false fdformat fdisk fg fgrep file find fmt fold format ' + 29 | 'free fsck ftp gawk getopts grep groups gzip hash head history hostname id ifconfig ' + 30 | 'import install join kill less let ln local locate logname logout look lpc lpr lprint ' + 31 | 'lprintd lprintq lprm ls lsof make man mkdir mkfifo mkisofs mknod more mount mtools ' + 32 | 'mv netstat nice nl nohup nslookup open op passwd paste pathchk ping popd pr printcap ' + 33 | 'printenv printf ps pushd pwd quota quotacheck quotactl ram rcp read readonly renice ' + 34 | 'remsync rm rmdir rsync screen scp sdiff sed select seq set sftp shift shopt shutdown ' + 35 | 'sleep sort source split ssh strace su sudo sum symlink sync tail tar tee test time ' + 36 | 'times touch top traceroute trap tr true tsort tty type ulimit umask umount unalias ' + 37 | 'uname unexpand uniq units unset unshar useradd usermod users uuencode uudecode v vdir ' + 38 | 'vi watch wc whereis which who whoami Wget xargs yes' 39 | ; 40 | 41 | this.regexList = [ 42 | { regex: /^#!.*$/gm, css: 'preprocessor bold' }, 43 | { regex: /\/[\w-\/]+/gm, css: 'plain' }, 44 | { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' }, // one line comments 45 | { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings 46 | { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings 47 | { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // keywords 48 | { regex: new RegExp(this.getKeywords(commands), 'gm'), css: 'functions' } // commands 49 | ]; 50 | } 51 | 52 | Brush.prototype = new SyntaxHighlighter.Highlighter(); 53 | Brush.aliases = ['bash', 'shell']; 54 | 55 | SyntaxHighlighter.brushes.Bash = Brush; 56 | 57 | // CommonJS 58 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null; 59 | })(); 60 | -------------------------------------------------------------------------------- /app.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | express = require "express" 3 | jade = require "jade" 4 | stylus = require "stylus" 5 | nib = require "nib" 6 | fs = require "fs" 7 | http = require 'http' 8 | domain = require 'domain' 9 | 10 | views_dir = "#{__dirname}/views" 11 | static_dir = "#{views_dir}/static" 12 | 13 | app = express() 14 | app.configure -> 15 | app.set 'port', (process.env.PORT || 3000) 16 | app.set "views", views_dir 17 | app.set "view engine", "jade" 18 | 19 | app.use express.bodyParser() 20 | app.use express.methodOverride() 21 | 22 | app.use express.cookieParser (secret = 'adf19dfe1a4bbdd949326870e3997d799b758b9b') 23 | app.use express.session secret:secret 24 | app.use express.logger 'dev' 25 | 26 | app.use (req, res, next)-> 27 | if req.is 'text/*' 28 | req.text = '' 29 | req.setEncoding 'utf8' 30 | req.on 'data', (chunk)-> req.text += chunk 31 | req.on 'end', next 32 | else 33 | next() 34 | 35 | app.use stylus.middleware 36 | src: views_dir 37 | dest: static_dir 38 | compile: (str, path, fn)-> 39 | stylus(str) 40 | .set('filename', path) 41 | .set('compress', true) 42 | .use(nib()).import('nib') 43 | 44 | app.use '/public', express.static "#{__dirname}/public" 45 | app.use '/', express.static static_dir 46 | app.use app.router 47 | app.use express.favicon() 48 | 49 | app.use (req, res, next)-> 50 | res.status 404 51 | res.render '404', req._parsedUrl 52 | 53 | 54 | require("underscore").extend jade.filters, 55 | code: (str, args)-> 56 | type = args.type or "javascript" 57 | str = str.replace /\\n/g, '\n' 58 | js = str.replace(/\\/g, '\\\\').replace /\n/g, '\\n' 59 | """
      #{js}
      """ 60 | jumly: (body, attrs)-> 61 | type = attrs.type or "sequence" 62 | id = if attrs.id then "id=#{attrs.id}" else "" 63 | body = body.replace /\\n/g, '\n' 64 | js = body.replace(/\\/g, '\\\\').replace /\n/g, '\\n' 65 | """""" 66 | 67 | 68 | pkg = JSON.parse fs.readFileSync("package.json") 69 | ctx = 70 | version: pkg.version 71 | paths: 72 | release: "public" 73 | images: "public/images" 74 | tested_version: 75 | node: pkg.engines.node 76 | jquery: pkg.dependencies["jquery"] 77 | coffeescript: pkg.dependencies["coffee-script"] 78 | 79 | 80 | routes = require("./routes") ctx 81 | api = require("./routes/api") ctx 82 | 83 | app.get "/", routes.html "index" 84 | app.get "/index.html", routes.html "index" 85 | app.get "/reference.html", routes.html "reference" 86 | app.get "/api.html", routes.html "api" 87 | app.get "/try.html", routes.html "try" 88 | app.get "/api/diagrams", api.diagrams.get 89 | app.post "/api/diagrams", api.diagrams.post 90 | 91 | # redirect 302 92 | app.get "/:path([a-z]+)", (req, res)-> res.redirect "/#{req.params.path}.html" 93 | 94 | 95 | http.createServer(app).listen app.get('port'), -> 96 | console.log "Express server listening on port #{app.get('port')}" 97 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/scripts/shBrushGroovy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | ;(function() 18 | { 19 | // CommonJS 20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; 21 | 22 | function Brush() 23 | { 24 | // Contributed by Andres Almiray 25 | // http://jroller.com/aalmiray/entry/nice_source_code_syntax_highlighter 26 | 27 | var keywords = 'as assert break case catch class continue def default do else extends finally ' + 28 | 'if in implements import instanceof interface new package property return switch ' + 29 | 'throw throws try while public protected private static'; 30 | var types = 'void boolean byte char short int long float double'; 31 | var constants = 'null'; 32 | var methods = 'allProperties count get size '+ 33 | 'collect each eachProperty eachPropertyName eachWithIndex find findAll ' + 34 | 'findIndexOf grep inject max min reverseEach sort ' + 35 | 'asImmutable asSynchronized flatten intersect join pop reverse subMap toList ' + 36 | 'padRight padLeft contains eachMatch toCharacter toLong toUrl tokenize ' + 37 | 'eachFile eachFileRecurse eachB yte eachLine readBytes readLine getText ' + 38 | 'splitEachLine withReader append encodeBase64 decodeBase64 filterLine ' + 39 | 'transformChar transformLine withOutputStream withPrintWriter withStream ' + 40 | 'withStreams withWriter withWriterAppend write writeLine '+ 41 | 'dump inspect invokeMethod print println step times upto use waitForOrKill '+ 42 | 'getText'; 43 | 44 | this.regexList = [ 45 | { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments 46 | { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments 47 | { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings 48 | { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings 49 | { regex: /""".*"""/g, css: 'string' }, // GStrings 50 | { regex: new RegExp('\\b([\\d]+(\\.[\\d]+)?|0x[a-f0-9]+)\\b', 'gi'), css: 'value' }, // numbers 51 | { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, // goovy keyword 52 | { regex: new RegExp(this.getKeywords(types), 'gm'), css: 'color1' }, // goovy/java type 53 | { regex: new RegExp(this.getKeywords(constants), 'gm'), css: 'constants' }, // constants 54 | { regex: new RegExp(this.getKeywords(methods), 'gm'), css: 'functions' } // methods 55 | ]; 56 | 57 | this.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags); 58 | } 59 | 60 | Brush.prototype = new SyntaxHighlighter.Highlighter(); 61 | Brush.aliases = ['groovy']; 62 | 63 | SyntaxHighlighter.brushes.Groovy = Brush; 64 | 65 | // CommonJS 66 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null; 67 | })(); 68 | -------------------------------------------------------------------------------- /views/try.jade: -------------------------------------------------------------------------------- 1 | extends _layouts/default 2 | 3 | block title 4 | title JUMLY Demo to edit an UML sequence diagram interactive, try me. 5 | 6 | block prepend description 7 | - var _ = "You can try JUMLY here interactively changing an example snippet."; 8 | - _ += " Then you can see the update of the sequence diagram as soon as you change."; 9 | - var __description = _; 10 | 11 | block styles 12 | link(rel='stylesheet', href="/try.css") 13 | :stylus 14 | .link-to-image 15 | margin-top 1em 16 | #link_img 17 | font-size 12px 18 | #open_btn_1 19 | margin-left 1em 20 | 21 | block navbar 22 | include _includes/navbar 23 | 24 | block content 25 | .container 26 | .row 27 | .span6 28 | header 29 | h1.logo Try 30 | small 31 | a(href='/') JUMLY 32 | span.desc Interactive Demo 33 | .span6 34 | //.alert.alert-success 35 | Heads up! Available to get .png file 36 | a#open_btn_1.btn.btn-success.btn-mini Open 37 | 38 | .row 39 | .span6 40 | p 41 | span.label.label-success Heads up! 42 | | tapioca in beta to save your diagram. 43 | 44 | p This page allows you to edit a sequence diagram interactively, 45 | |and you can get the image. 46 | 47 | p Try changing below. Available directives are @found, 48 | |@message, @create, @reply, @alt, @loop, @ref and @note. 49 | |In more detail, see the reference. 50 | 51 | textarea#code(rows=10) 52 | |@found "You", -> 53 | | @message "Think", -> 54 | | @message "Write your idea", "JUMLY", -> 55 | | @create "Diagram" 56 | |jumly.css "background-color":"#8CC84B" 57 | 58 | .link-to-image 59 | p 60 | span.label.label-success Heads up! 61 | | To get image, click below link or copy with browser. 62 | a#link_img(target="_blank") 63 | 64 | #notification 65 | 66 | #disqus_thread 67 | script(src='public/js/disqus.js') 68 | 69 | .span6 70 | #diagram_container 71 | 72 | footer 73 | .copyright 74 | include _includes/copyright 75 | 76 | include _includes/fork-me-rt 77 | 78 | block scripts 79 | :coffeescript 80 | _link = -> location.origin + "/api/diagrams?data=" + encodeURIComponent $("#code").val() 81 | 82 | _gen_link = -> 83 | url = _link() 84 | $("#link_img").text(url.slice(0, 50) + "...").attr "href", url 85 | 86 | $ -> 87 | $("#open_btn_1").on 'click', -> window.open _link() 88 | 89 | compile = -> 90 | $(code).removeClass "failed" 91 | $(notification).text "" 92 | try 93 | JUMLY.eval $(code), into:$(diagram_container) 94 | _gen_link() 95 | catch ex 96 | $(code).addClass "failed" 97 | $(notification).text ex 98 | Raven.captureException ex 99 | 100 | compile() 101 | 102 | id = -1 103 | $(code).on "keyup", (a,b,c)-> 104 | clearTimeout id 105 | id = setTimeout compile, 500 106 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/styles/shThemeDefault.css: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | .syntaxhighlighter { 18 | background-color: white !important; 19 | } 20 | .syntaxhighlighter .line.alt1 { 21 | background-color: white !important; 22 | } 23 | .syntaxhighlighter .line.alt2 { 24 | background-color: white !important; 25 | } 26 | .syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { 27 | background-color: #e0e0e0 !important; 28 | } 29 | .syntaxhighlighter .line.highlighted.number { 30 | color: black !important; 31 | } 32 | .syntaxhighlighter table caption { 33 | color: black !important; 34 | } 35 | .syntaxhighlighter .gutter { 36 | color: #afafaf !important; 37 | } 38 | .syntaxhighlighter .gutter .line { 39 | border-right: 3px solid #6ce26c !important; 40 | } 41 | .syntaxhighlighter .gutter .line.highlighted { 42 | background-color: #6ce26c !important; 43 | color: white !important; 44 | } 45 | .syntaxhighlighter.printing .line .content { 46 | border: none !important; 47 | } 48 | .syntaxhighlighter.collapsed { 49 | overflow: visible !important; 50 | } 51 | .syntaxhighlighter.collapsed .toolbar { 52 | color: blue !important; 53 | background: white !important; 54 | border: 1px solid #6ce26c !important; 55 | } 56 | .syntaxhighlighter.collapsed .toolbar a { 57 | color: blue !important; 58 | } 59 | .syntaxhighlighter.collapsed .toolbar a:hover { 60 | color: red !important; 61 | } 62 | .syntaxhighlighter .toolbar { 63 | color: white !important; 64 | background: #6ce26c !important; 65 | border: none !important; 66 | } 67 | .syntaxhighlighter .toolbar a { 68 | color: white !important; 69 | } 70 | .syntaxhighlighter .toolbar a:hover { 71 | color: black !important; 72 | } 73 | .syntaxhighlighter .plain, .syntaxhighlighter .plain a { 74 | color: black !important; 75 | } 76 | .syntaxhighlighter .comments, .syntaxhighlighter .comments a { 77 | color: #008200 !important; 78 | } 79 | .syntaxhighlighter .string, .syntaxhighlighter .string a { 80 | color: blue !important; 81 | } 82 | .syntaxhighlighter .keyword { 83 | color: #006699 !important; 84 | } 85 | .syntaxhighlighter .preprocessor { 86 | color: gray !important; 87 | } 88 | .syntaxhighlighter .variable { 89 | color: #aa7700 !important; 90 | } 91 | .syntaxhighlighter .value { 92 | color: #009900 !important; 93 | } 94 | .syntaxhighlighter .functions { 95 | color: #ff1493 !important; 96 | } 97 | .syntaxhighlighter .constants { 98 | color: #0066cc !important; 99 | } 100 | .syntaxhighlighter .script { 101 | font-weight: bold !important; 102 | color: #006699 !important; 103 | background-color: none !important; 104 | } 105 | .syntaxhighlighter .color1, .syntaxhighlighter .color1 a { 106 | color: gray !important; 107 | } 108 | .syntaxhighlighter .color2, .syntaxhighlighter .color2 a { 109 | color: #ff1493 !important; 110 | } 111 | .syntaxhighlighter .color3, .syntaxhighlighter .color3 a { 112 | color: red !important; 113 | } 114 | 115 | .syntaxhighlighter .keyword { 116 | font-weight: bold !important; 117 | } 118 | -------------------------------------------------------------------------------- /lib/js/api.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Some public APIs which are experimental 3 | ### 4 | _type = "text/jumly+sequence" 5 | 6 | _t2l = # type 2 logic 7 | "text/jumly+sequence": 8 | builder: require "SequenceDiagramBuilder.coffee" 9 | layout: require "SequenceDiagramLayout.coffee" 10 | "text/jumly+robustness": 11 | builder: require "RobustnessDiagramBuilder.coffee" 12 | layout: require "RobustnessDiagramLayout.coffee" 13 | 14 | _compile = (code, type = _type)-> 15 | throw "unknown type: #{type}" unless _t2l[type] 16 | (new _t2l[type].builder).build code 17 | 18 | _layout = (doc, type = _type)-> 19 | throw "unknown type: #{type}" unless _t2l[type] 20 | (new _t2l[type].layout).layout doc 21 | 22 | ## returns JUMLY meta object 23 | _to_meta = ($src)-> 24 | throw new Error("data method is missing -- #{$src}") unless $src.data 25 | meta = $src.data _mkey 26 | if meta is undefined 27 | $src.data _mkey, meta = {} 28 | else if typeof meta is "string" 29 | $src.data _mkey, meta = type:meta 30 | else if typeof meta is "object" 31 | meta # nop 32 | else 33 | throw "unknown type: #{typeof meta}" 34 | meta.type = $src.attr("type") if _is_script $src[0] 35 | meta 36 | 37 | ## returns true if n is 38 | _is_script = (n)-> n.nodeName.toLowerCase() is "script" 39 | 40 | ## returns value of node 41 | _val = (s)-> 42 | switch s[0].nodeName.toLowerCase() 43 | when "textarea", "input" then s.val() 44 | else s.text() 45 | 46 | ### 47 | node: a jQuery node 48 | To get jumly script from it. 49 | 50 | opts: function | object 51 | If object, it must have "into" property which value is 52 | acceptable by $() like selector, dom and jQuery object. 53 | 54 | If function, it must return a function in order to put 55 | a new diagram node into the document. 56 | 1st arg is the diagram node, 2nd arg is jQuery object 57 | having the source jumly code. 58 | ### 59 | _mkey = "jumly" # meta data key 60 | 61 | _eval = ($src, opts)-> 62 | meta = _to_meta $src 63 | 64 | d = _compile _val($src), meta.type 65 | if typeof opts is "function" 66 | opts d, $src 67 | else if typeof opts is "object" 68 | throw "missing `into`" unless opts.into 69 | $(opts.into).html d 70 | else 71 | throw "no idea to place a new diagram." 72 | 73 | _layout d, meta.type 74 | 75 | $.extend meta, "dst":d 76 | d.data _mkey, "src":$src 77 | 78 | ### 79 | scope: DOM | jQuery nodeset 80 | nodeset to scan 81 | opts: 82 | finder: function 83 | to find candidated nodes 84 | placer: function 85 | to put new created diagram into somewhere 86 | ### 87 | _opts = 88 | finder: ($n)-> 89 | nodes = $n.find "script, *[data-jumly]" 90 | filter = (n)-> unless _is_script n 91 | true 92 | else 93 | $(n).attr("type")?.match /text\/jumly\+.*/ 94 | e for e in nodes when filter(e) 95 | placer: (d, $e)-> $e.after d 96 | 97 | _scan = (scope = document, opts)-> 98 | p = $.extend {}, _opts, opts 99 | for e in p.finder($ scope) 100 | $e = $(e) 101 | if dst = $e.data(_mkey)?.dst 102 | if p.synchronize 103 | _eval $e, into:dst 104 | ## skip already evaluated ones if no synchronize 105 | else 106 | _eval $e, p.placer 107 | 108 | global.JUMLY = 109 | eval: _eval 110 | scan: _scan 111 | 112 | module.exports = global.JUMLY 113 | 114 | -------------------------------------------------------------------------------- /routes/api.coffee: -------------------------------------------------------------------------------- 1 | fs = require "fs" 2 | child_process = require "child_process" 3 | tmp = require 'tmp' 4 | 5 | _unlink = (path)-> 6 | fs.unlink path, (err)-> 7 | if err 8 | console.error "unlink: #{err}" 9 | #else 10 | # console.log "unlink: #{path}" 11 | 12 | _400_if_not_have = (res, str, vals)-> 13 | unless str.toLowerCase() in vals 14 | res.status 400 15 | res.setHeader "content-type", "text/plain" 16 | res.write "unsupported media-type: #{str}" 17 | res.end() 18 | true 19 | 20 | _senderr = (err, res, status = 500)-> 21 | console.error err 22 | res.status status 23 | res.write JSON.stringify(err) 24 | res.end() 25 | 26 | _diagrams = (is_post, jmcode, req, res)-> 27 | tmp.file (err, tmp_path, tmp_fd)-> 28 | return _senderr(err, res) if err 29 | 30 | fs.write tmp_fd, jmcode 31 | fs.close tmp_fd, (err)-> 32 | if err 33 | _unlink tmp_path 34 | return _senderr(err, res) 35 | 36 | if is_post 37 | #req.headers["content-type"].match /(^[^\/]+)\/([^+]+)\+?(.*)$/ 38 | [_, encoding, format, base64] = req.headers["accept"].match /(^[^\/]+)\/([^;]+)(;.*)?$/ 39 | encoding = "base64" if base64 is ";base64" and encoding.match /image/i 40 | encoding = "html" if encoding.match(/^text$/i) and format.match(/^html$/i) 41 | format = "png" if format is "*" 42 | encoding = "image" if encoding is "*" 43 | 44 | return if _400_if_not_have res, encoding, ["image", "base64", "html"] 45 | return if _400_if_not_have res, format, ["png", "gif", "jpg", "jpeg", "html"] 46 | else 47 | format = "png" 48 | encoding = "image" 49 | 50 | ## jumly prints tmpfile path to stdout if it creates image file 51 | filepath = "" 52 | if encoding.match /image/ ## jumly prints the filepath to stdout 53 | stdouth = (data)-> filepath += data 54 | else 55 | stdouth = (data)-> res.write data 56 | 57 | ## cmd path 58 | cmd = "#{__dirname}/../bin/jumly" 59 | console.log cmd, tmp_path, format, encoding 60 | 61 | ## exec jumly 62 | proc = child_process.spawn cmd, [tmp_path, format, encoding] 63 | proc.stdout.on 'data', stdouth 64 | proc.stderr.on 'data', (data)-> res.write data 65 | proc.on 'error', (err)-> console.error "error:", err 66 | 67 | proc.on 'close', (code)-> 68 | console.log "close:", code 69 | if filepath 70 | fs.readFile filepath.trim(), flags:"rb", (err, data)-> 71 | if err 72 | _senderr err, res, 400 73 | else 74 | res.write data 75 | res.end() 76 | _unlink tmp_path 77 | _unlink filepath.trim() 78 | else 79 | res.end() 80 | _unlink tmp_path 81 | 82 | module.exports = (ctx)-> 83 | b64decode: (req, res)-> 84 | b64 = req.body.data.replace /^data:image\/png;base64,/, "" 85 | buf = new Buffer(b64, 'base64').toString 'binary' 86 | res.contentType "image/png" 87 | res.header "Content-Disposition", "attachment; filename=" + "diagram.png" 88 | res.status 201 89 | res.end buf, "binary" 90 | 91 | diagrams: 92 | get: (req, res)-> 93 | res.setHeader "content-type", "image/png" 94 | #if req.query["data"].match /%/ 95 | data = unescape req.query["data"] 96 | #else 97 | # data = new Buffer(req.query["data"], 'base64').toString('ascii') 98 | _diagrams false, data, req, res 99 | 100 | post: (req, res)-> 101 | _diagrams true, req.text, req, res 102 | -------------------------------------------------------------------------------- /lib/js/SequenceOccurrence.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | pos = require "position.coffee" 3 | HTMLElement = require "HTMLElement.coffee" 4 | SequenceInteraction = require "SequenceInteraction.coffee" 5 | SequenceFragment = require "SequenceFragment.coffee" 6 | 7 | class SequenceOccurrence extends HTMLElement 8 | constructor: (@_actor)-> 9 | super() 10 | 11 | SequenceOccurrence::interact = (actor, acts) -> 12 | if acts?.stereotype is ".lost" 13 | occurr = new SequenceOccurrence().addClass "icon" 14 | iact = new SequenceInteraction this, occurr 15 | iact.addClass "lost" 16 | else if acts?.stereotype is ".destroy" 17 | #NOTE: Destroy message building 18 | else if actor?.stereotype is ".alt" 19 | alt = new SequenceFragment "alt" 20 | alt.alter this, acts 21 | return this 22 | else 23 | occurr = new SequenceOccurrence actor 24 | iact = new SequenceInteraction this, occurr 25 | if actor is iact._actor._actor 26 | iact.addClass "self" 27 | iact.append(occurr).appendTo this 28 | iact 29 | 30 | SequenceOccurrence::create = (objsrc) -> 31 | SequenceParticipant = require "SequenceParticipant.coffee" 32 | obj = new SequenceParticipant(objsrc.name) 33 | .addClass "created-by" 34 | @_actor.parent().append obj 35 | iact = (@interact obj) 36 | .addClass("creating") 37 | .find(".message") 38 | .addClass("create") 39 | .end() 40 | 41 | SequenceOccurrence::_move_horizontally = -> 42 | if @parent().hasClass "lost" 43 | offset left:pos.mostLeftRight(@parents(".diagram").find(".participant")).right 44 | return this 45 | if not @is_on_another() 46 | left = @_actor.offset().left + (@_actor.preferred_width() - @width())/2 47 | else 48 | left = @_parent_occurr().offset().left 49 | left += @width()*@_shift_to_parent()/2 50 | @offset left:left 51 | 52 | SequenceOccurrence::is_on_another =-> 53 | not (@_parent_occurr() is null) 54 | 55 | SequenceOccurrence::is_self = -> 56 | @parents(".interaction:eq(0)").hasClass("self") 57 | 58 | SequenceOccurrence::_parent_occurr = -> 59 | occurrs = @parents ".occurrence" 60 | return null if occurrs.length is 0 61 | 62 | for i in [0..occurrs.length - 1] 63 | if @_actor is $(occurrs[i]).data("_self")._actor 64 | return $(occurrs[i]).data("_self") 65 | null 66 | 67 | SequenceOccurrence::_shift_to_parent = -> 68 | return 0 if not @is_on_another() 69 | # find a message contained in the same interaction together. 70 | a = @parent().find(".message:eq(0)").data("_self") 71 | return 0 if a is undefined 72 | return -1 if a.isTowardRight() 73 | return 1 if a.isTowardLeft() 74 | # in case of self-invokation below 75 | return 1 76 | 77 | SequenceOccurrence::preceding = (obj) -> 78 | f = (ll) -> 79 | a = jumly(ll.parents ".occurrence:eq(0)")[0] 80 | return null if !a 81 | return a if a.gives(".participant") is obj 82 | return f a 83 | f this 84 | 85 | SequenceOccurrence::destroy = (actee) -> 86 | #NOTE: expecting interface 87 | #return @interact(actee, {stereotype:"destroy"}) 88 | #Tentative deprecated implementation. 89 | occur = @interact(actee) 90 | #.stereotype("destroy") 91 | .data("_self")._actee 92 | if occur.is_on_another() 93 | occur = occur._parent_occurr() 94 | 95 | $("
      ").addClass("stop") 96 | .append($("
      ").addClass("icon") 97 | .addClass("square") 98 | .addClass("cross")) 99 | .insertAfter(occur) 100 | occur 101 | 102 | module.exports = SequenceOccurrence 103 | -------------------------------------------------------------------------------- /lib/js/SequenceInteraction.coffee: -------------------------------------------------------------------------------- 1 | core = require "core.coffee" 2 | $ = require "jquery" 3 | HTMLElement = require "HTMLElement.coffee" 4 | SequenceMessage = require "SequenceMessage.coffee" 5 | SequenceFragment = require "SequenceFragment.coffee" 6 | 7 | class SequenceInteraction extends HTMLElement 8 | constructor: (@_actor, @_actee)-> 9 | self = this 10 | super null, (me)-> 11 | me.append new SequenceMessage self 12 | 13 | SequenceInteraction::interact = (obj) -> @awayfrom().interact obj 14 | SequenceInteraction::forward = (obj) -> @toward() 15 | 16 | SequenceInteraction::to = (func) -> 17 | occurrs = @gives(".occurrence") 18 | tee = occurrs.as(".actee") 19 | tor = occurrs.as(".actor") 20 | func(tee, tor) 21 | 22 | SequenceInteraction::forwardTo = -> @gives(".occurrence").as ".actee" 23 | SequenceInteraction::backwardTo = -> @gives(".occurrence").as ".actor" 24 | SequenceInteraction::toward = -> @forwardTo() 25 | SequenceInteraction::awayfrom = (obj) -> 26 | return @backwardTo() unless obj 27 | for e in @parents(".occurrence").not(".activated") 28 | e = core.self $(e) 29 | return e if e?.gives(".participant") is obj 30 | obj.activate() 31 | 32 | SequenceInteraction::_compose_ = -> 33 | that = this 34 | src = @_actor 35 | dst = @_actee 36 | msg = that.find("> .message").data("_self") 37 | # Self-invokation case 38 | if @isToSelf() 39 | @_buildSelfInvocation src, dst, msg 40 | return 41 | 42 | # Determine the width of interaction for normal message 43 | w = src.offset().left - (dst.offset().left + $(".occurrence:eq(0)", that).width()) 44 | if @hasClass("lost") 45 | msg.height dst.outerHeight() 46 | else if msg.isTowardLeft() 47 | w = dst.offset().left - (src.offset().left + $(".occurrence:eq(0)", that).width()) 48 | msg.width(Math.abs(w)) 49 | .offset(left:Math.min(src.offset().left, dst.offset().left)) #TODO: remove me? 50 | .repaint() 51 | 52 | # Return message 53 | rmsg = $("> .message.return:last", that).data "_self" 54 | if rmsg 55 | x = msg.offset().left 56 | actee = rmsg._actee 57 | if actee 58 | newdst = rmsg._findOccurr actee 59 | unless newdst 60 | errmsg = "SemanticError: it wasn't able to reply back to '#{actee.find('.name').text()}' which is missing" 61 | throw new Error errmsg 62 | w = dst.offset().left - newdst.offset().left 63 | x = Math.min dst.offset().left, newdst.offset().left 64 | rmsg.width(Math.abs w) 65 | .offset(left:x) 66 | .addClass("reverse") 67 | .repaint() 68 | 69 | SequenceInteraction::_buildSelfInvocation = (a, b, msg) -> 70 | w = @find(".occurrence:eq(0)").outerWidth() ## It's based on the width of occurrence. 71 | dx = w*2 72 | dy = w*1 73 | b.css top:0 + dy # Shift the actee occurrence to y-positive 74 | @css "padding-bottom":dy # To expand the height of occurrence of actor 75 | 76 | msg.css(top:0) 77 | .width(b.width() + dx) 78 | .height(b.offset().top - msg.offset().top + dy + w/8) 79 | .offset left:b.offset().left 80 | 81 | msg.addClass "self" 82 | msg.repaint() 83 | 84 | arrow = msg.find ".arrow" 85 | msg.find(".name").offset 86 | left: arrow.offset().left + arrow.outerWidth() 87 | top : arrow.offset().top 88 | 89 | SequenceInteraction::reply = (p) -> 90 | @addClass "reply" 91 | a = new SequenceMessage(this, p?[".actee"]) 92 | .addClass("return") 93 | .insertAfter @children ".occurrence:eq(0)" 94 | name = (it)-> 95 | return it.name if it?.name 96 | $(p).find(".name:eq(0)").text() 97 | $(a).find(".name:eq(0)").text name p 98 | this 99 | 100 | SequenceInteraction::fragment = (attrs, opts) -> 101 | frag = new SequenceFragment() 102 | frag.enclose(this) 103 | 104 | SequenceInteraction::isToSelf = -> 105 | a = @_actor 106 | b = @_actee 107 | unless a && b 108 | return false 109 | a._actor is b._actor 110 | 111 | SequenceInteraction::is_to_itself = -> @isToSelf() 112 | 113 | module.exports = SequenceInteraction 114 | -------------------------------------------------------------------------------- /views/index.styl: -------------------------------------------------------------------------------- 1 | @import common 2 | 3 | h2,h3,h4 4 | font-family Coda,Tahoma,sans-serif 5 | font-size 18px 6 | 7 | #new-feature, #getting-started, #bg-why, #examples 8 | h2,h3,h4 9 | L-EMBOSS() 10 | 11 | #document-support-information 12 | h2,h3,h4 13 | D-EMBOSS() 14 | 15 | #meta-information 16 | h2,h3,h4 17 | D-EMBOSS(.77) 18 | 19 | #latest-version 20 | text-align center 21 | margin-bottom 1rem 22 | 23 | #intro 24 | line-height 180% 25 | 26 | #new-feature-robustness 27 | .diagram 28 | margin-left auto 29 | margin-right auto 30 | div.robustness-diagram.diagram ~ div 31 | margin-left 80px 32 | margin-right 80px 33 | 34 | #example1 35 | .diagram 36 | margin-left auto 37 | margin-right auto 38 | div.sequence-diagram.diagram ~ div 39 | margin-left 80px 40 | margin-right 80px 41 | .buttons 42 | .button 43 | padding 4px 0 44 | width 152px 45 | -webkit-transition background-color 0.2s ease-in-out 46 | &:hover 47 | background-color darken(darken(white, 25%), 14%) 48 | &.selected 49 | background-color #8bc84b 50 | label:nth-child(1), label:nth-child(2) 51 | .button 52 | margin-right: 2px 53 | &.normal 54 | color inherit 55 | 56 | #getting-started 57 | margin-top 60px 58 | h2 59 | font-size 18px 60 | .diagram 61 | margin-left auto 62 | margin-right auto 63 | margin-top 20px 64 | margin-bottom 20px 65 | 66 | .buttons 67 | L-EMBOSS(.33) 68 | 69 | #popular-links 70 | margin-top 20px 71 | margin-bottom 30px 72 | .buttons 73 | a.download.button, a.apidocs.button, a.reference.button 74 | -webkit-transition background-color 0.2s ease-in-out 75 | -moz-transition background-color 0.2s ease-in-out 76 | &:hover 77 | background-color darken(darken(white, 25%), 14%) 78 | a.download.button, a.reference.button, a.try-jumly.button, a.apidocs 79 | width 200px 80 | a.try-jumly.button 81 | background-color #8bc84b 82 | a.try-jumly.button 83 | display block 84 | text-decoration none 85 | margin-left auto 86 | margin-right auto 87 | text-align center 88 | L-EMBOSS(.33) 89 | padding 8px 0 90 | border-radius 2px 91 | -webkit-transition background-color 0.2s ease-in-out 92 | -moz-transition background-color 0.2s ease-in-out 93 | &:hover 94 | background-color darken(#8bc84b, 14%) 95 | color #46483e 96 | 97 | #bg-why 98 | margin-top 60px 99 | dd 100 | margin-bottom 14px 101 | label span 102 | color #690 103 | 104 | #examples 105 | color inherit 106 | .diagram 107 | margin-left auto 108 | margin-right auto 109 | .example:not(:last-child) 110 | padding-bottom 10px 111 | border-bottom 1px solid rgba(0,0,0,.1) 112 | box-shadow(rgba(255,255,255,1) 0 1px 0) 113 | margin-bottom 30px 114 | .ref 115 | .name 116 | L-EMBOSS(.33) 117 | 118 | #document-support-information 119 | margin-top 60px 120 | padding-bottom 60px 121 | THICK-DARK() 122 | 123 | .history 124 | list-style none 125 | > li:not(:last-child) 126 | margin-bottom 1em 127 | 128 | #meta-information 129 | LIGHT-DARK() 130 | padding-bottom 60px 131 | 132 | .copyright 133 | margin-top 30px 134 | text-align center 135 | font-size smaller 136 | 137 | #quick-links 138 | position fixed 139 | //left 0 140 | bottom 0 141 | .inner:last-child 142 | margin-left 10px 143 | margin-bottom 60px 144 | img 145 | width 24px 146 | -webkit-filter unquote('contrast(10%)') 147 | -moz-filter unquote('contrast(10%)') 148 | -o-filter unquote('contrast(10%)') 149 | filter unquote('contrast(10%)') 150 | &:hover 151 | -webkit-filter unquote('contrast(90%)') 152 | -moz-filter unquote('contrast(90%)') 153 | -o-filter unquote('contrast(90%)') 154 | filter unquote('contrast(90%)') 155 | ul 156 | margin-left 10px 157 | list-style none 158 | 159 | .buttons 160 | text-align center 161 | margin-top 2px 162 | a 163 | text-decoration none 164 | label 165 | display inline-block 166 | .button 167 | display inline-block 168 | padding 8px 0 169 | border-radius 2px 170 | background-color darken(white, 25%) 171 | color #46483e 172 | text-align center 173 | -------------------------------------------------------------------------------- /spec/issuesSpec.coffee: -------------------------------------------------------------------------------- 1 | SequenceDiagramBuilder = require "SequenceDiagramBuilder.coffee" 2 | SequenceDiagramLayout = require "SequenceDiagramLayout.coffee" 3 | utils = require "./jasmine-utils.coffee" 4 | 5 | describe "issues", -> 6 | 7 | div = utils.div this 8 | 9 | describe "#15", -> 10 | 11 | it "is empty for the .condition of fragment", -> 12 | code = ''' 13 | @found "a", -> 14 | @loop "[until i > 100]", -> 15 | @message "touch", "@create", -> 16 | @fragment "critial section": -> 17 | @message "select", "Context" 18 | ''' 19 | diag = (new SequenceDiagramBuilder).build code 20 | 21 | conds = diag.find(".condition") 22 | expect(conds.length).toBe 2 23 | expect(conds.filter(":eq(0)").text()).toBe "[until i > 100]" 24 | expect(conds.filter(":eq(1)").text()).toBe "" 25 | 26 | describe "#12", -> 27 | 28 | describe "@create", -> 29 | beforeEach -> 30 | diag = (new SequenceDiagramBuilder).build ''' 31 | @found "You", -> 32 | @create "Diagram", -> 33 | @reply "400" 34 | ''' 35 | div.append diag 36 | (new SequenceDiagramLayout).layout diag 37 | 38 | occur = diag.find ".occurrence:eq(1)" 39 | @rmsg = diag.find ".message.return" 40 | @bottom = occur.offset().top + occur.outerHeight() - 1 41 | @top = @rmsg.offset().top 42 | 43 | it "top < bottom of occurrence", -> 44 | expect(@top).toBeLessThan @bottom 45 | 46 | it "bototm < bottom of occurrence", -> 47 | expect(@top + @rmsg.outerHeight() - 1).toBeGreaterThan @bottom 48 | 49 | describe "@message", -> 50 | beforeEach -> 51 | diag = (new SequenceDiagramBuilder).build ''' 52 | @found "You", -> 53 | @message "get", "Diagram", -> 54 | @reply "200" 55 | ''' 56 | div.append diag 57 | (new SequenceDiagramLayout).layout diag 58 | 59 | occur = diag.find ".occurrence:eq(1)" 60 | @rmsg = diag.find ".message.return" 61 | @bottom = occur.offset().top + occur.outerHeight() - 1 62 | @top = @rmsg.offset().top 63 | 64 | it "top < bottom of occurrence", -> 65 | expect(@top).toBeLessThan @bottom 66 | 67 | it "bototm < bottom of occurrence", -> 68 | expect(@top + @rmsg.outerHeight() - 1).toBeGreaterThan @bottom 69 | 70 | describe "#6", -> 71 | describe "@found 'get'", -> 72 | it "can be built without exception", -> 73 | f = -> (new SequenceDiagramBuilder).build '''@found "get", ->''' 74 | expect(f).toThrow new Error("Reserved word 'get'") 75 | 76 | describe "issue#38", -> 77 | 78 | beforeEach -> 79 | @layout = new SequenceDiagramLayout 80 | @builder = new SequenceDiagramBuilder 81 | @diagram = @builder.diagram() 82 | 83 | describe "normal nested interaction", -> 84 | 85 | beforeEach -> 86 | @diagram = diag = @builder.build """ 87 | @found "A", -> 88 | @message "1", "B",-> 89 | @message "2", "A", -> 90 | @reply 3 91 | """ 92 | div.append diag 93 | @layout.layout diag 94 | 95 | @interBA = diag.find ".interaction:eq(1)" 96 | @interBA.find("> .occurrence").css("background-color", "red") 97 | @interAB = diag.find ".interaction:eq(2)" 98 | @interAB.find("> .occurrence").css("background-color", "blue") 99 | 100 | it "contains reply message in nested interaction", -> 101 | #expect(@interBA.length).toBe 1 102 | #expect(@interAB.length).toBe 1 103 | expect(@interAB.find("> .message.return").length).toBe 1 104 | 105 | describe "self interaction", -> 106 | 107 | beforeEach -> 108 | @diagram = diag = @builder.build """ 109 | @found "A", -> 110 | @message "1", "B",-> 111 | @message "2", "B", -> 112 | @reply 3 113 | """ 114 | div.append diag 115 | @layout.layout diag 116 | 117 | @interBB = diag.find ".interaction:eq(1)" 118 | @interBB.find("> .occurrence").css("background-color", "red") 119 | @interSelf = diag.find ".interaction:eq(2)" 120 | @interSelf.find("> .occurrence").css("background-color", "blue") 121 | 122 | it "contains reply message in nested interaction", -> 123 | #expect(@interBB.length ).toBe 1 124 | #expect(@interSelf.length).toBe 1 125 | expect(@interBB.find("> .message.return").length ).toBe 1 126 | expect(@interSelf.find("> .message.return").length).toBe 0 127 | 128 | -------------------------------------------------------------------------------- /lib/css/sequence.styl: -------------------------------------------------------------------------------- 1 | _box-shadow(a, h, v, c) 2 | -webkit-box-shadow a h v rgba(0,0,0,c) 3 | -moz-box-shadow a h v rgba(0,0,0,c) 4 | -o-box-shadow a h v rgba(0,0,0,c) 5 | box-shadow a h v rgba(0,0,0,c) 6 | 7 | box-shadow() 8 | _box-shadow(8px, 5px, 10px, .22) 9 | 10 | border_width = 2px 11 | obj_inner_width = 88px 12 | occur_width = 12px 13 | 14 | primary-border() 15 | border border_width solid #808080 16 | 17 | .sequence-diagram 18 | font-family Tahoma,Verdana 19 | 20 | .participant 21 | display inline-block 22 | background-color #fff 23 | primary-border() 24 | min-height 31px 25 | .name 26 | display inline-block 27 | margin 8px 0 // looks vertical centering 28 | padding 0px 4px 29 | min-width obj_inner_width 30 | max-width obj_inner_width*2 31 | text-align center 32 | word-break normal // normal, keep-all, loose, break-strict, break-all 33 | 34 | .lifeline, 35 | .lifeline .line, 36 | .occurrence, 37 | .interaction, 38 | .ref, 39 | .stop 40 | position relative 41 | 42 | .message, 43 | .message svg 44 | position absolute 45 | 46 | .message svg.arrow 47 | line 48 | stroke gray 49 | stroke-width 1.5 50 | polyline 51 | stroke gray 52 | fill gray 53 | stroke-width 1.5 54 | stroke-linejoin round 55 | 56 | .message.create svg.arrow, 57 | .message.asynchronous svg.arrow, 58 | .message.return svg.arrow 59 | polyline 60 | fill none 61 | polyline.closed 62 | stroke none 63 | 64 | .message.self svg.arrow 65 | polyline 66 | fill none 67 | polyline.head 68 | fill gray 69 | 70 | .message.create svg.arrow line, 71 | .message.return svg.arrow line 72 | stroke-dasharray 8,8 73 | 74 | .lifeline .line 75 | margin-left 50% 76 | left 1px 77 | height 100% 78 | border-left dashed 1px gray 79 | 80 | .occurrence 81 | width occur_width 82 | padding 1em 0 1em 0 83 | margin-top 4px 84 | primary-border() 85 | background-color lightgray 86 | z-index 1 87 | 88 | .creating 89 | margin-bottom 40px 90 | 91 | .interaction 92 | &.lost .occurrence.icon 93 | height 20px 94 | width 20px 95 | -moz-border-radius 20px 96 | -webkit-border-radius 20px 97 | -o-border-radius 20px 98 | border-radius 20px 99 | padding 0 100 | margin 0 101 | border-color #999 102 | background-color #aaa 103 | 104 | &.activated 105 | margin-top 0px 106 | 107 | &.lost .occurrence.icon::before 108 | content '' 109 | 110 | &:not(:last-child).reply 111 | margin-bottom 1em*2.5 112 | 113 | &.reply > .occurrence 114 | padding-bottom 1.44em*1.44 115 | 116 | &:not(.activated) .occurrence .note:nth-child(1) 117 | margin-top 1px + border_width 118 | 119 | .message 120 | // This value is depended by arrow's shape 121 | height 20px 122 | 123 | .name 124 | margin-top -0.85em 125 | text-align center 126 | 127 | &.create + .occurrence 128 | padding 7px 0 7px 0 129 | 130 | &.lost .icon 131 | background-color gray 132 | border 1px solid #444 133 | 134 | &.name::before 135 | content '«create»' 136 | 137 | &.return 138 | margin-top -0.85em 139 | .name 140 | margin-top -1em*0.6 141 | 142 | &.return .name:empty 143 | position relative 144 | top -8px 145 | 146 | &.return .name::before 147 | display block 148 | //content '«return»' 149 | 150 | &.destroy .name::before 151 | content '«destroy»' 152 | 153 | .creating .message.return 154 | margin-top 12px + 4px - 1px 155 | 156 | 157 | .fragment 158 | margin 4px 4px 8px 4px 159 | padding 4px 160 | border solid 1px #aaa 161 | -moz-border-radius 2px 162 | -webkit-border-radius 2px 163 | -o-border-radius 2px 164 | border-radius 2px 165 | 166 | & > .reply 167 | margin-bottom .5em 168 | 169 | & .header .name 170 | color #000 171 | font-weight 700 172 | 173 | & .header .condition 174 | color #000 175 | font-weight 400 176 | 177 | &.alt>.condition 178 | color #000 179 | font-weight 400 180 | margin-bottom -.5em 181 | 182 | &.alt div.divider 183 | border-bottom 1px dashed #555 184 | margin 4px -4px 4px -4px 185 | 186 | &.alt 187 | .condition + .interaction 188 | .message 189 | margin-top 10px 190 | //background-color #fbb 191 | 192 | .header + .note 193 | margin-top 2px 194 | 195 | .ref 196 | min-width 128px 197 | margin 4px 198 | padding 4px 199 | padding-bottom 1em 200 | background-color #fff 201 | border solid 1px black 202 | 203 | .tag 204 | color #000 205 | font-weight 700 206 | 207 | .name 208 | text-align center 209 | 210 | .self .message + .occurrence 211 | > .ref, .interaction 212 | margin-top 10px 213 | padding-top 6px 214 | padding-bottom 6px 215 | 216 | .self > .message 217 | line-height 12px 218 | .name 219 | min-width obj_inner_width 220 | max-width obj_inner_width * 1.5 221 | text-align left 222 | 223 | .participant, 224 | .occurrence, 225 | .ref 226 | box-shadow() 227 | 228 | .note 229 | margin-top 2px 230 | margin-bottom 1em 231 | margin-left occur_width + border_width*2 232 | min-width 128px 233 | .name 234 | primary-border() 235 | background-color #fff 236 | padding 4px 237 | &:first-child 238 | margin-top -1em 239 | -------------------------------------------------------------------------------- /lib/js/SequenceMessage.coffee: -------------------------------------------------------------------------------- 1 | core = require "core.coffee" 2 | pos = require "position.coffee" 3 | g2d = require "g2d.coffee" 4 | HTMLElement = require "HTMLElement.coffee" 5 | 6 | class SequenceMessage extends HTMLElement 7 | constructor: (@_iact, @_actee)-> 8 | super null, (me)-> 9 | me.append($("")) 10 | .append($("
      ").addClass "name") 11 | 12 | SequenceMessage::_lineToNextOccurr = (svg) -> 13 | if false #@hasClass("destroy")) { 14 | ##FIXME: Destroy message 15 | console.log "FIXME: to avoid runtime error." 16 | {src:{x:0, y:0}, dst:{x:400, y:0}} 17 | srcll = @_srcOccurr() 18 | dstll = @_dstOccurr() 19 | @_toLine srcll, dstll, svg 20 | 21 | SequenceMessage::_toLine = (src, dst, svg) -> 22 | # Lost message is always toward right. 23 | e = if !@parent().hasClass("lost") and @isTowardLeft() 24 | src: x: src.offset().left - @offset().left 25 | dst: x: dst.outerWidth() 26 | else 27 | src: x: src.outerWidth() 28 | dst: x: dst.offset().left - src.offset().left 29 | y = svg.outerHeight()/2 30 | e.src.y = y 31 | e.dst.y = y 32 | e 33 | 34 | SequenceMessage::_srcOccurr = -> core.self @parents(".occurrence:eq(0)") 35 | 36 | SequenceMessage::_dstOccurr = -> core.self (if @hasClass "return" then @prev ".occurrence" else $ "~ .occurrence", this) 37 | 38 | SequenceMessage::_prefferedCanvas = -> 39 | @find("svg:eq(0)") 40 | .attr(width:@width(), height:@height()) 41 | 42 | SequenceMessage::_toCreateLine = (svg)-> 43 | e = @_toLine @_srcOccurr(), @_dstOccurr()._actor, svg 44 | if @isTowardLeft() 45 | src = @_srcOccurr() 46 | outerRight = (it)-> it.offset().left + it.outerWidth() 47 | e.dst.x = outerRight(src._actor) - src.offset().left 48 | e 49 | 50 | SequenceMessage::_findOccurr = (actee)-> 51 | occurr = null 52 | @parents(".occurrence").each (i, e)=> 53 | e = $(e).data "_self" 54 | if e._actor is actee 55 | occurr = e 56 | occurr 57 | 58 | MESSAGE_STYLE = 59 | width : 1 60 | base : 6 61 | height : 10 62 | lineWidth : 1.5 63 | shape : "line2" 64 | pattern : [8, 8] 65 | strokeStyle: 'gray' 66 | fillStyle : 'gray' 67 | 68 | STEREOTYPE_STYLES = # From streotype to style object 69 | create : {shape: "dashed"} 70 | asynchronous: {shape: "line"} 71 | synchronous : {shape: "line2", fillStyle: 'gray'} 72 | destroy : {shape: "line2", fillStyle: 'gray'} 73 | 74 | _determine_primary_stereotype = (jqnode) -> 75 | for e in ["create", "asynchronous", "synchronous", "destroy"] 76 | return e if jqnode.hasClass e 77 | 78 | to_points = (vals)-> vals.map((e)-> "#{e[0]},#{e[1]}").join " " 79 | 80 | ahead = (svg, sign, q)-> 81 | dx = sign * 10 82 | dy = 6 83 | e = g2d.svg.new 'polyline', class:"head", points:to_points [[q.x+dx,q.y-dy], [q.x,q.y], [q.x+dx,q.y+dy]] 84 | svg.appendChild e 85 | 86 | e = g2d.svg.new 'polyline', class:"closed", points:to_points [[q.x+dx,q.y+(dy+1)], [q.x+dx,q.y-(dy+1)]] 87 | svg.appendChild e 88 | 89 | SequenceMessage::repaint = () -> 90 | shape = STEREOTYPE_STYLES[_determine_primary_stereotype this] 91 | arrow = jQuery.extend {}, MESSAGE_STYLE, shape 92 | svg = @_prefferedCanvas() 93 | 94 | if false 95 | p = @parents(".occurrence:eq(0)") 96 | arrow.fillStyle = p.css "background-color" 97 | arrow.strokeStyle = p.css "border-top-color" 98 | (p.css "box-shadow").match /(rgba\(.*\)) ([0-9]+)px ([0-9]+)px ([0-9]+)px ([0-9]+)px/ 99 | arrow.shadowColor = RegExp.$1 100 | arrow.shadowOffsetX = RegExp.$2 101 | arrow.shadowOffsetY = RegExp.$3 102 | arrow.shadowBlur = RegExp.$4 103 | 104 | if @hasClass "self" 105 | gap = 2 106 | rcx = @width() - (gap + 4) 107 | rey = @height() - (arrow.height/2 + 4) 108 | llw = @_dstOccurr().outerWidth() 109 | e = g2d.svg.new 'polyline', points:to_points [[llw/2 + gap, gap], [rcx, gap], [rcx, rey], [llw + gap, rey]] 110 | svg[0].appendChild e 111 | 112 | ahead svg[0], 1, x:llw + gap, y:rey 113 | return this 114 | 115 | if @hasClass "create" 116 | line = @_toCreateLine svg 117 | else if @_actee 118 | newsrc = @_findOccurr @_actee 119 | newdst = @_dstOccurr() 120 | line = @_toLine newsrc, newdst, svg 121 | else 122 | line = @_lineToNextOccurr svg 123 | 124 | if @hasClass "reverse" 125 | a = line.src 126 | line.src = line.dst 127 | line.dst = a 128 | arrow.shape = 'dashed' 129 | 130 | arrow = (svg, p, q)-> 131 | e = g2d.svg.new 'line', x1:p.x, y1:p.y, x2:q.x, y2:q.y 132 | svg[0].appendChild e 133 | ahead svg[0], -1*(Math.sign q.x - p.x), q 134 | 135 | arrow svg, line.src, line.dst, arrow 136 | this 137 | 138 | SequenceMessage::isToward = (dir) -> 139 | actor = @_iact._actor._actor 140 | actee = @_iact._actee._actor 141 | if "right" is dir 142 | actor.isLeftAt(actee) 143 | else if "left" is dir 144 | actor.isRightAt(actee) 145 | 146 | SequenceMessage::isTowardRight = -> 147 | @isToward "right" 148 | 149 | SequenceMessage::isTowardLeft = -> 150 | @isToward "left" 151 | 152 | SequenceMessage::_to_be_creation = -> 153 | src = @_srcOccurr() 154 | dst = @_dstOccurr() 155 | 156 | line_width = (msg) -> 157 | l = msg._toLine src, dst._actor, msg 158 | Math.abs l.src.x - l.dst.x 159 | 160 | shift_downward = (msg) -> 161 | obj = dst._actor 162 | obj.offset top:msg.offset().top - obj.height()/3 163 | mt = parseInt dst.css "margin-top" 164 | dst.offset top:pos.outerBottom(obj) + mt 165 | 166 | @outerWidth (line_width this) + src.outerWidth() - 1 167 | shift_downward this 168 | 169 | module.exports = SequenceMessage 170 | -------------------------------------------------------------------------------- /lib/js/UsecaseDiagram.coffee: -------------------------------------------------------------------------------- 1 | core = require "core.coffee" 2 | Diagram = require "Diagram.coffee" 3 | DiagramBuilder = require "DiagramBuilder.coffee" 4 | 5 | class UMLUsecase 6 | constructor: (props, opts) -> 7 | jQuery.extend this, UMLUsecase.newNode() 8 | this 9 | @newNode = -> 10 | $("
      ").addClass("usecase") 11 | .append($("
      ").addClass("icon") 12 | .append($("
      ").addClass("name"))) 13 | 14 | UMLUsecase::pack = (T = (1 + 2.2360679)/2) -> 15 | icon = @find(".icon") 16 | name = @find(".icon .name") 17 | minwidth = icon.css("min-width").toInt() 18 | minheight = icon.css("min-height").toInt() 19 | R = minwidth/minheight 20 | 21 | h = icon.height() 22 | v = name.height() 23 | 24 | t = h/v 25 | t = T if t > T 26 | icon.css width:minwidth*t, height:minheight*t 27 | 28 | class UMLActor 29 | constructor: (props, opts) -> 30 | jQuery.extend this, $.jumly ".participant" 31 | @iconify ".actor" 32 | @addClass "actor" 33 | this 34 | 35 | class UMLSystemBoundary 36 | constructor: (props, opts) -> 37 | jQuery.extend this, UMLSystemBoundary.newNode() 38 | this 39 | @newNode = -> 40 | $("
      ").addClass("system-boundary") 41 | .append $("
      ").addClass "name" 42 | 43 | class JUMLYUsecaseDiagram extends Diagram 44 | 45 | set_min_size = (nodes) -> 46 | nodes.each (i, e) -> 47 | e = $(e) 48 | if w = e.css "min-width" then e.width(w) .css width :w 49 | if h = e.css "min-height" then e.height(h).css height:h 50 | 51 | shift_usecase_down_to_above = (nodes) -> 52 | nodes.each (i, e) -> 53 | $(e).find("> .usecase .icon").each (i, e) -> 54 | e = $(e) 55 | if i > 0 56 | e.css "margin-top", -e.css("min-height").toInt()/3 57 | 58 | bind_between = (nodes, diag) -> 59 | nodes.each (i, e) -> 60 | src = $(e).self() 61 | find_with_id = (id) -> 62 | return diag[id] if diag[id] 63 | return id unless (typeof id is "string") or (typeof id is "number") ## Regard as the own object 64 | if (t = $("#" + id, diag)).length > 0 65 | t 66 | bind = (type) -> 67 | $(src.jprops()[type]).each (i, e) -> 68 | return unless dst = find_with_id e 69 | link = $.jumly ".relationship", src:src, dst:dst 70 | link.addClass type 71 | diag.append link 72 | bind "use" 73 | bind "extend" 74 | bind "include" 75 | 76 | JUMLYUsecaseDiagram::align_actors_ = -> 77 | tb = @find(".system-boundary").mostTopBottom() 78 | height = tb.height() 79 | actors = @find(".actor") 80 | dh = height / actors.length 81 | actors.each (i, e) -> 82 | dy = if i > 1 then (if i % 2 is 0 then dh else -dh) else 0 83 | y = tb.top + dy + height/2 84 | $(e).offset top:y 85 | 86 | JUMLYUsecaseDiagram::render = -> 87 | @find(".relationship").each (i, e) -> 88 | $(e).self().render() 89 | 90 | JUMLYUsecaseDiagram::compose = -> 91 | set_min_size @find(".usecase .icon") 92 | shift_usecase_down_to_above @find(".system-boundary") 93 | bind_between @find(".usecase, .actor"), this 94 | @align_actors_() 95 | @render() 96 | #@width @mostLeftRight().width() 97 | @height @mostTopBottom().height() 98 | this 99 | 100 | #JUMLY.def ".usecase-diagram", JUMLYUsecaseDiagram 101 | 102 | 103 | 104 | class JUMLYUsecaseDiagramBuilder extends DiagramBuilder 105 | constructor: (@_diagram, @_boundary) -> 106 | 107 | JUMLYUsecaseDiagramBuilder::new_ = (type, uname) -> 108 | uname = core._normalize uname 109 | a = $.jumly type, uname 110 | $.extend a.jprops(), uname 111 | a 112 | 113 | JUMLYUsecaseDiagramBuilder::_declare_ = (uname, type, target)-> 114 | a = @new_ type, uname 115 | target.append a 116 | b = core._normalize uname 117 | 118 | ref = @_diagram._regByRef_ b.id, a 119 | eval "#{ref} = a" 120 | 121 | JUMLYUsecaseDiagramBuilder::usecase = (uname) -> 122 | @_declare_ uname, ".usecase", @_boundary 123 | 124 | JUMLYUsecaseDiagramBuilder::actor = (uname) -> 125 | @_declare_ uname, ".actor", @_diagram 126 | 127 | JUMLYUsecaseDiagramBuilder::boundary = (name, acts) -> 128 | @_diagram = @diagram unless @_diagram ##WORKAROUND: to v0.1.0 129 | name ?= "" 130 | return curry_ this, @boundary, id if id = $.jumly.identify name 131 | boundary = @new_ ".system-boundary", name 132 | 133 | ctxt = new JUMLYUsecaseDiagramBuilder(@_diagram, boundary) 134 | ctxt.diagram = @_diagram ##WORKAROUND: to v0.1.0 135 | acts.apply ctxt 136 | if @_boundary 137 | @_boundary.append boundary 138 | else 139 | @_diagram.append boundary 140 | norm = core._normalize name 141 | @_diagram._regByRef_ norm.id 142 | this 143 | 144 | JUMLYUsecaseDiagramBuilder::compose = (something) -> 145 | if typeof something is "function" 146 | something @_diagram 147 | else if typeof something is "object" and something.each 148 | something.append @_diagram 149 | else 150 | throw something + " MUST be a function or a jQuery object" 151 | @_diagram.compose() 152 | this 153 | 154 | JUMLYUsecaseDiagram::boundary = (name, acts)-> 155 | ctxt = new JUMLYUsecaseDiagramBuilder(this) 156 | ctxt.boundary name, acts 157 | ctxt 158 | 159 | #JUMLY.DSL type:".usecase-diagram", compileScript: (script) -> 160 | # diag = $.jumly ".usecase-diagram" 161 | # sbname = $(script).attr "system-boundary-name" 162 | # diag.boundary sbname, -> 163 | # unless sbname 164 | # @_boundary.addClass("out-of-bounds").removeClass("system-boundary") 165 | # .find(".name").remove() 166 | # eval CoffeeScript.compile script.html() 167 | # diag 168 | 169 | 170 | #UsecaseDiagramBuilder = JUMLYUsecaseDiagramBuilder 171 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/scripts/shBrushCss.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | ;(function() 18 | { 19 | // CommonJS 20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; 21 | 22 | function Brush() 23 | { 24 | function getKeywordsCSS(str) 25 | { 26 | return '\\b([a-z_]|)' + str.replace(/ /g, '(?=:)\\b|\\b([a-z_\\*]|\\*|)') + '(?=:)\\b'; 27 | }; 28 | 29 | function getValuesCSS(str) 30 | { 31 | return '\\b' + str.replace(/ /g, '(?!-)(?!:)\\b|\\b()') + '\:\\b'; 32 | }; 33 | 34 | var keywords = 'ascent azimuth background-attachment background-color background-image background-position ' + 35 | 'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top ' + 36 | 'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color ' + 37 | 'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width ' + 38 | 'border-bottom-width border-left-width border-width border bottom cap-height caption-side centerline clear clip color ' + 39 | 'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display ' + 40 | 'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font ' + 41 | 'height left letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top ' + 42 | 'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans ' + 43 | 'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page ' + 44 | 'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position ' + 45 | 'quotes right richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress ' + 46 | 'table-layout text-align top text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em ' + 47 | 'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index'; 48 | 49 | var values = 'above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+ 50 | 'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+ 51 | 'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero default digits disc dotted double '+ 52 | 'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+ 53 | 'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+ 54 | 'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+ 55 | 'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+ 56 | 'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+ 57 | 'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+ 58 | 'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+ 59 | 'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+ 60 | 'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+ 61 | 'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+ 62 | 'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow'; 63 | 64 | var fonts = '[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif [cC]ourier mono sans serif'; 65 | 66 | this.regexList = [ 67 | { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments 68 | { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // double quoted strings 69 | { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // single quoted strings 70 | { regex: /\#[a-fA-F0-9]{3,6}/g, css: 'value' }, // html colors 71 | { regex: /(-?\d+)(\.\d+)?(px|em|pt|\:|\%|)/g, css: 'value' }, // sizes 72 | { regex: /!important/g, css: 'color3' }, // !important 73 | { regex: new RegExp(getKeywordsCSS(keywords), 'gm'), css: 'keyword' }, // keywords 74 | { regex: new RegExp(getValuesCSS(values), 'g'), css: 'value' }, // values 75 | { regex: new RegExp(this.getKeywords(fonts), 'g'), css: 'color1' } // fonts 76 | ]; 77 | 78 | this.forHtmlScript({ 79 | left: /(<|<)\s*style.*?(>|>)/gi, 80 | right: /(<|<)\/\s*style\s*(>|>)/gi 81 | }); 82 | }; 83 | 84 | Brush.prototype = new SyntaxHighlighter.Highlighter(); 85 | Brush.aliases = ['css']; 86 | 87 | SyntaxHighlighter.brushes.CSS = Brush; 88 | 89 | // CommonJS 90 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null; 91 | })(); 92 | -------------------------------------------------------------------------------- /spec/apiSpec.coffee: -------------------------------------------------------------------------------- 1 | core = require "core.coffee" 2 | JUMLY = require "api.coffee" 3 | $ = require "jquery" 4 | 5 | describe "JUMLY", -> 6 | 7 | describe "eval", -> 8 | beforeEach -> 9 | @node = $ '''
      @found "that"
      ''' 10 | @here = $ """
      """ 11 | 12 | describe "a jQuery node is given", -> 13 | describe "with `into`", -> 14 | it "makes a new diagram and put it", -> 15 | a = JUMLY.eval @node, into:@here 16 | b = @here.find ">.diagram" 17 | expect(b.length).toBe 1 18 | expect(a[0]).toBe b[0] 19 | 20 | describe "with a funciton `placer`", -> 21 | it "makes a new diagram and put it", -> 22 | a = JUMLY.eval @node, (d, $e)=> @here.find(">span").append d 23 | b = @here.find ">span>.diagram" 24 | expect(b.length).toBe 1 25 | expect(a[0]).toBe b[0] 26 | 27 | describe "text/jumly+robustness", -> 28 | describe "in type of script", -> 29 | it "makes a new robustness diagram", -> 30 | node = $ '''''' 31 | a = JUMLY.eval node, into:@here 32 | expect(a.hasClass "robustness-diagram").toBeTruthy() 33 | expect(@here.find(">.diagram").length).toBe 1 34 | 35 | describe "in data-jumly", -> 36 | describe "as string", -> 37 | it "makes a new robustness diagram", -> 38 | node = $ '''
      @view "Browser"
      ''' 39 | a = JUMLY.eval node, into:@here 40 | expect(a.hasClass "robustness-diagram").toBeTruthy() 41 | 42 | describe "as object", -> 43 | it "makes a new robustness diagram", -> 44 | node = $ '''
      @view "Browser"
      ''' 45 | a = JUMLY.eval node, into:@here 46 | expect(a.hasClass "robustness-diagram").toBeTruthy() 47 | 48 | 49 | describe "scan", -> 50 | 51 | beforeEach -> 52 | @finder = (a)->a.find("> *") 53 | 54 | describe "a jQuery nodeset is given", -> 55 | it "makes a new diagram after each node which has data-jumly attr", -> 56 | nodes = $ """ 57 |
      58 |
      @found "that"
      59 |
      60 | """ 61 | JUMLY.scan nodes, finder:@finder 62 | expect(nodes.find("> .diagram").length).toBe 1 63 | 64 | describe "with opiton", -> 65 | 66 | it "has an function placer", -> 67 | nodes = $ """ 68 |
      69 |
      @found "dog"
      70 |
      71 | """ 72 | JUMLY.scan nodes, finder:@finder, placer:(d, $e)-> nodes.html d 73 | expect(nodes.find("> .diagram").length).toBe 1 74 | expect(nodes.find("> *").length).toBe 1 75 | 76 | describe "call twice", -> 77 | 78 | describe "for a single script in scope", -> 79 | 80 | nodes = """ 81 |
      82 | 83 |
      84 | """ 85 | 86 | it "doesn't create more than 1", -> 87 | nodes = $(nodes) 88 | JUMLY.scan nodes 89 | JUMLY.scan nodes 90 | expect(nodes.find("> .diagram").length).toBe 1 91 | 92 | describe "when modifying code", -> 93 | beforeEach -> 94 | @nodes = $(nodes) 95 | JUMLY.scan @nodes, finder:(n)->n.find("> script") 96 | @nodes.find("script").text "@found 'bird'" 97 | 98 | describe "without eval", -> 99 | it "doesn't eval again", -> 100 | JUMLY.scan @nodes # scan again 101 | 102 | expect(@nodes.find("> .diagram").length).toBe 1 103 | expect(@nodes.find("> .diagram .participant").text()).toBe "cat" 104 | 105 | describe "with eval", -> 106 | it "evals", -> 107 | JUMLY.scan @nodes, synchronize:true # scan again with re-eval 108 | 109 | expect(@nodes.find("> .diagram").length).toBe 1 110 | expect(@nodes.find("> .diagram .participant").text()).toBe "bird" 111 | 112 | 113 | describe "for a single div in scope", -> 114 | 115 | nodes = """ 116 |
      117 |
      @found "cat" 118 |
      119 | """ 120 | 121 | it "doesn't create more than 1", -> 122 | nodes = $(nodes) 123 | JUMLY.scan nodes 124 | JUMLY.scan nodes 125 | expect(nodes.find("> .diagram").length).toBe 1 126 | 127 | describe "for multi-nodes in scope", -> 128 | 129 | nodes = """ 130 |
      131 | 132 |
      @found "cat"
      133 |
      134 | """ 135 | 136 | it "doesn't create more than 1", -> 137 | nodes = $(nodes) 138 | JUMLY.scan nodes 139 | JUMLY.scan nodes 140 | expect(nodes.find("> .diagram").length).toBe 2 141 | 142 | describe "one by one", -> 143 | it "makes diagrams properly", -> 144 | nodes = $ """ 145 |
      146 | 147 |
      148 | """ 149 | JUMLY.scan nodes 150 | nodes.append '''
      @found "cat"
      ''' 151 | JUMLY.scan nodes 152 | expect(nodes.find("> .diagram").length).toBe 2 153 | -------------------------------------------------------------------------------- /lib/js/IconElement.coffee: -------------------------------------------------------------------------------- 1 | core = require "core.coffee" 2 | g2d = require "g2d.coffee" 3 | HTMLElement = require "HTMLElement.coffee" 4 | 5 | _STYLES = 6 | lineWidth : 1.5 7 | fillStyle : 'white' 8 | strokeStyle : 'gray' 9 | shadowBlur : 12 10 | shadowColor : 'rgba(0,0,0,0.22)' # 'transparent black' 11 | shadowOffsetX: 8 12 | shadowOffsetY: 5 13 | 14 | ns = "http://www.w3.org/2000/svg" 15 | 16 | # http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Filters 17 | """ 18 | 19 | 25 | [3] 27 | 28 | [4] 29 | 30 | 31 | 32 | 33 | """ 34 | 35 | ce = g2d.svg.create 36 | sa = g2d.svg.attrs 37 | ne = (n, attrs)-> sa ce(n), attrs 38 | 39 | red = green = blue = 0.22*3 40 | 41 | drop_shadow = -> 42 | shadow2 = ne "filter", id:"dropshadow", width:"200%", height:"200%" 43 | matrix = ne "feColorMatrix", type:"matrix", values:"0 0 0 #{red} 0 0 0 0 #{green} 0 0 0 0 #{blue} 0 0 0 0 1 0" 44 | blur = ne "feGaussianBlur", stdDeviation:2.5, result:"coloreBlur" 45 | offset = ne "feOffset", dx:8, dy:5, result:"coloreBlur" 46 | merge = ne "feMerge" 47 | mnBlur = ne "feMergeNode", in:"coloreBlur" 48 | mnSrc = ne "feMergeNode", in:"SourceGraphic" 49 | 50 | merge.appendChild mnBlur 51 | merge.appendChild mnSrc 52 | 53 | shadow2.appendChild matrix 54 | shadow2.appendChild blur 55 | shadow2.appendChild offset 56 | shadow2.appendChild merge 57 | shadow2 58 | 59 | to_d = (d)-> (d.map (e)-> "#{e[0]}#{e[1]},#{e[2]}").join "" 60 | 61 | svg_g = (svg)-> 62 | svg.appendChild drop_shadow() 63 | g = sa (ce 'g'), style:"filter:url(#dropshadow)" 64 | svg.appendChild g 65 | g 66 | 67 | _actor = (svg, styles) -> 68 | r = styles.radius || 12 69 | r2 = r*2 70 | exth = r*0.25 # 25% of radius 71 | lw = Math.round(styles.lineWidth) # lw: line-width 72 | 73 | g = svg_g svg 74 | 75 | # Render a head 76 | g.appendChild ne 'circle', cx:lw + r, cy:lw + r, r:r 77 | 78 | # Render a body 79 | dh = 3*lw 80 | dv = r2*0.77 81 | d = [ 82 | ["M", 0, r2 + lw + exth] 83 | ["l", lw + r2 + lw, 0] # actor's arms (h-line) 84 | ["M", lw + r, r2 + lw] 85 | ["l", 0, r2*0.35] # actor's body (v-line) 86 | ["l", -r, dv] 87 | ["m", r, -dv] # actor's right leg, and back to the groin :) 88 | ["l", r, dv] # actor's left leg 89 | ] 90 | e = ne 'path', d:to_d d 91 | g.appendChild e 92 | 93 | ret = 94 | size: 95 | width : lw + r2 + lw 96 | height: lw + r2*2 + lw 97 | 98 | _view = (svg, styles) -> 99 | r = styles.radius || 16 100 | r2 = r*2 101 | extw = r*0.4 # 40% of r 102 | lw = styles.lineWidth # lw: line-width 103 | 104 | g = svg_g svg 105 | 106 | g.appendChild ne 'circle', cx:lw + r + extw, cy:lw + r, r:r 107 | 108 | d = [ 109 | ["M", lw, r] 110 | ["l", extw, 0] 111 | ["M", lw, 0] 112 | ["l", 0, r2] 113 | ] 114 | e = ce 'path' 115 | e.setAttribute "d", to_d d 116 | g.appendChild e 117 | 118 | ret = 119 | size: 120 | width :lw + r2 + extw + lw 121 | height:lw + r2 + lw 122 | 123 | _controller = (svg, styles) -> 124 | r = styles.radius || 16 125 | r2 = r*2 126 | exth = r*0.4 # 40% of r 127 | lw = lh = styles.lineWidth # lw: line-width 128 | dy = 0 129 | effectext = 0 130 | 131 | g = svg_g svg 132 | 133 | g.appendChild ne 'circle', cx:lw + r, cy:lw + r + exth, r:r 134 | 135 | x0 = lw + r*0.8 136 | x1 = lw + r*1.2 137 | y0 = lh + exth 138 | d = [ 139 | ["M", x0, y0] 140 | ["L", x1, lh + exth/4] 141 | ["M", x0, y0] 142 | ["L", x1, lh + exth*7/4] 143 | ] 144 | e = ne 'path', d:to_d d 145 | g.appendChild e 146 | 147 | ret = 148 | size: 149 | width :lw + r2 + lw + effectext 150 | height:lw + r2 + lw + effectext + exth 151 | 152 | _entity = (svg, styles) -> 153 | r = styles.radius || 16 154 | r2 = r*2 155 | exth = r*0.4 # 40% of r 156 | lw = styles.lineWidth # lw: line-width 157 | 158 | g = svg_g svg 159 | 160 | g.appendChild ne 'circle', cx:lw + r, cy:lw + r, r:r 161 | 162 | d = [ 163 | ["M", lw + r, r2] # v-line (short) 164 | ["L", lw + r, r2 + exth] 165 | ["M", 0, r2 + exth] # h-line (long) 166 | ["L", r2 + lw, r2 + exth] # 167 | ] 168 | e = ne 'path', d:to_d d 169 | g.appendChild e 170 | 171 | ret = 172 | size: 173 | width :lw + r2 + lw 174 | height:lw + r2 + exth + lw 175 | 176 | _render = (svg, renderer, args) -> 177 | #return unless svg.getContext ## for test on CLI 178 | 179 | styles = $.extend {}, _STYLES, args 180 | 181 | {size, paths} = renderer svg, styles 182 | 183 | dw = (styles.shadowOffsetX || 0) + (styles.shadowBlur/2 || 0) 184 | dh = (styles.shadowOffsetY || 0) + (styles.shadowBlur/2 || 0) 185 | $(svg).attr 186 | width:size.width + dw, height:size.height + dh 187 | "data-actual-width":size.width, "data-actual-height":size.height 188 | 189 | class IconElement extends HTMLElement 190 | @renderer = (type)-> 191 | r = 192 | actor: _actor 193 | view: _view 194 | controller: _controller 195 | entity: _entity 196 | (svg, styles) -> _render svg, r[type], styles 197 | 198 | constructor: (args, opts)-> 199 | idname = core._normalize args 200 | super args, (me)-> 201 | svg = $("") 202 | me.addClass("icon").addClass(opts.kind) 203 | .append(div = $("
      ").append svg) 204 | .append $("
      ").addClass("name").append idname.name 205 | (IconElement.renderer opts.kind) svg[0] 206 | div.css height:svg.data("actual-height") 207 | 208 | module.exports = IconElement 209 | -------------------------------------------------------------------------------- /lib/js/SequenceDiagramLayout.coffee: -------------------------------------------------------------------------------- 1 | core = require "core.coffee" 2 | pos = require "position.coffee" 3 | DiagramLayout = require "DiagramLayout.coffee" 4 | SequenceLifeline = require "SequenceLifeline.coffee" 5 | 6 | class SequenceDiagramLayout extends DiagramLayout 7 | 8 | selfEach = ($e, f)-> $e.each (i, e)-> 9 | e = core.self $(e) 10 | throw new Error("_self have nothing ", e) unless e? 11 | f e 12 | this 13 | 14 | SequenceDiagramLayout::_q = (sel)-> 15 | $ sel, @diagram 16 | 17 | SequenceDiagramLayout::_layout = -> 18 | objs = $(".participant:eq(0) ~ .participant", @diagram) 19 | objs.last().css "margin-right": 0 20 | 21 | $(".participant:eq(0)", @diagram).after objs 22 | @_q(".occurrence").each (i, e)-> $(e).data("_self")._move_horizontally() 23 | selfEach @_q(".occurrence .interaction"), (e)-> e._compose_() 24 | @generate_lifelines_and_align_horizontally() 25 | @pack_refs_horizontally() 26 | @pack_fragments_horizontally() 27 | selfEach @_q(".create.message"), (e)-> e._to_be_creation() 28 | @align_lifelines_vertically() 29 | @align_lifelines_stop_horizontally() 30 | @rebuild_asynchronous_self_calling() 31 | @render_icons() 32 | 33 | occurs = @diagram.find ".occurrence" 34 | ml = occurs.sort (e)-> $(e).offset().left 35 | mr = occurs.sort (e)-> $(e).offset().left + $(e).outerWidth() - 1 36 | $(ml[0]).addClass "leftmost" 37 | $(mr[mr.length - 1]).addClass "rightmost" 38 | 39 | objs = @diagram.find(".participant, .ref, .note, .loop, .fragment, .self > .name") 40 | l = pos.min objs, (e)-> $(e).offset().left 41 | r = pos.max objs, (e)-> 42 | e = $(e) 43 | a = e.css("box-shadow").match /[0-9]+px/g 44 | hblur = if a then parseInt(a[2]) else 0 45 | e.offset().left + e.outerWidth() - 1 + hblur 46 | @diagram.width r - l + 1 47 | 48 | SequenceDiagramLayout::generate_lifelines_and_align_horizontally = -> 49 | diag = @diagram 50 | $(".participant", @diagram).each (i, e)-> 51 | obj = $(e).data "_self" 52 | a = new SequenceLifeline obj 53 | a.offset left:obj.offset().left 54 | a.width obj.preferred_width() 55 | diag.append a 56 | 57 | SequenceDiagramLayout::pack_refs_horizontally = -> 58 | selfEach @_q(".ref"), (ref) -> 59 | pw = ref.preferred_left_and_width() 60 | ref.offset(left:pw.left) 61 | 62 | ## workaround for checking width of css is defined 63 | idx = ref.index() 64 | parent = ref.parent() 65 | ref.detach() 66 | not_defined = ref.css("width") is "0px" 67 | if idx is 0 68 | parent.prepend ref 69 | else 70 | ref.insertAfter parent.find("> *:eq(#{idx-1})") 71 | 72 | if not_defined 73 | ref.width pw.width 74 | else 75 | ref.width parseInt ref.css "width" 76 | 77 | SequenceDiagramLayout::pack_fragments_horizontally = -> 78 | # fragments just under this diagram. 79 | fragments = $ "> .fragment", @diagram 80 | if fragments.length > 0 81 | # To controll the width, you can write selector below. 82 | # ".participant:eq(0), > .interaction > .occurrence .interaction" 83 | most = pos.mostLeftRight @_q(".participant") 84 | left = fragments.offset().left 85 | fragments.width (most.right - left) + (most.left - left) 86 | 87 | # fragments inside diagram 88 | fixwidth = (fragment) -> 89 | most = pos.mostLeftRight $(".occurrence, .message, .fragment", fragment).not(".return, .lost") 90 | fragment.width(most.width() - (fragment.outerWidth() - fragment.width())) 91 | ## WORKAROUND: it's tentative for both of next condition and the body 92 | msg = fragment.find("> .interaction > .message").data "_self" 93 | if (msg?.isTowardLeft()) 94 | fragment.offset(left:most.left) 95 | .find("> .interaction > .occurrence") 96 | .each (i, occurr) -> 97 | occurr = $(occurr).data "_self" 98 | occurr._move_horizontally() 99 | .prev().offset left:occurr.offset().left 100 | 101 | a = (selfEach @_q(".occurrence > .fragment"), fixwidth) 102 | .parents(".occurrence > .fragment") 103 | selfEach a, fixwidth 104 | 105 | SequenceDiagramLayout::align_lifelines_vertically = -> 106 | nodes = @diagram.find(".interaction, > .ref") 107 | return if nodes.length is 0 108 | 109 | if nodes.filter(".ref").length > 0 110 | last = nodes.filter(":last") 111 | mh = last.offset().top + last.outerHeight() - nodes.filter(":first").offset().top 112 | else 113 | if (iters = @diagram.find("> .interaction")).length is 1 114 | mh = iters.filter(":eq(0)").height() 115 | else 116 | a = iters.filter(":eq(0)") 117 | b = iters.filter(":last") 118 | mh = (b.offset().top + b.height() - 1) - a.offset().top 119 | 120 | min = pos.min @diagram.find(".participant"), (e)-> $(e).offset().top 121 | 122 | @_q(".lifeline").each (i, e) -> 123 | a = $(e).data "_self" 124 | a.offset left:a._object.offset().left 125 | 126 | ot = Math.ceil a._object.offset().top 127 | dh = ot - min 128 | a.height mh - dh + 16 129 | 130 | mt = a.offset().top - (ot + a._object.outerHeight()) 131 | a.css "margin-top":"-#{mt}px" 132 | 133 | SequenceDiagramLayout::align_lifelines_stop_horizontally = -> 134 | $(".stop", @diagram).each (i, e) -> 135 | e = $(e) 136 | occurr = e.prev(".occurrence") 137 | e.offset left:occurr.offset().left 138 | 139 | SequenceDiagramLayout::rebuild_asynchronous_self_calling = -> 140 | @diagram.find(".message.asynchronous").parents(".interaction:eq(0)").each (i, e) -> 141 | e = core.self $(e) 142 | if not e.isToSelf() 143 | return 144 | iact = e.addClass("activated") 145 | .addClass("asynchronous") 146 | prev = iact.parents(".interaction:eq(0)") 147 | iact.insertAfter prev 148 | 149 | aa = core.self iact.css("padding-bottom", 0).find("> .occurrence") 150 | occurr = aa._move_horizontally() 151 | .css("top", 0) 152 | 153 | msg = core.self iact.find(".message") 154 | msg.css("z-index", -1) 155 | .offset 156 | left: occurr.offset().left 157 | top : pos.outerBottom(prev.find(".occurrence")) - msg.height()/3 158 | 159 | SequenceDiagramLayout::render_icons = -> 160 | selfEach @_q(".participant"), (e)-> e.renderIcon?() 161 | 162 | module.exports = SequenceDiagramLayout 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README [![Build Status](https://travis-ci.org/tmtk75/jumly.png)](https://travis-ci.org/tmtk75/jumly) 2 | 3 | JUMLY is a JavaScript library. 4 | Using JUMLY, you can easily embed UML diagram on your HTML document. 5 | All you need is just two things you use everyday. 6 | 7 | - Text editor you get used to use. 8 | - A modern browser like WebKit-base brwoser and Opera. 9 | (working for Firefox now) 10 | 11 | For more information, see . 12 | The auther's blog is . 13 | 14 | ## Trait and Purpose 15 | Trait I'd like to tell you is that JUMLY doesn't render diagram as image like PNG or SVG, 16 | but render as **combination of DOM with CSS on browser**. 17 | You can easily calibrate any CSS parameters like font-size, 18 | color, padding, margin, etc using CSS at runtime. 19 | 20 | Actually you can also usual image data in PNG and JPEG. 21 | They are generated by a [CLI](https://github.com/tmtk75/jumly/wiki/CLI) 22 | or [REST API](https://github.com/tmtk75/jumly/wiki/REST-API), 23 | so JUMLY is convenient way to embed small diagrams in your HTML documents like README file like below. 24 | 25 | 26 | I aim to develop JUMLY to render small UML diagrams in handy without any heavy UML modeling tools. 27 | A UML diagram is usually more understandable than a tedious text. 28 | It's better that people can casually use UML digrams in every documents. 29 | 30 | 31 | # Getting Started 32 | Copy following code, save as a file, and open the file with your browser. 33 | 34 | ```html 35 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | ``` 47 | 48 | [Here](http://jumly.tmtk.net/public/examples/simple.html) is a minimal sample. 49 | 50 | 51 | # How to build 52 | Requiring [node.js](http://nodejs.org/) v0.10.20 or upper. 53 | 54 | ## node.js installation 55 | [nvm](https://github.com/creationix/nvm) is good to get it. 56 | 57 | $ git clone git://github.com/creationix/nvm.git ~/.nvm 58 | $ . ~/.nvm/nvm.sh 59 | $ nvm install 0.10 60 | 61 | ## Build jumly 62 | 63 | In order to build jumly.js, jumly.css and minified ones, it's shortly steps. 64 | 65 | $ git clone https://github.com/tmtk75/jumly.git 66 | $ cd jumly 67 | $ . .env 68 | $ npm install 69 | $ make build 70 | 71 | `./public/jumly.min.js` is generated. 72 | 73 | 74 | # How to develop 75 | Written in CoffeeScript and stylus. They are in `./lib` directory. 76 | `./lib/js/jumly.coffee` organizes other \*.coffee files in order. 77 | 78 | On a webapp, which is described at [next](#how-to-launch-the-webapp), 79 | you can use them without build. 80 | Editing \*.coffee and \*.styl, reload a page of webapp, and your change will make effect. 81 | 82 | 83 | # How to launch the webapp 84 | You can launch the webapp using [express](http://expressjs.com/). 85 | 86 | $ . .env 87 | $ git submodule update --init 88 | $ ./app.coffee 89 | 90 | Please access to [localhost:3000](http://localhost:3000) thru your browser. 91 | 92 | 93 | # How to run specs 94 | ## With browser 95 | 96 | $ make test 97 | 98 | Compile spec files, and open `./spec/index.html` with your browser. 99 | 100 | ## With Karma 101 | To compile them, 102 | 103 | $ . .env 104 | $ build karam 105 | $ open spec/index.html 106 | 107 | [jasmine](http://pivotal.github.com/jasmine/) is used for writing specs. 108 | 109 | 110 | # License 111 | JUMLY v0.2.2 is under [MIT License](http://opensource.org/licenses/MIT). 112 | 113 | JUMLY v0.2.2, 2010-2015 copyright(c), all rights reserved Tomotaka Sakuma. 114 | 115 | 116 | # History 117 | - 0.2.4, Jul 21, 2015 118 | - Workaround for [#40](https://github.com/tmtk75/jumly/issues/40) 119 | - 0.2.3, Apr 1, 2015 120 | - Fixed [#38](https://github.com/tmtk75/jumly/issues/38) 121 | - 0.2.2, Feb 7, 2015 122 | - Fixed an issue for image width 123 | - 0.2.1, Jan 25, 2015 124 | - Fixed several issues. 125 | [#8](https://github.com/tmtk75/jumly/issues/8) 126 | [#18](https://github.com/tmtk75/jumly/issues/18) 127 | [#19](https://github.com/tmtk75/jumly/issues/19) 128 | [#23](https://github.com/tmtk75/jumly/issues/23) 129 | [#24](https://github.com/tmtk75/jumly/issues/24) 130 | [#31](https://github.com/tmtk75/jumly/issues/31) 131 | [#35](https://github.com/tmtk75/jumly/issues/35) 132 | - 0.2.0, Jan 4, 2015 133 | - Followed CommonJS for module system 134 | - Use Karam for test 135 | - 0.1.5-2, Mar 12, 2014 136 | - Removed z-index from `.sequence-diagram .lifeline .line` for [#28](https://github.com/tmtk75/jumly/issues/28) 137 | - 0.1.5-1, June 23, 2013 138 | - REST API to generate image 139 | - 0.1.4, May 20, 2013 140 | - Replaced canvas with svg 141 | - 0.1.3b, Apr 27, 2013 142 | - API JUMLY.scan (beta) 143 | - 0.1.3a, Mar 29, 2013 144 | - Robustness diagram prototyping 145 | - Fixed pollution of jQuery namespace with some funcitons 146 | - Use GRUNT for bulid, Mar 10, 2013 147 | - 0.1.2b, Jan 9, 2013 148 | - @fragment directive 149 | - 0.1.2a, Dec 31, 2012 150 | - Fixed https://github.com/tmtk75/jumly/issues/4 151 | - Try JUMLY, Dec 29, 2012 152 | - interactive demo for sequence diagram 153 | - 0.1.2, Dec 29, 2012 154 | - change CSS class name for .participant, which was .object 155 | - Reference Manual r1 published Dec 10, 2012 156 | - 0.1.1 Nov 29, 2012 157 | - support @note directive 158 | - adjust margins and spaces in stylesheet 159 | - 0.1.0 Nov 23, 2012 -- initial release 160 | - support sequence diagram 161 | 162 | # Special Thanks 163 | - jQuery 164 | - CoffeeScript 165 | - node.js 166 | - express 167 | - GitHub 168 | - heroku 169 | - jade 170 | - Stylus 171 | - Markdown 172 | - GRUNT 173 | - webpack 174 | - Karma 175 | -------------------------------------------------------------------------------- /lib/css/candidate.styl: -------------------------------------------------------------------------------- 1 | .centering 2 | margin-left: auto 3 | margin-right: auto 4 | 5 | .unimportant 6 | z-index: 0 7 | 8 | .primary_border 9 | border: 2px solid #808080 10 | 11 | .class-diagram .class .icon 12 | border: 2px solid #808080 13 | -webkit-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 14 | -moz-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 15 | -o-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 16 | box-shadow: 10px 5px 5px rgba(0,0,0,.33) 17 | -moz-border-radius: 3px 18 | -webkit-border-radius: 3px 19 | -o-border-radius: 3px 20 | border-radius: 3px 21 | float: left 22 | min-width: 120px 23 | background-color: #fff 24 | margin-left: 2px 25 | margin-bottom: 2px 26 | 27 | .class-diagram .class .icon .stereotype,.class-diagram .class .icon .name 28 | display: block 29 | text-align: center 30 | 31 | .class-diagram .class .icon .stereotype:before 32 | content: '<<' 33 | 34 | .class-diagram .class .icon .stereotype:after 35 | content: '>>' 36 | 37 | .class-diagram .class .icon .name 38 | margin: .2em .5em .2em .5em 39 | font-weight: 700 40 | 41 | .class-diagram .class .icon .attrs,.class-diagram .class .icon .methods 42 | border-top: solid 2px gray 43 | min-height: 8px 44 | 45 | .class-diagram .class .icon .attrs>*,.class-diagram .class .icon .methods>* 46 | display: block 47 | margin: 0 48 | padding: 0 49 | 50 | .class-diagram .class .icon ul 51 | -webkit-margin-before: 0 52 | -webkit-margin-after: 0 53 | -webkit-margin-start: 0 54 | -webkit-margin-end: 0 55 | -webkit-padding-start: 0 56 | padding-left: 1em 57 | padding-right: 1em 58 | 59 | 60 | // relationship 61 | .diagram .relationship 62 | position: absolute 63 | 64 | svg line 65 | stroke gray 66 | stroke-width 1.5 67 | stroke-dasharray 4,4 68 | 69 | // usecase-a 70 | .usecase-diagram.layout-a .usecase:nth-child(odd)>.icon 71 | margin-left: auto 72 | 73 | .usecase-diagram.layout-a .usecase:nth-child(even)>.icon 74 | margin-right: auto 75 | 76 | .usecase-diagram.layout-a .actor 77 | position: relative 78 | 79 | .usecase-diagram.layout-a .actor:nth-child(odd) 80 | float: left 81 | 82 | .usecase-diagram.layout-a .actor:nth-child(even) 83 | float: right 84 | 85 | .usecase-diagram.layout-a .extend .name:before,.usecase-diagram.layout-a .include .name:before,.usecase-diagram.layout-a .use .name:before 86 | left: 45% 87 | top: 45% 88 | position: absolute 89 | 90 | .usecase-diagram.layout-a .system-boundary 91 | width: 55% 92 | margin-left: auto 93 | margin-right: auto 94 | 95 | .usecase-diagram.layout-a .system-boundary>.name 96 | font-weight: 700 97 | 98 | .usecase-diagram.layout-a .system-boundary .usecase:nth-child(4n+2) .icon 99 | margin-left: 4px 100 | 101 | .usecase-diagram.layout-a .system-boundary .usecase:nth-child(3) .icon 102 | margin-right: 4px 103 | 104 | .usecase-diagram.layout-a .system-boundary .system-boundary 105 | margin-top: 4px 106 | width: 95% 107 | 108 | .usecase-diagram.layout-a .system-boundary.out-of-bounds 109 | border-width: 0 110 | border-color: transparent 111 | box-shadow: none 112 | width: 0 113 | height: 0 114 | padding: 0 115 | margin-top: 0 116 | margin-bottom: 0 117 | 118 | // usecase 119 | .box_shadow 120 | -webkit-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 121 | -moz-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 122 | -o-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 123 | box-shadow: 10px 5px 5px rgba(0,0,0,.33) 124 | 125 | .box_shadow2 126 | -webkit-box-shadow: 5px 2.5px 2.5px rgba(0,0,0,.33) 127 | -moz-box-shadow: 5px 2.5px 2.5px rgba(0,0,0,.33) 128 | -o-box-shadow: 5px 2.5px 2.5px rgba(0,0,0,.33) 129 | box-shadow: 5px 2.5px 2.5px rgba(0,0,0,.33) 130 | 131 | .text_shadow 132 | -text-shadow: 4px 3px 0 #fff,9px 8px 0 rgba(0,0,0,.15) 133 | 134 | .centering 135 | margin-left: auto 136 | margin-right: auto 137 | 138 | .unimportant 139 | z-index: 0 140 | 141 | .primary_border 142 | border: 2px solid #808080 143 | 144 | .usecase-diagram .usecase .icon 145 | min-width: 132px 146 | min-height: 51px 147 | border: 2px solid #808080 148 | -moz-border-radius: "132px / 51px" 149 | -webkit-border-radius: "132px / 51px" 150 | -o-border-radius: "132px / 51px" 151 | border-radius: "132px / 51px" 152 | -webkit-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 153 | -moz-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 154 | -o-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 155 | box-shadow: 10px 5px 5px rgba(0,0,0,.33) 156 | background-color: #fff 157 | 158 | .usecase-diagram .usecase .icon .name 159 | text-align: left 160 | vertical-align: middle 161 | 162 | .usecase-diagram .use .name:before 163 | content: '<>' 164 | 165 | .usecase-diagram .extend .name:before 166 | content: '<>' 167 | 168 | .usecase-diagram .include .name:before 169 | content: '<>' 170 | 171 | .usecase-diagram .system-boundary 172 | border: 2px solid #808080 173 | -webkit-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 174 | -moz-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 175 | -o-box-shadow: 10px 5px 5px rgba(0,0,0,.33) 176 | box-shadow: 10px 5px 5px rgba(0,0,0,.33) 177 | padding: 4px 178 | background-color: #fff 179 | 180 | .usecase-diagram .system-boundary .system-boundary 181 | min-width: 198px 182 | 183 | .usecase-diagram.layout-a .usecase:nth-child(odd)>.icon 184 | margin-left: auto 185 | 186 | .usecase-diagram.layout-a .usecase:nth-child(even)>.icon 187 | margin-right: auto 188 | 189 | .usecase-diagram.layout-a .actor 190 | position: relative 191 | 192 | .usecase-diagram.layout-a .actor:nth-child(odd) 193 | float: left 194 | 195 | .usecase-diagram.layout-a .actor:nth-child(even) 196 | float: right 197 | 198 | .usecase-diagram.layout-a .extend .name:before,.usecase-diagram.layout-a .include .name:before,.usecase-diagram.layout-a .use .name:before 199 | left: 45% 200 | top: 45% 201 | position: absolute 202 | 203 | .usecase-diagram.layout-a .system-boundary 204 | width: 55% 205 | margin-left: auto 206 | margin-right: auto 207 | 208 | .usecase-diagram.layout-a .system-boundary>.name 209 | font-weight: 700 210 | 211 | .usecase-diagram.layout-a .system-boundary .usecase:nth-child(4n+2) .icon 212 | margin-left: 4px 213 | 214 | .usecase-diagram.layout-a .system-boundary .usecase:nth-child(3) .icon 215 | margin-right: 4px 216 | 217 | .usecase-diagram.layout-a .system-boundary .system-boundary 218 | margin-top: 4px 219 | width: 95% 220 | 221 | .usecase-diagram.layout-a .system-boundary.out-of-bounds 222 | border-width: 0 223 | border-color: transparent 224 | box-shadow: none 225 | width: 0 226 | height: 0 227 | padding: 0 228 | margin-top: 0 229 | margin-bottom: 0 230 | 231 | // 232 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/styles/shCore.css: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | .syntaxhighlighter a, 18 | .syntaxhighlighter div, 19 | .syntaxhighlighter code, 20 | .syntaxhighlighter table, 21 | .syntaxhighlighter table td, 22 | .syntaxhighlighter table tr, 23 | .syntaxhighlighter table tbody, 24 | .syntaxhighlighter table thead, 25 | .syntaxhighlighter table caption, 26 | .syntaxhighlighter textarea { 27 | -moz-border-radius: 0 0 0 0 !important; 28 | -webkit-border-radius: 0 0 0 0 !important; 29 | background: none !important; 30 | border: 0 !important; 31 | bottom: auto !important; 32 | float: none !important; 33 | height: auto !important; 34 | left: auto !important; 35 | line-height: 1.1em !important; 36 | margin: 0 !important; 37 | outline: 0 !important; 38 | overflow: visible !important; 39 | padding: 0 !important; 40 | position: static !important; 41 | right: auto !important; 42 | text-align: left !important; 43 | top: auto !important; 44 | vertical-align: baseline !important; 45 | width: auto !important; 46 | box-sizing: content-box !important; 47 | font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; 48 | font-weight: normal !important; 49 | font-style: normal !important; 50 | font-size: 1em !important; 51 | min-height: inherit !important; 52 | min-height: auto !important; 53 | } 54 | 55 | .syntaxhighlighter { 56 | width: 100% !important; 57 | margin: 1em 0 1em 0 !important; 58 | position: relative !important; 59 | overflow: auto !important; 60 | font-size: 1em !important; 61 | } 62 | .syntaxhighlighter.source { 63 | overflow: hidden !important; 64 | } 65 | .syntaxhighlighter .bold { 66 | font-weight: bold !important; 67 | } 68 | .syntaxhighlighter .italic { 69 | font-style: italic !important; 70 | } 71 | .syntaxhighlighter .line { 72 | white-space: pre !important; 73 | } 74 | .syntaxhighlighter table { 75 | width: 100% !important; 76 | } 77 | .syntaxhighlighter table caption { 78 | text-align: left !important; 79 | padding: .5em 0 0.5em 1em !important; 80 | } 81 | .syntaxhighlighter table td.code { 82 | width: 100% !important; 83 | } 84 | .syntaxhighlighter table td.code .container { 85 | position: relative !important; 86 | } 87 | .syntaxhighlighter table td.code .container textarea { 88 | box-sizing: border-box !important; 89 | position: absolute !important; 90 | left: 0 !important; 91 | top: 0 !important; 92 | width: 100% !important; 93 | height: 100% !important; 94 | border: none !important; 95 | background: white !important; 96 | padding-left: 1em !important; 97 | overflow: hidden !important; 98 | white-space: pre !important; 99 | } 100 | .syntaxhighlighter table td.gutter .line { 101 | text-align: right !important; 102 | padding: 0 0.5em 0 1em !important; 103 | } 104 | .syntaxhighlighter table td.code .line { 105 | padding: 0 1em !important; 106 | } 107 | .syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { 108 | padding-left: 0em !important; 109 | } 110 | .syntaxhighlighter.show { 111 | display: block !important; 112 | } 113 | .syntaxhighlighter.collapsed table { 114 | display: none !important; 115 | } 116 | .syntaxhighlighter.collapsed .toolbar { 117 | padding: 0.1em 0.8em 0em 0.8em !important; 118 | font-size: 1em !important; 119 | position: static !important; 120 | width: auto !important; 121 | height: auto !important; 122 | } 123 | .syntaxhighlighter.collapsed .toolbar span { 124 | display: inline !important; 125 | margin-right: 1em !important; 126 | } 127 | .syntaxhighlighter.collapsed .toolbar span a { 128 | padding: 0 !important; 129 | display: none !important; 130 | } 131 | .syntaxhighlighter.collapsed .toolbar span a.expandSource { 132 | display: inline !important; 133 | } 134 | .syntaxhighlighter .toolbar { 135 | position: absolute !important; 136 | right: 1px !important; 137 | top: 1px !important; 138 | width: 11px !important; 139 | height: 11px !important; 140 | font-size: 10px !important; 141 | z-index: 10 !important; 142 | } 143 | .syntaxhighlighter .toolbar span.title { 144 | display: inline !important; 145 | } 146 | .syntaxhighlighter .toolbar a { 147 | display: block !important; 148 | text-align: center !important; 149 | text-decoration: none !important; 150 | padding-top: 1px !important; 151 | } 152 | .syntaxhighlighter .toolbar a.expandSource { 153 | display: none !important; 154 | } 155 | .syntaxhighlighter.ie { 156 | font-size: .9em !important; 157 | padding: 1px 0 1px 0 !important; 158 | } 159 | .syntaxhighlighter.ie .toolbar { 160 | line-height: 8px !important; 161 | } 162 | .syntaxhighlighter.ie .toolbar a { 163 | padding-top: 0px !important; 164 | } 165 | .syntaxhighlighter.printing .line.alt1 .content, 166 | .syntaxhighlighter.printing .line.alt2 .content, 167 | .syntaxhighlighter.printing .line.highlighted .number, 168 | .syntaxhighlighter.printing .line.highlighted.alt1 .content, 169 | .syntaxhighlighter.printing .line.highlighted.alt2 .content { 170 | background: none !important; 171 | } 172 | .syntaxhighlighter.printing .line .number { 173 | color: #bbbbbb !important; 174 | } 175 | .syntaxhighlighter.printing .line .content { 176 | color: black !important; 177 | } 178 | .syntaxhighlighter.printing .toolbar { 179 | display: none !important; 180 | } 181 | .syntaxhighlighter.printing a { 182 | text-decoration: none !important; 183 | } 184 | .syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { 185 | color: black !important; 186 | } 187 | .syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { 188 | color: #008200 !important; 189 | } 190 | .syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { 191 | color: blue !important; 192 | } 193 | .syntaxhighlighter.printing .keyword { 194 | color: #006699 !important; 195 | font-weight: bold !important; 196 | } 197 | .syntaxhighlighter.printing .preprocessor { 198 | color: gray !important; 199 | } 200 | .syntaxhighlighter.printing .variable { 201 | color: #aa7700 !important; 202 | } 203 | .syntaxhighlighter.printing .value { 204 | color: #009900 !important; 205 | } 206 | .syntaxhighlighter.printing .functions { 207 | color: #ff1493 !important; 208 | } 209 | .syntaxhighlighter.printing .constants { 210 | color: #0066cc !important; 211 | } 212 | .syntaxhighlighter.printing .script { 213 | font-weight: bold !important; 214 | } 215 | .syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { 216 | color: gray !important; 217 | } 218 | .syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { 219 | color: #ff1493 !important; 220 | } 221 | .syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { 222 | color: red !important; 223 | } 224 | .syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { 225 | color: black !important; 226 | } 227 | -------------------------------------------------------------------------------- /lib/js/SequenceDiagramBuilder.coffee: -------------------------------------------------------------------------------- 1 | $ = require "jquery" 2 | core = require "core.coffee" 3 | DiagramBuilder = require "DiagramBuilder.coffee" 4 | SequenceDiagram = require "SequenceDiagram.coffee" 5 | SequenceParticipant = require "SequenceParticipant.coffee" 6 | SequenceRef = require "SequenceRef.coffee" 7 | SequenceFragment = require "SequenceFragment.coffee" 8 | NoteElement = require "NoteElement.coffee" 9 | 10 | class SequenceDiagramBuilder extends DiagramBuilder 11 | constructor: (@_diagram, @_occurr)-> 12 | super() 13 | @_diagram ?= new SequenceDiagram 14 | 15 | SequenceDiagramBuilder::_curr_occurr = -> 16 | @_occurr 17 | 18 | SequenceDiagramBuilder::_curr_actor = -> 19 | @_occurr._actor 20 | 21 | SequenceDiagramBuilder::found = (sth, callback)-> 22 | actor = @_find_or_create sth 23 | actor.addClass "found" 24 | @_occurr = actor.activate() 25 | callback?.apply this, [this] 26 | @_occurr = null 27 | this 28 | 29 | 30 | SequenceDiagramBuilder::_find_or_create = (sth)-> 31 | a = core._normalize sth 32 | r = core._to_ref a.id 33 | throw new Error("Reserved word '#{r}'") if typeof @_diagram[r] is "function" 34 | return @_diagram[r] if @_diagram[r] 35 | obj = new SequenceParticipant sth 36 | @_diagram._reg_by_ref a.id, obj 37 | @_diagram.append obj 38 | switch typeof sth 39 | when "string" 40 | @_diagram._var r, obj 41 | when "object" 42 | @_diagram._var core._to_ref(a.id), obj 43 | else 44 | console.error "It must be string or object for", eth 45 | throw new Error "Unrecognized argument: #{e}" 46 | obj 47 | 48 | SequenceDiagramBuilder::message = (a, b, c)-> 49 | actname = a 50 | if (typeof a is "string") and (typeof b is "function" or b is undefined) 51 | actee = @_curr_actor() 52 | callback = b 53 | else if typeof a is "string" and typeof b is "string" 54 | if typeof c is "function" 55 | actee = @_find_or_create b 56 | callback = c 57 | else if c is undefined 58 | actee = @_find_or_create b 59 | callback = null 60 | else if typeof a is "object" and typeof b is "string" 61 | actee = @_find_or_create b 62 | callback = c 63 | for e of a 64 | switch e 65 | when "asynchronous" 66 | actname = a[e] 67 | stereotype = "asynchronous" 68 | else if typeof a is "string" and typeof b is "object" 69 | norm = core._normalize b 70 | actee = @_find_or_create norm 71 | callback = c 72 | else 73 | msg = "invalid arguments" 74 | console.error "SequenceDiagramBuilder::message", msg, a, b, c 75 | throw new Error(msg, a, b, c) 76 | 77 | iact = @_curr_occurr().interact actee 78 | iact.find(".name").text(actname).end() 79 | .find(".message").addClass(stereotype) 80 | 81 | it = (new SequenceDiagramBuilder @_diagram, iact._actee) 82 | callback?.apply it, [] 83 | it 84 | 85 | SequenceDiagramBuilder::create = (a, b, c)-> 86 | if typeof a is "string" and typeof b is "function" 87 | name = null 88 | actee = a 89 | callback = b 90 | else if typeof a is "string" and typeof b is "string" and typeof c is "function" 91 | name = a 92 | actee = b 93 | callback = c 94 | else if typeof a is "string" and b is undefined 95 | name = null 96 | actee = a 97 | callback = null 98 | else if typeof a is "object" 99 | e = core._normalize a 100 | actee = e.name 101 | async = a.asynchronous? 102 | callback = b if typeof b is "function" 103 | 104 | if typeof a is "string" 105 | id = core._to_id(actee) 106 | else 107 | norm = core._normalize a 108 | id = norm.id 109 | actee = norm.name 110 | 111 | iact = @_curr_occurr().create id:id, name:actee 112 | iact.name name if name 113 | iact.find(".message:eq(0)").addClass "asynchronous" if async 114 | occurr = iact._actee 115 | ctxt = new SequenceDiagramBuilder(@_diagram, occurr) 116 | callback?.apply ctxt, [] 117 | @_var id, occurr._actor 118 | @_diagram._reg_by_ref id, occurr._actor 119 | ctxt 120 | 121 | SequenceDiagramBuilder::_var = (varname, refobj)-> 122 | ref = core._to_ref varname 123 | @_diagram._var ref, refobj 124 | 125 | SequenceDiagramBuilder::destroy = (a)-> 126 | @_curr_occurr().destroy @_find_or_create a 127 | null 128 | 129 | SequenceDiagramBuilder::reply = (a, b)-> 130 | obj = b 131 | if typeof b is "string" 132 | ref = core._to_ref core._to_id b 133 | obj = @_diagram[ref] if @_diagram[ref] 134 | 135 | f = (occur, n)-> if occur.is_self() then f(occur._parent_occurr(), n + 1) else n 136 | n = f @_curr_occurr(), 0 137 | @_curr_occurr() 138 | .parents(".interaction:eq(#{n})").data("_self") 139 | .reply name:a, ".actee":obj 140 | null 141 | 142 | SequenceDiagramBuilder::ref = (a)-> 143 | occur = @_curr_occurr() 144 | ref = new SequenceRef a 145 | if occur 146 | occur.append ref 147 | else 148 | @diagram().append ref 149 | 150 | @_refer ref, by:a 151 | ref 152 | 153 | SequenceDiagramBuilder::lost = (a)-> 154 | @_curr_occurr.lost() 155 | null 156 | 157 | SequenceDiagramBuilder::fragment = (nctx)-> 158 | for name of nctx 159 | ctx = nctx[name] 160 | frag = @_fragment ctx, label:name 161 | 162 | @_refer frag, by:name 163 | return # stop at first property 164 | 165 | SequenceDiagramBuilder::loop = (a, b, c)-> 166 | last = [].slice.apply(arguments).pop() ## Last one is Function 167 | return unless $.isFunction(last) 168 | @_fragment last, kind:"loop", label:"Loop", a 169 | 170 | SequenceDiagramBuilder::_fragment = (last, opts, desc)-> 171 | kids = @_curr_occurr().find("> *") 172 | last.apply this, [] 173 | newones = @_curr_occurr().find("> *").not(kids) 174 | if newones.length > 0 175 | frag = new SequenceFragment() 176 | frag.addClass opts.kind if opts.kind 177 | frag.enclose newones 178 | frag.find(".name:first").html opts.label 179 | if typeof desc is "string" 180 | frag.find("> .header > .condition").html desc 181 | frag 182 | 183 | SequenceDiagramBuilder::alt = (ints)-> 184 | iacts = {} 185 | self = this 186 | for name of ints 187 | unless typeof ints[name] is "function" 188 | break 189 | 190 | _new_act = (name, act)-> -> ## Double '->' is in order to bind name & act in this loop. 191 | nodes = [] 192 | _ = (it)-> 193 | if it?.constructor is SequenceDiagramBuilder 194 | node = it._curr_occurr().parent(".interaction:eq(0)") 195 | else 196 | node = it 197 | nodes.push node 198 | act.apply { 199 | _curr_actor: -> self._curr_actor.apply self, arguments 200 | message: -> _ (self.message.apply self, arguments) 201 | loop: -> _ (self.loop.apply self, arguments) 202 | ref: -> _ (self.ref.apply self, arguments) 203 | } 204 | nodes 205 | 206 | iacts[name] = _new_act name, ints[name] 207 | 208 | @_curr_occurr().interact stereotype:".alt", iacts 209 | this 210 | 211 | ### 212 | Examples: 213 | - @reactivate "do something", "A" 214 | - @reactivate @message "call a taxi", "Taxi agent" 215 | ### 216 | SequenceDiagramBuilder::reactivate = (a, b, c)-> 217 | if a.constructor is this.constructor 218 | e = a._curr_occurr.parents(".interaction:eq(0)") 219 | @_curr_actor().activate().append e 220 | return a 221 | occurr = @_curr_actor().activate() 222 | @_occurr = occurr 223 | @message(a, b, c) 224 | 225 | SequenceDiagramBuilder::css = (styles)-> 226 | @_diagram.css styles 227 | 228 | SequenceDiagramBuilder::find = (selector)-> 229 | @_diagram.find selector 230 | 231 | 232 | SequenceDiagramBuilder::note = (text, opts)-> 233 | note = new NoteElement text, opts 234 | @_curr_occurr().append note 235 | 236 | SequenceDiagramBuilder::compose = (opts)-> 237 | if typeof opts is "function" 238 | opts @_diagram 239 | else 240 | opts?.append @_diagram 241 | @_diagram.compose opts 242 | 243 | SequenceDiagramBuilder::preferences = -> 244 | @_diagram.preferences.apply @_diagram, arguments 245 | 246 | ## 247 | #JUMLY.DSL type:'.sequence-diagram', compileScript: (script)-> 248 | # b = new SequenceDiagramBuilder 249 | # b.build script.html() 250 | 251 | #JUMLY.SequenceDiagramBuilder = SequenceDiagramBuilder 252 | 253 | module.exports = SequenceDiagramBuilder 254 | -------------------------------------------------------------------------------- /public/syntaxhighlighter/scripts/shBrushAppleScript.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | ;(function() 18 | { 19 | // CommonJS 20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; 21 | 22 | function Brush() 23 | { 24 | // AppleScript brush by David Chambers 25 | // http://davidchambersdesign.com/ 26 | var keywords = 'after before beginning continue copy each end every from return get global in local named of set some that the then times to where whose with without'; 27 | var ordinals = 'first second third fourth fifth sixth seventh eighth ninth tenth last front back middle'; 28 | var specials = 'activate add alias AppleScript ask attachment boolean class constant delete duplicate empty exists false id integer list make message modal modified new no paragraph pi properties quit real record remove rest result reveal reverse run running save string true word yes'; 29 | 30 | this.regexList = [ 31 | 32 | { regex: /(--|#).*$/gm, 33 | css: 'comments' }, 34 | 35 | { regex: /\(\*(?:[\s\S]*?\(\*[\s\S]*?\*\))*[\s\S]*?\*\)/gm, // support nested comments 36 | css: 'comments' }, 37 | 38 | { regex: /"[\s\S]*?"/gm, 39 | css: 'string' }, 40 | 41 | { regex: /(?:,|:|¬|'s\b|\(|\)|\{|\}|«|\b\w*»)/g, 42 | css: 'color1' }, 43 | 44 | { regex: /(-)?(\d)+(\.(\d)?)?(E\+(\d)+)?/g, // numbers 45 | css: 'color1' }, 46 | 47 | { regex: /(?:&(amp;|gt;|lt;)?|=|� |>|<|≥|>=|≤|<=|\*|\+|-|\/|÷|\^)/g, 48 | css: 'color2' }, 49 | 50 | { regex: /\b(?:and|as|div|mod|not|or|return(?!\s&)(ing)?|equals|(is(n't| not)? )?equal( to)?|does(n't| not) equal|(is(n't| not)? )?(greater|less) than( or equal( to)?)?|(comes|does(n't| not) come) (after|before)|is(n't| not)?( in)? (back|front) of|is(n't| not)? behind|is(n't| not)?( (in|contained by))?|does(n't| not) contain|contain(s)?|(start|begin|end)(s)? with|((but|end) )?(consider|ignor)ing|prop(erty)?|(a )?ref(erence)?( to)?|repeat (until|while|with)|((end|exit) )?repeat|((else|end) )?if|else|(end )?(script|tell|try)|(on )?error|(put )?into|(of )?(it|me)|its|my|with (timeout( of)?|transaction)|end (timeout|transaction))\b/g, 51 | css: 'keyword' }, 52 | 53 | { regex: /\b\d+(st|nd|rd|th)\b/g, // ordinals 54 | css: 'keyword' }, 55 | 56 | { regex: /\b(?:about|above|against|around|at|below|beneath|beside|between|by|(apart|aside) from|(instead|out) of|into|on(to)?|over|since|thr(ough|u)|under)\b/g, 57 | css: 'color3' }, 58 | 59 | { regex: /\b(?:adding folder items to|after receiving|choose( ((remote )?application|color|folder|from list|URL))?|clipboard info|set the clipboard to|(the )?clipboard|entire contents|display(ing| (alert|dialog|mode))?|document( (edited|file|nib name))?|file( (name|type))?|(info )?for|giving up after|(name )?extension|quoted form|return(ed)?|second(?! item)(s)?|list (disks|folder)|text item(s| delimiters)?|(Unicode )?text|(disk )?item(s)?|((current|list) )?view|((container|key) )?window|with (data|icon( (caution|note|stop))?|parameter(s)?|prompt|properties|seed|title)|case|diacriticals|hyphens|numeric strings|punctuation|white space|folder creation|application(s( folder)?| (processes|scripts position|support))?|((desktop )?(pictures )?|(documents|downloads|favorites|home|keychain|library|movies|music|public|scripts|sites|system|users|utilities|workflows) )folder|desktop|Folder Action scripts|font(s| panel)?|help|internet plugins|modem scripts|(system )?preferences|printer descriptions|scripting (additions|components)|shared (documents|libraries)|startup (disk|items)|temporary items|trash|on server|in AppleTalk zone|((as|long|short) )?user name|user (ID|locale)|(with )?password|in (bundle( with identifier)?|directory)|(close|open for) access|read|write( permission)?|(g|s)et eof|using( delimiters)?|starting at|default (answer|button|color|country code|entr(y|ies)|identifiers|items|name|location|script editor)|hidden( answer)?|open(ed| (location|untitled))?|error (handling|reporting)|(do( shell)?|load|run|store) script|administrator privileges|altering line endings|get volume settings|(alert|boot|input|mount|output|set) volume|output muted|(fax|random )?number|round(ing)?|up|down|toward zero|to nearest|as taught in school|system (attribute|info)|((AppleScript( Studio)?|system) )?version|(home )?directory|(IPv4|primary Ethernet) address|CPU (type|speed)|physical memory|time (stamp|to GMT)|replacing|ASCII (character|number)|localized string|from table|offset|summarize|beep|delay|say|(empty|multiple) selections allowed|(of|preferred) type|invisibles|showing( package contents)?|editable URL|(File|FTP|News|Media|Web) [Ss]ervers|Telnet hosts|Directory services|Remote applications|waiting until completion|saving( (in|to))?|path (for|to( (((current|frontmost) )?application|resource))?)|POSIX (file|path)|(background|RGB) color|(OK|cancel) button name|cancel button|button(s)?|cubic ((centi)?met(re|er)s|yards|feet|inches)|square ((kilo)?met(re|er)s|miles|yards|feet)|(centi|kilo)?met(re|er)s|miles|yards|feet|inches|lit(re|er)s|gallons|quarts|(kilo)?grams|ounces|pounds|degrees (Celsius|Fahrenheit|Kelvin)|print( (dialog|settings))?|clos(e(able)?|ing)|(de)?miniaturized|miniaturizable|zoom(ed|able)|attribute run|action (method|property|title)|phone|email|((start|end)ing|home) page|((birth|creation|current|custom|modification) )?date|((((phonetic )?(first|last|middle))|computer|host|maiden|related) |nick)?name|aim|icq|jabber|msn|yahoo|address(es)?|save addressbook|should enable action|city|country( code)?|formatte(r|d address)|(palette )?label|state|street|zip|AIM [Hh]andle(s)?|my card|select(ion| all)?|unsaved|(alpha )?value|entr(y|ies)|group|(ICQ|Jabber|MSN) handle|person|people|company|department|icon image|job title|note|organization|suffix|vcard|url|copies|collating|pages (across|down)|request print time|target( printer)?|((GUI Scripting|Script menu) )?enabled|show Computer scripts|(de)?activated|awake from nib|became (key|main)|call method|of (class|object)|center|clicked toolbar item|closed|for document|exposed|(can )?hide|idle|keyboard (down|up)|event( (number|type))?|launch(ed)?|load (image|movie|nib|sound)|owner|log|mouse (down|dragged|entered|exited|moved|up)|move|column|localization|resource|script|register|drag (info|types)|resigned (active|key|main)|resiz(e(d)?|able)|right mouse (down|dragged|up)|scroll wheel|(at )?index|should (close|open( untitled)?|quit( after last window closed)?|zoom)|((proposed|screen) )?bounds|show(n)?|behind|in front of|size (mode|to fit)|update(d| toolbar item)?|was (hidden|miniaturized)|will (become active|close|finish launching|hide|miniaturize|move|open|quit|(resign )?active|((maximum|minimum|proposed) )?size|show|zoom)|bundle|data source|movie|pasteboard|sound|tool(bar| tip)|(color|open|save) panel|coordinate system|frontmost|main( (bundle|menu|window))?|((services|(excluded from )?windows) )?menu|((executable|frameworks|resource|scripts|shared (frameworks|support)) )?path|(selected item )?identifier|data|content(s| view)?|character(s)?|click count|(command|control|option|shift) key down|context|delta (x|y|z)|key( code)?|location|pressure|unmodified characters|types|(first )?responder|playing|(allowed|selectable) identifiers|allows customization|(auto saves )?configuration|visible|image( name)?|menu form representation|tag|user(-| )defaults|associated file name|(auto|needs) display|current field editor|floating|has (resize indicator|shadow)|hides when deactivated|level|minimized (image|title)|opaque|position|release when closed|sheet|title(d)?)\b/g, 60 | css: 'color3' }, 61 | 62 | { regex: new RegExp(this.getKeywords(specials), 'gm'), css: 'color3' }, 63 | { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' }, 64 | { regex: new RegExp(this.getKeywords(ordinals), 'gm'), css: 'keyword' } 65 | ]; 66 | }; 67 | 68 | Brush.prototype = new SyntaxHighlighter.Highlighter(); 69 | Brush.aliases = ['applescript']; 70 | 71 | SyntaxHighlighter.brushes.AppleScript = Brush; 72 | 73 | // CommonJS 74 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null; 75 | })(); 76 | --------------------------------------------------------------------------------