1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 13:19:43 +02:00

PlantUML integration

This commit is contained in:
sauls8t 2018-02-07 13:44:18 +00:00
parent 0f3de51ad5
commit 7d3473365a
18 changed files with 521 additions and 291 deletions

View file

@ -1,55 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package mermaid
import (
"net/http"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider"
)
// Provider represents Mermaid Diagram
type Provider struct {
Runtime *env.Runtime
Store *domain.Store
}
// Meta describes us
func (*Provider) Meta() provider.TypeMeta {
section := provider.TypeMeta{}
section.ID = "f1067a60-45e5-40b5-89f6-aa3b03dd7f35"
section.Title = "Mermaid Diagram"
section.Description = "Diagrams generated from textual descriptions"
section.ContentType = "mermaid"
section.PageType = "tab"
section.Order = 9990
return section
}
// Command stub.
func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
provider.WriteEmpty(w)
}
// Render returns data as-is (HTML).
func (*Provider) Render(ctx *provider.Context, config, data string) string {
return data
}
// Refresh just sends back data as-is.
func (*Provider) Refresh(ctx *provider.Context, config, data string) string {
return data
}

View file

@ -0,0 +1,116 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package plantuml
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider"
)
// Provider represents Mermaid Diagram
type Provider struct {
Runtime *env.Runtime
Store *domain.Store
}
// Meta describes us
func (*Provider) Meta() provider.TypeMeta {
section := provider.TypeMeta{}
section.ID = "f1067a60-45e5-40b5-89f6-aa3b03dd7f35"
section.Title = "PlantUML Diagram"
section.Description = "Diagrams generated from text"
section.ContentType = "plantuml"
section.PageType = "tab"
section.Order = 9990
return section
}
// Command stub.
func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
method := query.Get("method")
if len(method) == 0 {
provider.WriteMessage(w, "plantuml", "missing method name")
return
}
switch method {
case "preview":
var payload struct {
Data string `json:"data"`
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
provider.WriteMessage(w, "plantuml", "Bad payload")
return
}
err = json.Unmarshal(body, &payload)
if err != nil {
provider.WriteMessage(w, "plantuml", "Cannot unmarshal")
return
}
diagram := p.generateDiagram(ctx, payload.Data)
payload.Data = diagram
provider.WriteJSON(w, payload)
return
}
provider.WriteEmpty(w)
}
// Render returns data as-is (HTML).
func (p *Provider) Render(ctx *provider.Context, config, data string) string {
return p.generateDiagram(ctx, data)
}
// Refresh just sends back data as-is.
func (*Provider) Refresh(ctx *provider.Context, config, data string) string {
return data
}
func (p *Provider) generateDiagram(ctx *provider.Context, data string) string {
org, _ := p.Store.Organization.GetOrganization(ctx.Request, ctx.OrgID)
var transport = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // TODO should be glick.InsecureSkipVerifyTLS (from -insecure flag) but get error: x509: certificate signed by unknown authority
}}
client := &http.Client{Transport: transport}
resp, _ := client.Post(org.ConversionEndpoint+"/api/plantuml", "application/text", bytes.NewReader([]byte(data)))
defer func() {
if e := resp.Body.Close(); e != nil {
fmt.Println("resp.Body.Close error: " + e.Error())
}
}()
png, _ := ioutil.ReadAll(resp.Body)
pngEncoded := base64.StdEncoding.EncodeToString(png)
return string(fmt.Sprintf("data:image/png;base64, %s", pngEncoded))
}

View file

@ -21,8 +21,8 @@ import (
"github.com/documize/community/domain/section/gemini"
"github.com/documize/community/domain/section/github"
"github.com/documize/community/domain/section/markdown"
// "github.com/documize/community/domain/section/mermaid"
"github.com/documize/community/domain/section/papertrail"
"github.com/documize/community/domain/section/plantuml"
"github.com/documize/community/domain/section/provider"
"github.com/documize/community/domain/section/table"
"github.com/documize/community/domain/section/trello"
@ -41,7 +41,7 @@ func Register(rt *env.Runtime, s *domain.Store) {
provider.Register("trello", &trello.Provider{Runtime: rt, Store: s})
provider.Register("wysiwyg", &wysiwyg.Provider{Runtime: rt, Store: s})
provider.Register("airtable", &airtable.Provider{Runtime: rt, Store: s})
// provider.Register("mermaid", &mermaid.Provider{Runtime: rt, Store: s})
provider.Register("plantuml", &plantuml.Provider{Runtime: rt, Store: s})
p := provider.List()
rt.Log.Info(fmt.Sprintf("Registered %d sections", len(p)))

View file

@ -44,22 +44,23 @@ module.exports = {
}
],
globals: {
"is": true,
"mermaid": true,
"_": true,
"tinymce": true,
"CodeMirror": true,
"Mousetrap": true,
"Sortable": true,
"moment": true,
"Dropzone": true,
"server": true,
"authenticateUser": true,
"stubAudit": true,
"stubUserNotification": true,
"userLogin": true,
"Keycloak": true,
"slug": true,
"interact": true
}
"is": true,
"mermaid": true,
"_": true,
"tinymce": true,
"CodeMirror": true,
"Mousetrap": true,
"Sortable": true,
"moment": true,
"Dropzone": true,
"server": true,
"authenticateUser": true,
"stubAudit": true,
"stubUserNotification": true,
"userLogin": true,
"Keycloak": true,
"slug": true,
"interact": true,
"velocity": true
}
};

View file

@ -1,169 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import { computed, observer } from '@ember/object';
import Component from '@ember/component';
export default Component.extend({
isDirty: false,
diagramText: '',
diagramPreview: null,
config: null,
editorId: computed('page', function () {
let page = this.get('page');
return `mermaid-editor-${page.id}`;
}),
previewId: computed('page', function () {
let page = this.get('page');
return `mermaid-preview-${page.id}`;
}),
// generateDiagram: observer('diagramText', function() {
// let txt = this.get('diagramText');
// console.log('calc diaggram');
// let self = this;
// var cb = function(svg) {
// return svg;
// // self.set('diagramPreview', svg);
// };
// if (is.empty(this.get('diagramText'))) return '';
// mermaid.render(this.get('previewId'), txt, cb);
// }),
keyUp() {
this.generateDiagram();
},
generateDiagram() {
console.log('calc diaggram');
let txt = this.get('diagramText');
if (is.empty(this.get('diagramText'))) this.set('diagramPreview', '');
let self = this;
var cb = function(svg) {
self.set('diagramPreview', svg);
};
mermaid.render(this.get('previewId'), txt, cb);
},
didReceiveAttrs() {
this._super(...arguments);
let config = {};
mermaid.initialize({});
console.log('dra');
try {
config = JSON.parse(this.get('meta.config'));
} catch (e) {} // eslint-disable-line no-empty
if (is.empty(config)) {
config = {
txt: ""
};
}
this.set('diagramText', config.txt);
this.set('config', config);
this.generateDiagram();
},
// onType: function() {
// debounce(this, this.generateDiagram, 350);
// }.observes('diagramText'),
actions: {
isDirty() {
return this.get('isDirty') || (this.get('diagramText') !== this.get('config.txt'));
},
onCancel() {
let cb = this.get('onCancel');
cb();
},
onAction(title) {
let page = this.get('page');
let meta = this.get('meta');
meta.set('config', JSON.stringify({ txt: this.get('diagramText') }));
meta.set('rawBody', this.get('diagramPreview'));
page.set('body', this.get('diagramPreview'));
page.set('title', title);
let cb = this.get('onAction');
cb(page, meta);
},
onInsertFlowchart() {
let txt = `graph TB
c1-->a2
subgraph one
a1-->a2
end
subgraph two
b1-->b2
end
subgraph three
c1-->c2
end`;
// this.set('diagramPreview', null);
this.set('diagramText', txt);
this.generateDiagram();
},
onInsertSequence() {
let txt = `sequenceDiagram
participant Alice
participant Bob
Alice->John: Hello John, how are you?
loop Healthcheck
John->John: Fight against hypochondria
end
Note right of John: Rational thoughts <br/>prevail...
John-->Alice: Great!
John->Bob: How about you?
Bob-->John: Jolly good!`;
// this.set('diagramPreview', null);
this.set('diagramText', txt);
this.generateDiagram();
},
onInsertGantt() {
let txt = `gantt
dateFormat YYYY-MM-DD
title Adding GANTT diagram functionality to mermaid
section A section
Completed task :done, des1, 2014-01-06,2014-01-08
Active task :active, des2, 2014-01-09, 3d
Future task : des3, after des2, 5d
Future task2 : des4, after des3, 5d
section Critical tasks
Completed task in the critical line :crit, done, 2014-01-06,24h
Implement parser and jison :crit, done, after des1, 2d
Create tests for parser :crit, active, 3d
Future task in critical line :crit, 5d
Create tests for renderer :2d
Add to mermaid :1d`;
// this.set('diagramPreview', null);
this.set('diagramText', txt);
this.generateDiagram();
}
}
});

View file

@ -0,0 +1,342 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import { schedule } from '@ember/runloop';
import { inject as service } from '@ember/service';
import { computed, observer } from '@ember/object';
import Component from '@ember/component';
export default Component.extend({
appMeta: service(),
sectionSvc: service('section'),
isDirty: false,
waiting: false,
diagramText: '',
diagramPreview: null,
previewButtonCaption: 'Preview',
editorId: computed('page', function () {
let page = this.get('page');
return `plantuml-editor-${page.id}`;
}),
previewId: computed('page', function () {
let page = this.get('page');
return `plantuml-preview-${page.id}`;
}),
generatePreview() {
this.set('waiting', true);
this.set('previewButtonCaption', 'Generating preview...');
let self = this;
let data = { data: this.get('diagramText') };
schedule('afterRender', () => {
this.get('sectionSvc').fetch(this.get('page'), 'preview', data).then(function (response) {
self.set('diagramPreview', response.data);
self.set('waiting', false);
self.set('previewButtonCaption', 'Preview');
}, function (reason) { // eslint-disable-line no-unused-vars
self.set('diagramPreview', null);
self.set('waiting', false);
self.set('previewButtonCaption', 'Preview');
});
});
},
didReceiveAttrs() {
this._super(...arguments);
this.set('waiting', false);
this.set('diagramText', this.get('meta.rawBody'));
this.generatePreview();
},
actions: {
isDirty() {
return this.get('isDirty') || (this.get('diagramText') !== this.get('meta.rawBody'));
},
onCancel() {
let cb = this.get('onCancel');
cb();
},
onPreview() {
this.generatePreview();
},
onAction(title) {
this.set('waiting', true);
let page = this.get('page');
let meta = this.get('meta');
meta.set('rawBody', this.get('diagramText'));
page.set('title', title);
let cb = this.get('onAction');
cb(page, meta);
this.set('waiting', false);
},
onInsertActivity() {
let txt = `
@startuml
title Servlet Container
(*) --> "ClickServlet.handleRequest()"
--> "new Page"
if "Page.onSecurityCheck" then
->[true] "Page.onInit()"
if "isForward?" then
->[no] "Process controls"
if "continue processing?" then
-->[yes] ===RENDERING===
else
-->[no] ===REDIRECT_CHECK===
endif
else
-->[yes] ===RENDERING===
endif
if "is Post?" then
-->[yes] "Page.onPost()"
--> "Page.onRender()" as render
--> ===REDIRECT_CHECK===
else
-->[no] "Page.onGet()"
--> render
endif
else
-->[false] ===REDIRECT_CHECK===
endif
if "Do redirect?" then
->[yes] "redirect request"
--> ==BEFORE_DESTROY===
else
if "Do Forward?" then
-left->[yes] "Forward request"
--> ==BEFORE_DESTROY===
else
-right->[no] "Render page template"
--> ==BEFORE_DESTROY===
endif
endif
--> "Page.onDestroy()"
-->(*)
@enduml`;
this.set('diagramText', txt);
this.generatePreview();
},
onInsertSequence() {
let txt = `
@startuml
actor Bob #red
' The only difference between actor
'and participant is the drawing
participant Alice
participant "I have a reallylong name" as L #99FF99
/' You can also declare:
participant L as "I have a really long name" #99FF99
'/
Alice->Bob: Authentication Request
Bob->Alice: Authentication Response
Bob->L: Log transaction
@enduml`;
this.set('diagramText', txt);
this.generatePreview();
},
onInsertUseCase() {
let txt = `
@startuml
:Main Admin: as Admin
(Use the application) as (Use)
User -> (Start)
User --> (Use)
Admin ---> (Use)
note right of Admin : This is an example.
note right of (Use)
A note can also
be on several lines
end note
note "This note is connected to several objects." as N2
(Start) .. N2
N2 .. (Use)
@enduml
`;
this.set('diagramText', txt);
this.generatePreview();
},
onInsertClass() {
let txt = `
@startuml
class Foo1 {
You can use
several lines
..
as you want
and group
==
things together.
__
You can have as many groups
as you want
--
End of class
}
class User {
.. Simple Getter ..
+ getName()
+ getAddress()
.. Some setter ..
+ setName()
__ private data __
int age
-- encrypted --
String password
}
@enduml`;
this.set('diagramText', txt);
this.generatePreview();
},
onInsertActivityNew() {
let txt = `
@startuml
start
:ClickServlet.handleRequest();
:new page;
if (Page.onSecurityCheck) then (true)
:Page.onInit();
if (isForward?) then (no)
:Process controls;
if (continue processing?) then (no)
stop
endif
if (isPost?) then (yes)
:Page.onPost();
else (no)
:Page.onGet();
endif
:Page.onRender();
endif
else (false)
endif
if (do redirect?) then (yes)
:redirect process;
else
if (do forward?) then (yes)
:Forward request;
else (no)
:Render page template;
endif
endif
stop
@enduml`;
this.set('diagramText', txt);
this.generatePreview();
},
onInsertComponent() {
let txt = `
@startuml
package "Some Group" {
HTTP - [First Component]
[Another Component]
}
node "Other Groups" {
FTP - [Second Component]
[First Component] --> FTP
}
cloud {
[Example 1]
}
database "MySql" {
folder "This is my folder" {
[Folder 3]
}
frame "Foo" {
[Frame 4]
}
}
[Another Component] --> [Example 1]
[Example 1] --> [Folder 3]
[Folder 3] --> [Frame 4]
@enduml`;
this.set('diagramText', txt);
this.generatePreview();
},
onInsertState() {
let txt = `
@startuml
scale 600 width
[*] -> State1
State1 --> State2 : Succeeded
State1 --> [*] : Aborted
State2 --> State3 : Succeeded
State2 --> [*] : Aborted
state State3 {
state "Some State Name" as long1
long1 : Just a test
[*] --> long1
long1 --> long1 : New Data
long1 --> ProcessData : Enough Data
}
State3 --> State3 : Failed
State3 --> [*] : Succeeded / Save Result
State3 --> [*] : Aborted
@enduml`;
this.set('diagramText', txt);
this.generatePreview();
}
}
});

View file

@ -28,7 +28,7 @@
@import "section/markdown.scss";
@import "section/table.scss";
@import "section/code.scss";
@import "section/mermaid.scss";
@import "section/plantuml.scss";
@import "section/papertrail.scss";
@import "section/wysiwyg.scss";

View file

@ -1,4 +0,0 @@
.section-mermaid-diagram {
margin: 0;
padding: 0;
}

View file

@ -0,0 +1,4 @@
.section-plantuml-diagram {
margin: 0;
padding: 0;
}

View file

@ -1,5 +1,4 @@
<div id="section-editor-{{pageId}}">
<div class="row">
<div class="col-8 section-editor">
<div class="form-group">
@ -11,7 +10,6 @@
</div>
{{/if}}
</div>
<div class="col-4">
<div class="float-right">
{{#if busy}}
@ -23,7 +21,6 @@
</div>
</div>
</div>
<div class="row">
<div class="col section-editor">
<div class="canvas rounded">
@ -31,7 +28,6 @@
</div>
</div>
</div>
</div>
<div id={{concat "discard-modal"}} class="modal" tabindex="-1" role="dialog">

View file

@ -1,30 +0,0 @@
{{#section/base-editor document=document folder=folder page=page tip="Concise name that describes the diagram" isDirty=(action 'isDirty') onCancel=(action 'onCancel') onAction=(action 'onAction')}}
<div class="section-mermaid-diagram">
<div class="container">
<div class="row">
<div class="col-12 col-sm-6">
<div class="form-group">
<label>Diagram Text&nbsp;&nbsp;&nbsp;</label> <a href="https://mermaidjs.github.io/" target="_blank">(Mermaid User Guide)</a>
<textarea rows=20 id={{editorId}} class="diagram-editor form-control mousetrap">{{diagramText}}</textarea>
<div class="mt-3">
<p>Insert sample diagrams:</p>
<p>
<button type="button" class="btn btn-light btn-sm" {{action 'onInsertFlowchart'}}>Flowchart</button>
<button type="button" class="btn btn-light btn-sm" {{action 'onInsertSequence'}}>Sequence</button>
<button type="button" class="btn btn-light btn-sm" {{action 'onInsertGantt'}}>Gantt</button>
</p>
</div>
</div>
</div>
<div class="col-12 col-sm-6">
<div class="form-group">
<label>Diagram Preview</label>
<div id={{previewId}}>
{{{diagramPreview}}}
</div>
</div>
</div>
</div>
</div>
</div>
{{/section/base-editor}}

View file

@ -0,0 +1,36 @@
{{#section/base-editor document=document folder=folder page=page busy=waiting tip="Concise name that describes the diagram" isDirty=(action 'isDirty') onCancel=(action 'onCancel') onAction=(action 'onAction')}}
<div class="section-plantuml-diagram">
<div class="container">
<div class="row">
<div class="col-12">
<div class="form-group">
<label><a href="http://plantuml.com/" target="_blank">PlantUML Diagram</a></label>
<div class="my-3">
<p>Insert sample diagrams:</p>
<p>
<button type="button" class="btn btn-light" {{action 'onInsertSequence'}}>Sequence</button>
<button type="button" class="btn btn-light" {{action 'onInsertUseCase'}}>Use Case</button>
<button type="button" class="btn btn-light" {{action 'onInsertClass'}}>Class</button>
<button type="button" class="btn btn-light" {{action 'onInsertActivity'}}>Activity</button>
<button type="button" class="btn btn-light" {{action 'onInsertActivityNew'}}>Activity (new syntax)</button>
<button type="button" class="btn btn-light" {{action 'onInsertComponent'}}>Component</button>
<button type="button" class="btn btn-light" {{action 'onInsertState'}}>State</button>
</p>
</div>
{{focus-textarea value=diagramText rows=30 id=editorId class="diagram-editor form-control mousetrap"}}
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="form-group">
<button type="button" class="btn btn-secondary" {{action 'onPreview'}}>{{previewButtonCaption}}</button>
<div id={{previewId}} class="text-center my-5">
<img src="{{diagramPreview}}" />
</div>
</div>
</div>
</div>
</div>
</div>
{{/section/base-editor}}

View file

@ -1,3 +1,3 @@
<div class="text-center">
{{{page.body}}}
<img src="{{page.body}}" />
</div>

View file

@ -50,7 +50,6 @@ module.exports = function (defaults) {
app.import('vendor/keycloak.js');
app.import('vendor/markdown-it.min.js');
app.import('vendor/md5.js');
app.import('vendor/mermaid.min.js');
app.import('vendor/moment.js');
app.import('vendor/mousetrap.js');
app.import('vendor/prism.js');

View file

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 359 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 630 B

After

Width:  |  Height:  |  Size: 630 B

Before After
Before After

File diff suppressed because one or more lines are too long