").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($("