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

418 lines
330 KiB
Go
Raw Normal View History

2018-07-28 11:43:45 -04:00
// 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 document
import (
2018-07-28 15:30:33 -04:00
"database/sql"
2018-07-28 11:43:45 -04:00
"fmt"
"strings"
"time"
"github.com/documize/community/domain"
2018-07-28 17:15:16 -04:00
"github.com/documize/community/domain/permission"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/page"
"github.com/documize/community/model/workflow"
2018-07-28 11:43:45 -04:00
)
// exportSpec details what is being exported.
type exportSpec struct {
SpaceID string `json:"spaceId"`
FilterType string `json:"filterType"`
Data []string `json:"data"`
}
// exportTOC details the list of documents being exported.
type exportTOC struct {
ID string
Entry string
}
// BuildExport generates self-enclosed HTML for content specified.
func BuildExport(ctx domain.RequestContext, s domain.Store, spec exportSpec) (html string, err error) {
export := strings.Builder{}
2018-07-28 15:30:33 -04:00
content := strings.Builder{}
2018-07-28 11:43:45 -04:00
toc := []exportTOC{}
switch spec.FilterType {
case "space":
for _, spaceID := range spec.Data {
t, c, e := exportSpace(ctx, s, spaceID)
2018-07-28 15:47:14 -04:00
if e == nil {
content.WriteString(c)
toc = append(toc, t...)
2018-07-28 11:43:45 -04:00
}
}
case "category":
2018-07-28 17:15:16 -04:00
t, c, e := exportCategory(ctx, s, spec.SpaceID, spec.Data)
if e == nil {
content.WriteString(c)
toc = append(toc, t...)
}
2018-07-28 11:43:45 -04:00
case "document":
2018-07-28 17:15:16 -04:00
t, c, e := exportDocument(ctx, s, spec.SpaceID, spec.Data)
if e == nil {
content.WriteString(c)
toc = append(toc, t...)
}
2018-07-28 11:43:45 -04:00
}
// Generate export file header.
export.WriteString("<!DOCTYPE html>")
export.WriteString("<html")
export.WriteString("<head>")
export.WriteString(`<meta charset="utf-8">`)
export.WriteString(`<meta http-equiv="X-UA-Compatible" content="IE=edge">`)
export.WriteString("<title>")
export.WriteString("Documize Export")
export.WriteString("</title>")
export.WriteString("<style>")
export.WriteString(baseCSS)
export.WriteString("</style>")
export.WriteString("<style>")
export.WriteString(exportCSS)
export.WriteString("</style>")
export.WriteString("</head>")
export.WriteString("<body class='export-body'>")
2018-07-28 15:30:33 -04:00
// Show title and timestamp.
2018-07-28 11:43:45 -04:00
generated := time.Now().UTC().Format(time.ANSIC)
export.WriteString(fmt.Sprintf("<h1 class='export-h1'>%s</h1>", "Documize Export"))
export.WriteString(fmt.Sprintf("<div class='export-stamp'>%v</div>", generated))
2018-07-28 15:30:33 -04:00
// Spit out table of contents.
2018-07-28 17:15:16 -04:00
if len(toc) > 0 {
export.WriteString("<div class='export-toc'>")
for i, t := range toc {
export.WriteString(fmt.Sprintf("<a class='export-toc-entry' href='#%s'>%d. %s</a>", t.ID, i+1, t.Entry))
}
export.WriteString("</div>")
} else {
export.WriteString("<p>No documents found</p>")
2018-07-28 15:30:33 -04:00
}
// Write out content.
export.WriteString(content.String())
2018-07-28 11:43:45 -04:00
2018-07-28 15:30:33 -04:00
// Generate export file footer.
export.WriteString("</body>")
export.WriteString("</html>")
2018-07-28 11:43:45 -04:00
return export.String(), nil
}
2018-07-28 15:30:33 -04:00
// exportSpace returns documents exported.
2018-07-28 11:43:45 -04:00
func exportSpace(ctx domain.RequestContext, s domain.Store, spaceID string) (toc []exportTOC, export string, err error) {
2018-07-28 15:30:33 -04:00
// Permission check.
if !permission.CanViewSpace(ctx, s, spaceID) {
return toc, "", nil
}
// Get all documents for space.
docs, err := s.Document.GetBySpace(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
return toc, export, err
}
// Remove documents that cannot be seen due to lack of category view/access permission.
cats, err := s.Category.GetBySpace(ctx, spaceID)
members, err := s.Category.GetSpaceCategoryMembership(ctx, spaceID)
docs = FilterCategoryProtected(docs, cats, members, false)
// Keep the latest version when faced with multiple versions.
docs = FilterLastVersion(docs)
// Turn each document into TOC entry and HTML content export
b := strings.Builder{}
for _, d := range docs {
if d.Lifecycle == workflow.LifecycleLive {
2018-07-28 17:15:16 -04:00
docHTML, e := processDocument(ctx, s, d.RefID)
2018-07-28 15:30:33 -04:00
if e == nil && len(docHTML) > 0 {
toc = append(toc, exportTOC{ID: d.RefID, Entry: d.Title})
b.WriteString(docHTML)
} else {
return toc, b.String(), err
}
}
}
return toc, b.String(), nil
}
2018-07-28 17:15:16 -04:00
// exportCategory returns documents exported for selected categories.
func exportCategory(ctx domain.RequestContext, s domain.Store, spaceID string, category []string) (toc []exportTOC, export string, err error) {
// Permission check.
if !permission.CanViewSpace(ctx, s, spaceID) {
return toc, "", nil
}
// Get all documents for space.
docs, err := s.Document.GetBySpace(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
return toc, export, err
}
// Remove documents that cannot be seen due to lack of category view/access permission.
cats, err := s.Category.GetBySpace(ctx, spaceID)
members, err := s.Category.GetSpaceCategoryMembership(ctx, spaceID)
docs = FilterCategoryProtected(docs, cats, members, false)
// Keep the latest version when faced with multiple versions.
docs = FilterLastVersion(docs)
exportDocs := []doc.Document{}
// Process each requested category.
for _, categoryID := range category {
// Check to see if any documents has this category
for _, cm := range members {
// Save the document for export if it is in list of visible docs.
if cm.CategoryID == categoryID {
for _, d := range docs {
if d.RefID == cm.DocumentID {
exportDocs = append(exportDocs, d)
break
}
}
}
}
}
// Turn each document into TOC entry and HTML content export
b := strings.Builder{}
for _, d := range exportDocs {
if d.Lifecycle == workflow.LifecycleLive {
docHTML, e := processDocument(ctx, s, d.RefID)
if e == nil && len(docHTML) > 0 {
toc = append(toc, exportTOC{ID: d.RefID, Entry: d.Title})
b.WriteString(docHTML)
} else {
return toc, b.String(), err
}
}
}
return toc, b.String(), nil
}
// exportDocument returns documents for export.
func exportDocument(ctx domain.RequestContext, s domain.Store, spaceID string, document []string) (toc []exportTOC, export string, err error) {
// Permission check.
if !permission.CanViewSpace(ctx, s, spaceID) {
return toc, "", nil
}
// Get all documents for space.
docs, err := s.Document.GetBySpace(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
return toc, export, err
}
// Remove documents that cannot be seen due to lack of category view/access permission.
cats, err := s.Category.GetBySpace(ctx, spaceID)
members, err := s.Category.GetSpaceCategoryMembership(ctx, spaceID)
docs = FilterCategoryProtected(docs, cats, members, false)
// Keep the latest version when faced with multiple versions.
docs = FilterLastVersion(docs)
// Turn each document into TOC entry and HTML content export
b := strings.Builder{}
for _, documentID := range document {
for _, d := range docs {
2018-07-29 10:59:24 -04:00
if d.RefID == documentID {
if permission.CanViewDocument(ctx, s, d.RefID) {
docHTML, e := processDocument(ctx, s, d.RefID)
if e == nil && len(docHTML) > 0 {
toc = append(toc, exportTOC{ID: d.RefID, Entry: d.Title})
b.WriteString(docHTML)
} else {
return toc, b.String(), err
}
2018-07-28 17:15:16 -04:00
}
}
}
}
return toc, b.String(), nil
}
// processDocument writes out document as HTML content
func processDocument(ctx domain.RequestContext, s domain.Store, documentID string) (export string, err error) {
2018-07-28 15:30:33 -04:00
b := strings.Builder{}
// Permission check.
if !permission.CanViewDocument(ctx, s, documentID) {
return export, nil
}
// Get the document in question
doc, err := s.Document.Get(ctx, documentID)
if err != nil && err != sql.ErrNoRows {
return export, err
}
// Skip any document that is not live and published.
if doc.Lifecycle != workflow.LifecycleLive {
return export, nil
}
// Get published pages and new pages awaiting approval.
pages, err := s.Page.GetPages(ctx, documentID)
if err != nil && err != sql.ErrNoRows {
return export, err
}
if len(pages) == 0 {
pages = []page.Page{}
}
// Only show published pages
p := []page.Page{}
for _, page := range pages {
if page.Status == workflow.ChangePublished {
p = append(p, page)
}
}
// Attach section numbers
page.Numberize(p)
// Put out document name.
2018-07-28 15:47:14 -04:00
b.WriteString(fmt.Sprintf("<div class='export-doc-header' id='%s'>", doc.RefID))
b.WriteString("<div class='export-doc-title'>")
2018-07-28 15:30:33 -04:00
b.WriteString(doc.Title)
2018-07-28 15:47:14 -04:00
b.WriteString("</div>")
b.WriteString("<div class='export-doc-excerpt'>")
2018-07-28 15:30:33 -04:00
b.WriteString(doc.Excerpt)
2018-07-28 15:47:14 -04:00
b.WriteString("</div>")
b.WriteString("</div>")
2018-07-28 15:30:33 -04:00
// Construct HMTL.
for _, page := range p {
// Write out section header.
b.WriteString(`<div class="document-structure">`)
b.WriteString(`<div class="page-header">`)
b.WriteString(fmt.Sprintf("<span class='page-number'>%s</span>", page.Numbering))
b.WriteString(fmt.Sprintf("<span class='page-title'>%s</span>", page.Title))
b.WriteString("</div>")
b.WriteString("</div>")
// Process seciton content before writing out as HTML.
section := page.Body
if page.ContentType == "plantuml" {
section = fmt.Sprintf(`<img src="%s" />`, page.Body)
}
2018-07-28 15:30:33 -04:00
// Write out section content
b.WriteString(`<div class="wysiwyg">`)
b.WriteString(section)
2018-07-28 15:30:33 -04:00
b.WriteString("</div>")
}
return b.String(), nil
2018-07-28 11:43:45 -04:00
}
2018-07-28 15:30:33 -04:00
// CSS injected into self-enclosed HTML file export.
2018-07-28 11:43:45 -04:00
const (
2018-07-28 15:30:33 -04:00
// Styles specific to export process.
2018-07-28 11:43:45 -04:00
exportCSS = `
.export-body {
margin: 30px 20px;
}
.export-h1 {
color: #4c4c4c;
font-size: 2rem;
font-weight: bold;
margin: 0 0 10px 0;
padding: 0;
}
.export-stamp {
2018-07-28 17:35:29 -04:00
color: #8b9096;
2018-07-28 11:43:45 -04:00
font-size: 1.2rem;
font-weight: normal;
margin: 0 0 20px 0;
}
2018-07-28 15:30:33 -04:00
.export-toc {
padding: 20px 30px;
margin: 10px 0;
background-color: #fff8dc;
border: 2px solid #E5DFC6;
border-radius: 3px;
}
.export-toc .export-toc-entry {
display: block;
margin: 3px 0;
color: #348A37;
font-size: 1.1rem;
text-decoration: none;
}
.export-toc .export-toc-entry:hover {
text-decoration: underline;
}
2018-07-28 15:47:14 -04:00
.export-doc-header {
2018-07-28 15:30:33 -04:00
padding: 20px 30px;
2018-07-28 15:47:14 -04:00
margin: 70px 0 30px 0;
2018-07-28 15:30:33 -04:00
background-color: #F7F2FF;
border: 2px solid #280A42;
border-radius: 3px;
}
2018-07-28 15:47:14 -04:00
.export-doc-title {
2018-07-28 15:30:33 -04:00
color: #280A42;
font-size: 2rem;
font-weight; bold;
margin: 0 0 5px 0;
}
2018-07-28 15:47:14 -04:00
.export-doc-excerpt {
2018-07-28 15:30:33 -04:00
color: #280A42;
font-size: 1.1rem;
font-weight; normal;
}
2018-07-28 15:47:14 -04:00
.document-structure {
margin: 20px 0 !important;
}
2018-07-28 11:43:45 -04:00
`
2018-07-28 15:30:33 -04:00
// Styles copied from minified production CSS assets.
// vendor.css followed by documize.css.
2018-07-28 11:43:45 -04:00
baseCSS = `
.material-icons,html{text-rendering:optimizeLegibility}.CodeMirror pre,html{-webkit-tap-highlight-color:transparent}.material-icons,a,html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a a:focus,a a:hover,a.admin-link,a.admin-link a:focus,a.admin-link a:hover{text-decoration:none}.cursor-pointer,a{cursor:pointer}body,html{height:100%}.modal,.modal-open,svg:not(:root){overflow:hidden}body,caption{text-align:left}button,hr,input{overflow:visible}pre,textarea{overflow:auto}article,aside,figcaption,figure,footer,header,hgroup,legend,main,nav,section{display:block}address,legend{line-height:inherit}progress,sub,sup{vertical-align:baseline}label,output{display:inline-block}.dropdown-menu,ul li{list-style:none}.color-white{color:#fff!important}.color-off-white{color:#f5f5f5!important}.color-black{color:#000!important}.color-off-black{color:#111!important}.color-primary,.view-customize .user-table .admin-user{color:#280A42!important}.color-link{color:#348A37!important}.color-blue{color:#2667af!important}.color-red,.view-customize .user-table .inactive-user{color:#9E0D1F!important}.color-green{color:#348A37!important}.color-gray{color:#8b9096!important}.color-gold{color:gold!important}.color-orange{color:#FFAD15!important}.color-whats-new{color:#fc1530!important}.background-color-white{background-color:#fff!important}.background-color-off-white{background-color:#f5f5f5!important}.background-color-off-black{background-color:#111!important}.background-color-green{background-color:#348A37!important}.background-color-theme{background-color:#280A42!important}.background-color-theme-light{background-color:#F7F2FF!important}.background-color-gold{background-color:gold!important}.font-fixed-width{font-family:'courier new',courier}@font-face{font-family:"Material Icons";font-style:normal;font-weight:400;src:url(font/icons/MaterialIcons-Regular.eot);src:local("Material Icons"),local("MaterialIcons-Regular"),url(font/icons/MaterialIcons-Regular.woff2) format("woff2"),url(font/icons/MaterialIcons-Regular.woff) format("woff"),url(font/icons/MaterialIcons-Regular.ttf) format("truetype")}html{width:100%;overflow-y:scroll;font-size:.875rem;font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar}.popover,.tooltip,body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"}a{-webkit-transition:all .3s ease-in-out;-moz-transition:all .3s ease-in-out;-ms-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}a.admin-link{font-weight:700}a.broken-link{color:#9E0D1F;text-decoration:line-through}.no-outline,.section-markdown-preview,.wysiwyg-editor{outline:0!important;border:none!important;box-shadow:none!important}.no-outline:active,.no-outline:focus,.section-markdown-preview:active,.section-markdown-preview:focus,.wysiwyg-editor:active,.wysiwyg-editor:focus{border:none!important;box-shadow:none!important}.basic-table,.bordered{border:1px solid #d3d3d3}.button-gap,.document-sidebar>.document-toc>.index-list .item,.no-select,.start-section,.view-dashboard>.list>.view-toggle,.view-dashboard>.section-title>.filter-summary{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}ul{margin:0;padding:0}.cursor-auto{cursor:auto!important}.no-width{white-space:nowrap;width:1%}.absolute-center,.onboarding-container .rightbar{margin:auto;position:absolute;top:0;left:0;bottom:0;right:0}input:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px #fff inset;box-shadow:0 0 0 1000px #fff inset}.btn.focus,.btn:focus,.form-control:focus{box-shadow:0 0 0 .2rem rgba(40,10,66,.25)}::-webkit-scrollbar{width:7px}::-webkit-scrollbar-track{background:#f5f5f5}::-webkit-scrollbar-thumb{background:#d8d8d8}::-webkit-scrollbar-thumb:hover{background:#8b9096}.btn-outline-danger,.btn-outline-dark,.btn-outline-info,.btn-outline-light,.btn-outline-primary,.btn-
* animate.css -http://daneden.me/animate
* Version - 3.6.0
* Licensed under the MIT license - http://opensource.org/licenses/MIT
*
* Copyright (c) 2018 Daniel Eden
*/.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}@-webkit-keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}@keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);transform:translateZ(0)}40%,43%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06);-webkit-transform:translate3d(0,-30px,0);transform:translate3d(0,-30px,0)}70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);animation-timing-function:cubic-bezier(.755,.05,.855,.06);-webkit-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}.bounce{-webkit-animation-name:bounce;animation-name:bounce;-webkit-transform-origin:center bottom;transform-origin:center bottom}@-webkit-keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;animation-name:flash}@-webkit-keyframes pulse{0%,to{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}}@keyframes pulse{0%,to{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rubberBand{0%,to{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}}@keyframes rubberBand{0%,to{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}}.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand}@-webkit-keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.shake{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:tr
* OverlayScrollbars
* https://github.com/KingSora/OverlayScrollbars
*
* Version: 1.4.5
*
* Copyright KingSora.
* https://github.com/KingSora
*
* Released under the MIT license.
* Date: 18.05.2018
*/html.os-html,html.os-html>.os-host{display:block;overflow:hidden;box-sizing:border-box;height:100%!important;width:100%!important;min-width:100%!important;min-height:100%!important;margin:0!important;position:absolute!important}.os-padding,.os-viewport{top:0;left:0;right:0;margin:0;padding:0;bottom:0}html.os-html>.os-host>.os-padding{position:absolute}body.os-dragging,body.os-dragging *{cursor:default}.os-host,.os-host-textarea{position:relative;overflow:visible!important}#hs-dummy-scrollbar-size{position:fixed;opacity:0;-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=0)';visibility:hidden;overflow:scroll;height:500px;width:500px}#hs-dummy-scrollbar-size,.os-viewport{-ms-overflow-style:scrollbar!important}.os-viewport-native-scrollbars-invisible#hs-dummy-scrollbar-size::-webkit-scrollbar,.os-viewport-native-scrollbars-invisible#hs-dummy-scrollbar-size::-webkit-scrollbar-corner,.os-viewport-native-scrollbars-invisible.os-viewport::-webkit-scrollbar,.os-viewport-native-scrollbars-invisible.os-viewport::-webkit-scrollbar-corner{display:none!important;width:0!important;height:0!important;visibility:hidden!important;background:0 0!important}.os-content-glue{max-height:100%;max-width:100%;width:100%;pointer-events:none}.os-padding{direction:inherit;position:absolute;overflow:visible;width:auto!important;height:auto!important;z-index:1}.os-host-overflow>.os-padding,.os-viewport{overflow:hidden}.os-viewport{direction:inherit!important;box-sizing:inherit!important;resize:none!important;position:absolute;-webkit-overflow-scrolling:touch}.os-content>.os-textarea,.os-host-textarea>.os-padding>.os-content{overflow:hidden!important}.os-content-arrange{position:absolute;z-index:-1;min-height:1px;min-width:1px;pointer-events:none}.os-content{direction:inherit;box-sizing:border-box!important;position:relative;display:block;height:100%;width:100%;visibility:visible}.os-content>.os-textarea{direction:inherit!important;float:none!important;margin:0!important;max-height:none!important;max-width:none!important;border:none!important;border-radius:0!important;background:0 0!important;outline:transparent 0!important;resize:none!important;position:absolute!important;top:0!important;left:0!important;z-index:1;padding:0}.os-host-rtl>.os-padding>.os-viewport>.os-content>.os-textarea{right:0!important}.os-content>.os-textarea-cover{z-index:-1;pointer-events:none}.os-content>.os-textarea[wrap=off]{white-space:pre!important;margin:0!important}.os-text-inherit{font-family:inherit;font-size:inherit;font-weight:inherit;font-style:inherit;font-variant:inherit;text-transform:inherit;text-decoration:inherit;text-indent:inherit;text-align:inherit;text-shadow:inherit;text-overflow:inherit;letter-spacing:inherit;word-spacing:inherit;line-height:inherit;unicode-bidi:inherit;direction:inherit;color:inherit;cursor:text}.os-resize-observer,.os-resize-observer-host{box-sizing:inherit;display:block;opacity:0;position:absolute;top:0;left:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1}.os-resize-observer-host{padding:inherit;border:solid inherit;box-sizing:border-box}.os-resize-observer-host:after{content:''}.os-resize-observer-host:after,.os-resize-observer-host>.os-resize-observer{height:200%;width:200%;padding:inherit;border:inherit;margin:0;display:block;box-sizing:content-box}.os-resize-observer.observed,object.os-resize-observer{box-sizing:border-box!important}.os-size-auto-observer{box-sizing:inherit!important;height:100%;width:inherit;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none}.os-size-auto-observer>.os-resize-observer{width:1000%;height:1000%;min-height:1px;min-width:1px}.os-resize-observer-item{position:absolute;top:0;right:0;bottom:0;left:0;overflow:hidden;z-index:-1;opacity:0;direction:ltr!important;-webkit-box-flex:0!important;-ms-flex:none!important;flex:none!important}.os-resize-observer-item-final{position:absolute;left:0;top:0;-webkit-transition:none!important;transition:none!important;-webkit-box-flex:0!important;-ms-flex:none!i
font-size: 12px;
color: #111111; }
@bottom-left {
font-size: 12px;
color: #111111; }
@bottom-right {
font-size: 12px;
color: #111111; };}body,html{background-color:transparent!important;max-width:none!important;float:none!important;position:relative!important;height:initial!important;min-height:initial!important;-webkit-print-color-adjust:exact;overflow:hidden;margin:0!important;padding:0!important;min-width:768px!important}#nav-bar,#sidebar,#sub-nav,#top-bar,.document-toc,.document-toolbar,.new-section-wizard,.non-printable,.page-toolbar,.start-button,.start-section,.tabnav-control,footer,header{float:none!important;display:none!important;margin:0!important;padding:0!important;width:0!important;z-index:0!important}.non-printable-message,.print-title{display:block!important}.document-heading{text-align:left!important;padding:0!important;margin-bottom:40px!important;color:#000}.container,.doc-excerpt,.doc-title,.layout-body,.layout-content{padding:0!important;margin:0!important}.doc-title{font-size:1.5rem!important;color:#000!important}.doc-excerpt{color:#464545!important;font-size:1.2rem!important}.page-title,.wysiwyg,.wysiwyg h1,.wysiwyg h2,.wysiwyg h3,.wysiwyg h4{color:#000!important}.wysiwyg{font-size:15px!important;line-height:22px!important}.wysiwyg h1{font-size:22px}.wysiwyg h2{font-size:20px}.wysiwyg h3{font-size:18px}.wysiwyg h4{font-size:17px}.wysiwyg h5,.wysiwyg h6{font-size:16px;color:#000!important}.page-title{font-size:20px!important}.container{width:auto!important;min-width:100%!important}[class*=col-]{float:left!important;width:100%!important}.layout-body,.layout-content{display:block!important;flex:none!important;width:auto!important;max-width:auto!important}}.product-update{text-align:left;margin:50px 0}.product-update>.update-summary{padding:25px;border:1px solid #FFAD15;background-color:#f5f5f5;border-radius:2px}.update-available-dot,.whats-new-dot{border-radius:10px;width:10px;height:10px;position:absolute;right:0}.product-update>.update-summary>.caption{font-weight:700;font-size:1.5rem;color:#FFAD15;margin-bottom:15px;display:inline-block}.product-update>.update-summary>.instructions{font-weight:400;font-size:1.3rem;color:#8b9096}.product-update>.update-summary>.version{margin:30px 0 0 20px;font-size:1.3rem;color:#8b9096;font-weight:700}.product-update>.update-summary>.changes{margin:10px 0 0 40px}.product-update>.update-summary>.changes>li{list-style-type:disc;padding:5px 0;font-size:1.2rem;color:#000}.product-update>.update-summary>.changes>li>.tag-edition{margin:10px;padding:5px 10px;background-color:#d8d8d8;color:#280A42;font-weight:700;font-size:.9rem}.product-about{text-align:center;margin:30px}.product-about>.edition{font-weight:400;font-size:1.5rem;color:#000;margin-bottom:5px}.product-about>.version{font-weight:700;font-size:1.1rem;color:#8b9096;margin-bottom:20px}.product-about>.dotcom{font-weight:700;font-size:1.2rem;color:#348A37;margin-bottom:40px}.product-about>.copyright{text-align:center;font-weight:400;font-size:1rem;color:#111;margin-bottom:20px}.product-about>.license{text-align:left;font-weight:400;font-size:1rem;color:#8b9096}.update-available-dot{background-color:#FFAD15;bottom:0}.whats-new-dot{background-color:#fc1530;top:0}.product-news{text-align:left;margin:0 30px}.product-news>h2{margin:0 0 10px;text-align:center;font-size:2rem;color:#111}.product-news>.news-item{padding:30px 0;border-bottom:1px solid #d3d3d3;text-align:center}.product-news>.news-item>.title{color:#280A42;font-size:1.5rem;font-weight:700;margin-bottom:5px}.product-news>.news-item>.date{color:#8b9096;font-size:1rem;font-weight:600;margin-bottom:10px}.product-news>.news-item>.info{color:#000;font-size:1.1rem;font-weight:400;margin-top:15px}.product-news>.news-item>.tag-edition{margin:10px;padding:5px 10px;background-color:#f5f5f5;color:#8b9096;font-weight:700;font-size:.9rem;display:inline-block}.product-news>.news-item>img{max-width:450px;max-height:350px}.product-news>.action{margin:20px 0;text-align:center;color:#8b9096;font-weight:800;font-size:1.3rem}.section-trello-board{width:100%;padding:10px;white-space:nowrap;overflow:auto}.section-trello-board-title{font-weight:700;color:#fff;font-size:16px}.section-trel
.x-toggle{display:none}.x-toggle,.x-toggle *,.x-toggle ::after,.x-toggle ::before,.x-toggle+label>.x-toggle-btn,.x-toggle::after,.x-toggle::before{-moz-box-sizing:border-box;box-sizing:border-box}.x-toggle ::-moz-selection,.x-toggle ::after::-moz-selection,.x-toggle ::before::-moz-selection,.x-toggle+label>.x-toggle-btn::-moz-selection,.x-toggle::-moz-selection,.x-toggle::after::-moz-selection,.x-toggle::before::-moz-selection{background:0 0}.x-toggle ::after::selection,.x-toggle ::before::selection,.x-toggle ::selection,.x-toggle+label>.x-toggle-btn::selection,.x-toggle::after::selection,.x-toggle::before::selection,.x-toggle::selection{background:0 0}label>.x-toggle-btn.x-toggle-disabled{cursor:not-allowed}label>.x-toggle-btn{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;outline:0;display:flex;flex-basis:4em;height:2em;position:relative;cursor:pointer}label>.x-toggle-btn::after,label>.x-toggle-btn::before{position:relative;display:block;content:"";width:50%;height:100%}label>.x-toggle-btn::after{left:0}label>.x-toggle-btn::before{display:none}.x-toggle:checked+label>.x-toggle-btn::after{left:50%}.x-toggle-component{display:flex;justify-content:center;align-items:center}.x-toggle-container{display:flex;flex-wrap:nowrap;justify-content:center;height:auto;padding:0 .35rem}.x-toggle-component .toggle-text{display:flex;cursor:pointer}.x-toggle-container.small{width:2.75rem;font-size:1rem;padding:0 .25rem}.x-toggle-container.medium{width:3.75rem;font-size:1rem}.x-toggle-container.large{width:5.7rem;font-size:1.2rem;padding:0 .5rem}.x-toggle-container label{min-width:100%;max-width:100%}.x-toggle-container .toggle-text.toggle-prefix{padding-right:.25rem}.x-toggle-container .toggle-text.toggle-postfix{padding-left:.25rem}.x-toggle-component label.off-label{padding-right:.5rem}.x-toggle-component label.on-label{padding-left:.5rem}.x-toggle-light.x-toggle-btn{background:#f0f0f0;border-radius:2em;padding:2px;-webkit-transition:all .4s ease;transition:all .4s ease}.x-toggle-light.x-toggle-btn::after{border-radius:50%;background:#fff;-webkit-transition:all .2s ease;transition:all .2s ease}.x-toggle:checked+label>.x-toggle-light.x-toggle-btn{background:#9fd6ae}.x-toggle-light.small{width:3em;height:1.6em}.x-toggle-light.medium{width:4em;height:2.1em;padding:3px}.x-toggle-light.large{width:4.7em;height:2.1em;padding:4px}.x-toggle-ios.x-toggle-btn{background:#fbfbfb;border-radius:2em;padding:2px;-webkit-transition:all .4s ease;transition:all .4s ease;border:1px solid #e8eae9}.x-toggle-ios.x-toggle-btn::after{border-radius:2em;background:#fbfbfb;-webkit-transition:left .3s cubic-bezier(.175,.885,.32,1.275),padding .3s ease,margin .3s ease;transition:left .3s cubic-bezier(.175,.885,.32,1.275),padding .3s ease,margin .3s ease;box-shadow:0 0 0 1px rgba(0,0,0,.1),0 4px 0 rgba(0,0,0,.08)}.x-toggle-ios.x-toggle-btn:active{box-shadow:inset 0 0 0 2em #e8eae9}.x-toggle-ios.x-toggle-btn:active::after{padding-right:.8em}.x-toggle:checked+label>.x-toggle-ios.x-toggle-btn{background:#86d993}.x-toggle:checked+label>.x-toggle-ios.x-toggle-btn:active{box-shadow:none}.x-toggle:checked+label>.x-toggle-ios.x-toggle-btn:active::after{margin-left:-.8em}.x-toggle-ios.small{width:3em;height:1.6em}.x-toggle-ios.medium{width:4em;height:2.1em;padding:3px}.x-toggle-ios.large{width:4.7em;height:2.1em;padding:4px}.x-toggle-flip.x-toggle-btn{padding:2px;-webkit-transition:all .2s ease;transition:all .2s ease;font-family:sans-serif;-webkit-perspective:100px;perspective:100px}.x-toggle-flip.x-toggle-btn::after,.x-toggle-flip.x-toggle-btn::before{display:inline-block;-webkit-transition:all .4s ease;transition:all .4s ease;width:100%;text-align:center;position:absolute;line-height:2em;font-weight:700;color:#fff;top:0;left:0;-webkit-backface-visibility:hidden;backface-visibility:hidden;border-radius:4px}.x-toggle-flip.x-toggle-btn::after{content:attr(data-tg-on);background:#02c66f;-webkit-transform:rotateY(-180deg);transform:rotateY(-180deg)}.x-toggle-flip.x-toggle-btn::before{background:#ff3a19;content:attr(data-tg-off)}
`
)