Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F3427544
D25962.1744652532.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
15 KB
Referenced Files
None
Subscribers
None
D25962.1744652532.diff
View Options
diff --git a/rsrc/remarkup-image.css b/rsrc/remarkup-image.css
--- a/rsrc/remarkup-image.css
+++ b/rsrc/remarkup-image.css
@@ -2,7 +2,62 @@
* @provides diagram-remarkup-image-css
*/
-.diagram-container > .diagram-content {
+.diagram-content {
+ display: flex;
+}
+
+.diagram-content.diagram-center {
+ justify-content: center;
+}
+
+.diagram-content.diagram-right {
+ justify-content: right;
+}
+
+.diagram-content.diagram-float-left {
+ float: left;
+ margin-right: 1em;
max-width: 100%;
- cursor: pointer;
+}
+
+.diagram-content.diagram-float-right {
+ float: right;
+ margin-left: 1em;
+ max-width: 100%;
+}
+
+.diagram-content > .diagram-container {
+ max-width: 100%;
+ overflow: hidden !important;
+}
+
+.diagram-content > .diagram-container.full {
+ flex-grow: 1;
+}
+
+/* GraphViewer is only rendering when container has width */
+.diagram-content > .diagram-container:empty {
+ min-width: 1px;
+}
+
+/* Fixing images in GraphViewer lightbox toolbar */
+.geDiagramContainer + .geAdaptiveAsset + div > span > img {
+ display: initial;
+}
+
+/* Fixing centering of button in GraphViewer toolbar */
+body > div[style*="align-items:"]:not([class]) {
+ line-height: initial;
+ display: flex;
+}
+
+/* reset some styles for correct svg rendering */
+
+.diagram-content > .diagram-container, .geDiagramContainer {
+ line-height: initial;
+}
+
+.diagram-content > .diagram-container td,
+.geDiagramContainer td {
+ padding: revert-layer;
}
diff --git a/rsrc/remarkup-image.js b/rsrc/remarkup-image.js
--- a/rsrc/remarkup-image.js
+++ b/rsrc/remarkup-image.js
@@ -4,24 +4,58 @@
JX.onload(function() {
- var singleClickTimeout = null;
-
- JX.Stratcom.listen(
- 'click',
- ['diagram-remarkup-image'],
- function(evt) {
- var detail = evt.getRawEvent().detail;
-
- if (detail === 1) {
- singleClickTimeout = window.setTimeout(function() {
- window.open('/diagram/data/' + evt.getTarget().dataset.diagramVersion);
- singleClickTimeout = null;
- }, 300);
- } else if (detail === 2) {
- window.clearTimeout(singleClickTimeout);
- window.open('/diagram/DIAG' + evt.getTarget().dataset.diagramId);
+ var viewerScriptURI = 'https://viewer.diagrams.net/js/viewer-static.min.js';
+ var viewerScriptPrepended = false;
+ var viewerScriptBlocked = false;
+
+ document.addEventListener('securitypolicyviolation', function(e) {
+ if (viewerScriptURI === e.blockedURI) {
+ viewerScriptBlocked = true;
+ processElements();
+ }
+ });
+
+ function processViewer(viewer) {
+ viewer.toolbar.appendChild(
+ viewer.createToolbarButton(
+ function() { window.open(viewer.graphConfig.edit); },
+ 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMThweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMThweCIgZmlsbD0iIzAwMDAwMCI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTE0LjA2IDkuMDJsLjkyLjkyTDUuOTIgMTlINXYtLjkybDkuMDYtOS4wNk0xNy42NiAzYy0uMjUgMC0uNTEuMS0uNy4yOWwtMS44MyAxLjgzIDMuNzUgMy43NSAxLjgzLTEuODNjLjM5LS4zOS4zOS0xLjAyIDAtMS40MWwtMi4zNC0yLjM0Yy0uMi0uMi0uNDUtLjI5LS43MS0uMjl6bS0zLjYgMy4xOUwzIDE3LjI1VjIxaDMuNzVMMTcuODEgOS45NGwtMy43NS0zLjc1eiIvPjwvc3ZnPg==',
+ null,
+ true
+ )
+ );
+ }
+
+ function processElements() {
+ console.log('processElements: ', document.querySelectorAll('.diagram-container:empty'));
+ document.querySelectorAll('.diagram-container:empty').forEach(function(container) {
+ if (window.GraphViewer) {
+ GraphViewer.createViewerForElement(container, processViewer);
+ } else if (viewerScriptBlocked) {
+ container.innerHTML = '<em>Diagram Viewer could not be loaded.</em>';
}
+ });
+ }
+
+ function init() {
+ if (!document.querySelector('.diagram-container')) {
+ return;
}
- );
+ if (viewerScriptPrepended || viewerScriptBlocked) {
+ processElements();
+ return;
+ }
+
+ var viewerScript = document.createElement('script');
+ viewerScript.setAttribute('src', viewerScriptURI);
+ viewerScript.addEventListener('load', processElements);
+
+ document.body.prepend(viewerScript);
+
+ viewerScriptPrepended = true;
+ }
+
+ init();
+ JX.Request.listen('done', function() { setTimeout(init); });
});
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -26,6 +26,7 @@
'DiagramTransactionType' => 'xaction/DiagramTransactionType.php',
'DiagramUploadConduitAPIMethod' => 'conduit/DiagramUploadConduitAPIMethod.php',
'DiagramVersion' => 'storage/DiagramVersion.php',
+ 'DrawioPngParser' => 'parser/DrawioPngParser.php',
'PhabricatorDiagramQuery' => 'query/PhabricatorDiagramQuery.php',
'PhabricatorDiagramTransactionQuery' => 'query/PhabricatorDiagramTransactionQuery.php',
'PhabricatorDiagramVersionQuery' => 'query/PhabricatorDiagramVersionQuery.php',
@@ -61,6 +62,7 @@
'PhabricatorDestructibleInterface',
'PhabricatorPolicyInterface',
),
+ 'DrawioPngParser' => 'Phobject',
'PhabricatorDiagramQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorDiagramTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorDiagramVersionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
diff --git a/src/application/DiagramApplication.php b/src/application/DiagramApplication.php
--- a/src/application/DiagramApplication.php
+++ b/src/application/DiagramApplication.php
@@ -1,6 +1,11 @@
<?php
final class DiagramApplication extends PhabricatorApplication {
+ public function __construct() {
+ CelerityAPI::getStaticResourceResponse()
+ ->addContentSecurityPolicyURI('script-src', 'https://viewer.diagrams.net');
+ }
+
public function getName() {
return pht('Diagrams');
}
diff --git a/src/celerity/map.php b/src/celerity/map.php
--- a/src/celerity/map.php
+++ b/src/celerity/map.php
@@ -11,15 +11,15 @@
'diagram-extension.css' => '7ad39d5d',
'iframe-toolbtn.css' => '35ad6f49',
'iframe-toolbtn.js' => '26d75a35',
- 'remarkup-image.css' => '42b46bf1',
- 'remarkup-image.js' => '64e7e9e1',
+ 'remarkup-image.css' => '51ddb637',
+ 'remarkup-image.js' => '842accb1',
),
'symbols' => array(
'diagram-css-extension' => '7ad39d5d',
'diagram-css-iframe-toolbtn' => '35ad6f49',
'diagram-js-iframe-toolbtn' => '26d75a35',
- 'diagram-remarkup-image-css' => '42b46bf1',
- 'diagram-remarkup-image-js' => '64e7e9e1',
+ 'diagram-remarkup-image-css' => '51ddb637',
+ 'diagram-remarkup-image-js' => '842accb1',
'javelin-behavior-diagram-extension' => 'a0b36dca',
),
'requires' => array(),
diff --git a/src/controller/DiagramController.php b/src/controller/DiagramController.php
--- a/src/controller/DiagramController.php
+++ b/src/controller/DiagramController.php
@@ -232,53 +232,19 @@
* They are cut out before the 2 strings are compared.
*/
public static function equalPngMetaData($base64_1, $base64_2) {
- $base64 = array($base64_1, $base64_2);
- $textData = array();
- for ($i = 0; $i < 2; $i++) {
- $data = base64_decode($base64[$i]);
- $fp = fopen('data://text/plain;base64,' . base64_encode($data), 'rb');
- $sig = fread($fp, 8);
- if ($sig != "\x89PNG\x0d\x0a\x1a\x0a") {
- fclose($fp);
- return false;
- }
- $textData[$i] = array();
- while (!feof($fp)) {
- try {
- $chunk = unpack('Nlength/a4type', fread($fp, 8));
- } catch (Exception $e) {
- // invalid base64 data
- return false;
- }
- if ($chunk['type'] == 'IEND') break;
- if ($chunk['type'] == 'tEXt') {
- list($key, $val) = explode("\0", fread($fp, $chunk['length']));
- if ($key == 'mxfile') {
- // Decode the URL-encoded XML data
- $decodedVal = urldecode($val);
- // Load the XML and remove the modified and etag attributes
- $xml = simplexml_load_string($decodedVal);
- unset($xml->attributes()->modified);
- unset($xml->attributes()->etag);
- // Save the modified XML as the value
- $val = $xml->asXML();
- }
- $textData[$i][$key] = $val;
- fseek($fp, 4, SEEK_CUR);
- } else {
- fseek($fp, $chunk['length'] + 4, SEEK_CUR);
- }
- }
- fclose($fp);
- }
+ $mxfile1 = DrawioPngParser::getMxfile($base64_1);
+ $mxfile2 = DrawioPngParser::getMxfile($base64_2);
- if (isset($textData[0]['mxfile']) && isset($textData[1]['mxfile'])) {
- // Both arrays contain the mxfile key, compare their values
- return $textData[0]['mxfile'] == $textData[1]['mxfile'];
- } else {
- // At least one of the arrays doesn't contain mxfile key, return false
+ if ($mxfile1 === null || $mxfile2 === null) {
return false;
}
+
+ unset($mxfile1->attributes()->modified);
+ unset($mxfile1->attributes()->etag);
+ unset($mxfile2->attributes()->modified);
+ unset($mxfile2->attributes()->etag);
+
+ return $mxfile1->asXML() == $mxfile2->asXML();
}
/**
diff --git a/src/parser/DrawioPngParser.php b/src/parser/DrawioPngParser.php
new file mode 100644
--- /dev/null
+++ b/src/parser/DrawioPngParser.php
@@ -0,0 +1,38 @@
+<?php
+
+class DrawioPngParser extends Phobject {
+
+ static public function getMxfile($base64Data) {
+ $fp = fopen('data://text/plain;base64,' . $base64Data, 'rb');
+ $sig = fread($fp, 8);
+
+ if ($sig != "\x89PNG\x0d\x0a\x1a\x0a") {
+ fclose($fp);
+ return null;
+ }
+
+ while (!feof($fp)) {
+ try {
+ $chunk = unpack('Nlength/a4type', fread($fp, 8));
+ } catch (Exception $e) {
+ // invalid base64 data
+ return null;
+ }
+ if ($chunk['type'] == 'IEND') break;
+ if ($chunk['type'] == 'tEXt') {
+ list($key, $val) = explode("\0", fread($fp, $chunk['length']));
+ if ($key == 'mxfile') {
+ fclose($fp);
+ // Decode the URL-encoded XML data
+ return simplexml_load_string(urldecode($val), null, LIBXML_NOBLANKS);
+ }
+ fseek($fp, 4, SEEK_CUR);
+ } else {
+ fseek($fp, $chunk['length'] + 4, SEEK_CUR);
+ }
+ }
+
+ fclose($fp);
+ return null;
+ }
+}
diff --git a/src/remarkup/PhabricatorRemarkupDiagramRule.php b/src/remarkup/PhabricatorRemarkupDiagramRule.php
--- a/src/remarkup/PhabricatorRemarkupDiagramRule.php
+++ b/src/remarkup/PhabricatorRemarkupDiagramRule.php
@@ -11,10 +11,6 @@
return 'DIAG';
}
- public function getRuleVersion() {
- return '1.0';
- }
-
protected function loadObjects(array $ids) {
$viewer = $this->getEngine()->getConfig('viewer');
@@ -31,70 +27,66 @@
PhabricatorObjectHandle $handle,
$options) {
- if ($options) {
- $params = explode(',', $options);
- $params = array_map('trim', $params);
- } else {
- $params = array();
+ $options = $this->getOptions($options);
+ $fullSize = !!$options['full'];
+ $contentClass = 'diagram-content';
+ $contentStyle = '';
+
+ switch ($options['layout'] && !$fullSize) {
+ case 'right':
+ case 'center':
+ $contentClass .= ' diagram-' . $options['layout'];
+ break;
+ case 'left':
+ default:
+ $contentClass .= ' diagram-left';
+ break;
}
- // Generate the appropriate HTML using the data from the Diagram and
- // file objects.
- $style = '';
- $class = 'diagram-content';
- $alt = '';
-
- $has_layout = false;
- foreach ($params as $param) {
- if (strpos($param, '=') !== false) {
- list($key, $value) = explode('=', $param, 2);
- } else {
- $key = $param;
- $value = null;
- }
- switch ($key) {
- case 'layout':
- $has_layout = true;
- if ($value === 'left') {
- $class .= ' phabricator-remarkup-embed-layout-left';
- } else if ($value === 'right') {
- $class .= ' phabricator-remarkup-embed-layout-right';
- }
+ if ($options['float'] && !$fullSize) {
+ switch ($options['layout']) {
+ case 'right':
+ $contentClass .= ' diagram-float-right';
break;
- case 'float':
- $class .= ' phabricator-remarkup-embed-float-left';
- break;
- case 'size':
- if ($value === 'full') {
- $style .= 'width: 100%;';
- }
- break;
- case 'alt':
- $alt = phutil_escape_html($value);
+ case 'left':
+ default:
+ $contentClass .= ' diagram-float-left';
break;
}
}
- if ($has_layout == false) {
- $class .= ' phabricator-remarkup-embed-layout-left';
+ $width = $options['width'];
+ if ($width && !$fullSize && preg_match('/^(?:\d*\\.)?\d+(%|px)?$/', $width)) {
+ if (is_numeric($width)) {
+ $contentStyle = 'width: ' . $width . 'px;';
+ } else {
+ $contentStyle = 'width: ' . $width . ';';
+ }
}
+ $mxGraphConfig = array(
+ 'responsive' => $fullSize,
+ 'resize' => true,
+ 'editable' => true,
+ 'edit' => PhabricatorEnv::getURI('/diagram/DIAG' . $diagram->getDiagramID()),
+ 'lightbox' => true,
+ 'toolbar' => 'pages' . ($fullSize ? '' : ' zoom'),
+ 'toolbar-position' => 'inline',
+ 'page' => $options['page'],
+ 'xml' => DrawioPngParser::getMxfile($diagram->getBase64Data())->asXML()
+ );
+
$output = phutil_tag(
'div',
array(
- 'class' => 'diagram-container',
+ 'class' => $contentClass,
+ 'style' => $contentStyle,
),
phutil_tag(
- 'img',
+ 'div',
array(
- 'style' => $style,
- 'class' => $class,
- 'src' => 'data:image/png;base64,' . $diagram->getBase64Data(),
- 'alt' => $alt,
- 'data-sigil' => 'diagram-remarkup-image',
- 'data-diagram-version' => $diagram->getPHID(),
- 'data-diagram-id' => $diagram->getDiagramID(),
- 'title' => 'Double click to edit...'
+ 'class' => 'diagram-container' . ($fullSize ? ' full' : ''),
+ 'data-mxgraph' => json_encode($mxGraphConfig)
)
)
);
@@ -102,6 +94,24 @@
return $output;
}
+ private function getOptions($option_string) {
+ $options = array(
+ 'full' => false,
+ 'layout' => 'left',
+ 'float' => false,
+ 'width' => '',
+ 'page' => 0
+ );
+
+ if ($option_string) {
+ $option_string = trim($option_string, ', ');
+ $parser = new PhutilSimpleOptions();
+ $options = $parser->parse($option_string) + $options;
+ }
+
+ return $options;
+ }
+
public function getDocumentation() {
return <<<EOT
= Diagrams
@@ -111,13 +121,22 @@
You can create and store diagrams in your Phorge instance.
These diagrams can be referenced by means of the `DIAG` token.
-For example: `{DIAG123}`.
-
-When you double click on a diagram, the editor will open the corresponding
-diagram in a new browser tab.
-
-You can create diagrams with multiple pages, but only the first one will be
-visualized in a referenced diagram.
+For example: `{DIAG123, full, page=2}`.
+
+When you hover over on a diagram, an edit button will appear which opens
+the editor of the corresponding diagram in a new browser tab.
+
+When you click on a diagram, a modal will open to view the diagram
+standalone.
+
+The rule supports following options:
+- **layout**: left (default), center, right
+- **float**: If layout is set to left or right, the diagram will be floated
+so text wraps around it.
+- **full**: If full is set, layout and float are ignored.
+- **width**: Can only be a number (interpreted as pixel) or an css px/%
+value.
+- **page**: Which page of the diagram should be rendered initially.
When you modify an existing diagram, a new version will be created.
You can select older versions in the editor by means of the dropdown in the
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Apr 14, 17:42 (1 d, 23 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1285971
Default Alt Text
D25962.1744652532.diff (15 KB)
Attached To
Mode
D25962: Implements remarkup rendering with the drawio viewer component
Attached
Detach File
Event Timeline
Log In to Comment