How to add a link in a mermaid node description? - javascript

I would like, to the graph below,
<link href="https://cdnjs.cloudflare.com/ajax/libs/mermaid/7.0.0/mermaid.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/7.0.0/mermaid.js"></script>
<div class="mermaid">
graph TD;
A[hello]
B[an <b>important</b> link]
A-->B
</div>
to add an actual link under link pointing to http://google.com.
I tried to modify the relevant node to
B[an <b>important</b> link]
but this breaks (crashes) the graph. Specifically, I noticed that what is not accepted is the href element.
Is it possible to add a link in the mermaid node description?
EDIT: I opened a bug report on the mermaid.js repository. Not fixed yet as of June 2017.

I know it's late but: I was searching for a similar thing and found this. Your problem are the " of the href definition breaking the mermaid syntax.
I could achieve what you wanted to do by using
B[an <b>important</b> <a href='http://google.com'>link</a>]
so replacing the doublequotes " of the href definition with single ones '
See the example here.
Update a year later
in a newer version of mermaid this is not directly supported anymore (ಠ_ಠ)
more about it under the version 8.2.0 CHANGELOG
Now you'll need to additionally allow unsecure content via
mermaidAPI.initialize({
securityLevel: 'loose'
});

Sure, its possible to add a link in Mermaid node, as shown below:
mermaid.initialize({
startOnLoad: true
});
<script src="https://cdn.rawgit.com/knsv/mermaid/6.0.0/dist/mermaid.min.js"></script>
<link href="https://cdn.rawgit.com/knsv/mermaid/6.0.0/dist/mermaid.css" rel="stylesheet" />
<div class='mermaid'>
graph TD;
A(Start)-->B(Do some stuff);
B(Take some rest)-->C(do more);
click B "http://www.github.com" "This is a link"
</div>
You can also do a callback by using this script
<script>
var callback = function(){
alert('A callback was triggered');
}
<script>
and then inserting this into your HTML below node A-->B in your HTML
click A callback "Hi I'm a callback, whats up"

A few diagrams have interaction support:
flowchart
flowDB.js
other usage
classDiagram
classDB.js
gantt
ganttDB.js
This functionality is disabled when using securityLevel='strict'
and enabled when using securityLevel='loose'.
Example
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.14.0/mermaid.min.js"></script>
<h2>FlowChart</h2>
<div class="mermaid">
graph LR
A -- text --> B --> Stackoverflow -- msg --> myLabel2
click Stackoverflow "https://stackoverflow.com/" "some desc when mouse hover" _blank
click myLabel2 "https://stackoverflow.com/" "some desc when mouse hover"
</div>
<h2>classDiagram</h2>
<div class="mermaid">
%% https://github.com/mermaid-js/mermaid/blob/cbe3a9159db4d5e67d270fe701cd63de1510f6ee/docs/directives.md?plain=1#L10-L48
%%{init: {'theme': 'forest'}}%%
classDiagram
class myCls {
attr type
method()
}
%% ↓ must set: securityLevel=loose %% default para: clsID
click myCls call myFunc() "desc."
class myCls2
click myCls2 call myFunc('hello world') "desc."
class myClsUseLink {
+field1
}
link myClsUseLink "https://www.github.com" "This is a link"
</div>
<h2>Gantt</h2>
<div class="mermaid">
gantt
dateFormat HH:mm
axisFormat %H:%M
try to click me : gotoSO, 19:00, 5min
%% click : debug, after gotoSO, 5min --> error, click is "keyword"
clickMe : debug, after gotoSO, 5min
endNode : milestone, m, 20:00, 0min
click gotoSO href "https://stackoverflow.com/"
click debug call myFunc()
%% NOTE: not working on github
</div>
<script>
mermaid.initialize({
securityLevel: 'loose', // strict, loose, antiscript, sandbox // // https://github.com/mermaid-js/mermaid/blob/b141f24068e9c5f6979706383a29db6380ffdf31/docs/usage.md?plain=1#L114-L117
});
function myFunc(arg) {
console.log(arg)
}
</script>

Here's how to hack it into ERD diagrams, which are kinda new so currently lack any kind of clickable support. First create this Javascript object with node names that refer to your intended link destinations:
var links = {
Customer: "/customers/index",
Employee: "/employees/index",
Shipper: "/shippers/index",
OrderDetail: "/order_details/index",
Site: "/sites/index",
User: "/users/index"
};
And then in the Mermaid initializer click events can get wired up to the relevant graphics objects in the SVG with this trickery:
mermaid.initialize({
startOnLoad: true,
securityLevel: "loose",
er: { useMaxWidth: false },
mermaid: {callback: function(objId) {
var svg = document.getElementById(objId);
var nodeName;
for(nodeName in links) {
var gErd = svg.getElementById(nodeName);
gErd.addEventListener("click",
function (evt) {
location.href = links[this.id];
}
);
}
}}
});
If you click anywhere inside the node, it navigates.
I had put all this together for use in The Brick gem, which is a data-related Ruby on Rails add-on.

Related

Relative line numbers for monaco editor

Monaco Editor offers VIM key bindings. I also want to make the line numbers relative when using the VIM mode. Is there any API in the monaco global object which I can use to achieve this?
Note: I don't own the code base. I want to use this in a website that already has a monaco editor instance.
I went through the global object and could not find anything useful.
[
"create",
"onDidCreateEditor",
"createDiffEditor",
"createDiffNavigator",
"createModel",
"setModelLanguage",
"setModelMarkers",
"getModelMarkers",
"onDidChangeMarkers",
"getModels",
"getModel",
"onDidCreateModel",
"onWillDisposeModel",
"onDidChangeModelLanguage",
"createWebWorker",
"colorizeElement",
"colorize",
"colorizeModelLine",
"tokenize",
"defineTheme",
"setTheme",
"remeasureFonts",
"registerCommand",
"AccessibilitySupport",
"ContentWidgetPositionPreference",
"CursorChangeReason",
"DefaultEndOfLine",
"EditorAutoIndentStrategy",
"EditorOption",
"EndOfLinePreference",
"EndOfLineSequence",
"MinimapPosition",
"MouseTargetType",
"OverlayWidgetPositionPreference",
"OverviewRulerLane",
"RenderLineNumbersType",
"RenderMinimap",
"ScrollbarVisibility",
"ScrollType",
"TextEditorCursorBlinkingStyle",
"TextEditorCursorStyle",
"TrackedRangeStickiness",
"WrappingIndent",
"InjectedTextCursorStops",
"PositionAffinity",
"ConfigurationChangedEvent",
"BareFontInfo",
"FontInfo",
"TextModelResolvedOptions",
"FindMatch",
"ApplyUpdateResult",
"EditorType",
"EditorOptions"
]
Simply set lineNumbers to "relative".
monaco.editor.create(document.getElementById('container'), {
value: `function x() {
console.log("Hello world!");
}`,
language: 'javascript',
theme: 'vs-dark',
lineNumbers: 'relative'
});
<link rel="stylesheet" data-name="vs/editor/editor.main" href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.26.1/min/vs/editor/editor.main.min.css">
<div id="container" style="height:400px;border:1px solid black;"></div>
<script>var require = { paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.26.1/min/vs' } }</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.26.1/min/vs/loader.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.26.1/min/vs/editor/editor.main.nls.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.26.1/min/vs/editor/editor.main.js"></script>
Given that you want to dynamically change the options and you probably don't have access to the editor instance, you can find the active editors on the page using the global monaco object with monaco.editor.getEditors(), which returns an array of all active instances, then use updateOptions() to customize the line numbers.
In your case it'd look something like:
// anywhere in the JS code
monaco.editor.getEditors()[0].updateOptions({
lineNumbers: 'relative'
});

TinyMCE Enable button while in read only mode

I have a TinyMCE 4.x instance where the text should be in read only mode. But I still have some buttons that I want to have enabled. For example, one button could provide a character count for the part of the text I've selected.
But when I turn on read only mode for TinyMCE all buttons are disabled. Can I enable just my buttons while still retaining read only mode?
It's probably too late for you but other people may pass by here.
I came up by writing this function
function enableTinyMceEditorPlugin(editorId, pluginName, commandName) {
var htmlEditorDiv = document.getElementById(editorId).previousSibling;
var editor = tinymce.get(editorId);
var buttonDiv = htmlEditorDiv.querySelectorAll('.mce-i-' + pluginName.toLowerCase())[0].parentElement.parentElement;
buttonDiv.className = buttonDiv.className.replace(' mce-disabled', '');
buttonDiv.removeAttribute('aria-disabled');
buttonDiv.firstChild.onclick = function () {
editor.execCommand(commandName);
};
}
It does the trick in 2 steps:
make the button clickable (remove mce-disabled CSS class and remove the aria-disabled property)
assign the good command to the click event
And in my editor init event I call the function.
editor.on('init', function () {
if (readOnly) {
editor.setMode('readonly');
enableTinyMceEditorPlugin(htmlEditorId, 'preview', 'mcePreview');
enableTinyMceEditorPlugin(htmlEditorId, 'code', 'mceCodeEditor');
}
});
Current version of TinyMCE for which I wrote this code is 4.4.3. It may break in a future version, specifically about the selectors to get and modify the good HTML elements.
Command identifiers can be found at this page otherwise you can also find them under tinymce\plugins\PluginName\plugin(.min).js
Here is a simple way to enable your custom toolbar button and attach a click event handler inside a read only TinyMCE editor using JQUERY:
//Initialize read only Tinymce editor so that Lock button is also disabled
function initReadOnlyTinyMCE() {
tinymce.init({
selector: '#main'
, toolbar: 'myLockButton'
, body_class: 'main-div'
, content_css: 'stylesheets/index.css'
, readonly: true
, setup: function (readOnlyMain) {
readOnlyMain.addButton('myLockButton', { //Lock button is disabled because readonly is set to true
image: 'images/lock.png'
, tooltip: 'Lock Editor'
});
}
});
}
function displayReadOnlyTinyMCEwithLockButtonEnabled() {
var edContent = $('main').html();
$("#main").empty();
initReadOnlyTinyMCE(true);
tinyMCE.activeEditor.setContent(edContent);
//enable the lock button and attach a click event handler
$('[aria-label="Lock Editor"]').removeClass("mce-disabled");
$('[aria-label="Lock Editor"]').removeAttr("aria-disabled");
$('[aria-label="Lock Editor"]').attr("onclick", "LockEditor()");
}
function LockEditor() {
alert("Tiny mce editor is locked by the current user!!");
//Write your logic to lock the editor...
}
I couldn't find an easy way to do this. The simplest way is to remove the contenteditable attribute from the iframe body instead and substitute a read only toolbar set. It also means that people will still be able to copy content from the editor.
$("iframe").contents().find("body").removeAttr("contenteditable");
How about this :
editor.addButton('yourButton', {
title: 'One can Enable/disable TinyMCE',
text: "Disable",
onclick: function (ee) {
editor.setMode('readonly');
if($(ee.target).text() == "Disable"){
var theEle = $(ee.target).toggle();
var edit = editor;
var newBut = "<input type='button' style='opacity:1;color:white; background-color:orange;' value='Enable'/>";
$(newBut).prependTo($(theEle).closest("div")).click(function(e){
edit.setMode('design');
$(e.target).remove();
$(theEle).toggle();
});
}
}
});
You can try to run the code below:
$("#tinymce").contentEditable="false";
if you have more than one editors, you can use their id like below
$("#tinymce[data-id='idOfTheEditor']").contentEditable="false";

Replacing widget element with a newly constructed DOM structure

<script>
(function( $ ) {
$.widget( "my.dropbox", {
errorText: function(text) {
$(this.element).next().html(text);
},
_create: function() {
var id = $(this.element).attr("id");
var customDropbox = $(
"<div class='form-group'>"+
"<label for='"+id+"'>"+getLabelFor(id)+"</label>"+
"<select id='"+id+"'></select>"+
"<div class='errors'></div>"+
"</div>"
);
customDropbox.attr("id", id);
$(this.element).replaceWith(customDropbox); // This removes original element from DOM
populateOptions(id);
},
});
}( jQuery ));
$(document).ready(function(){
$("#field1").dropbox(); //blank input field turns into a select with a label, populated options e.t.c..
$("#button1").on("click", function(){
$("#field1").dropbox("errorText", "This is a validation error message"); //throws an error saying dropbox is not initialized
});
});
</script>
<html>
<body>
<input id="field1" />
<button id="button1">Press me</button>
</body>
</html>
So I want a widget with public methods that will replace the original element with all the widget data associated with it. The problem with the above code is that the <select..> element is just a DOM element and if you call .dropbox(..) on it, it will say the widget is not initialized. Is there a way to make the select element into the widget object with the .errorText() method? All widget examples online add stuff around the original element but never replace it. As for the bigger picture, I'm trying to make a generic tool to configure forms dynamically. It's going to be all <input id="..."> in html but then javascript will query a database, get configuration for the field and turn it into a dropbox, checkbox or, say, a date picker with all the labels, validation, and other bells and whistles.
There is more than one issue with your widget code. I'll try to summarize them:
1. Copy the data
You're not copying the data to the newly created customDropbox, so before
this.element.replaceWith(customDropbox);
you should copy the data:
customDropbox.data(this.element.data());
Now the widget will remember that it was initialized.
2. this.element is gone
After
this.element.replaceWith(customDropbox);
you should update this.element so that it points to the newly created customDropbox:
this.element = customDropbox;
3. errorText message takes wrong element
Since the widgets element (this.element) is now pointing to the <div class='form-group'></div> element, the errorText function must be slightly modified to:
this.element.find(".errors").html(text);
4. id should be unique
Now, both the wrapper <div> and the <select> have the same id, which is not allowed in HTML so remove the one on the <select> tag. Luckily, <label> can work without the for attribute, just write it like this:
<label>labelForId <select></select></label>
Then to get the <select>-element, use this.element.find("select") in the widget.
Side note
`this.element` is already a jQuery element, so no need for the additional `$()` wrapping.
See this jsFiddle
function show(){
$("#field1").input({....});
}
function hide(){
$("#field1").input("hide");
}
<button onclick="show()">show</button>
<button onclick="hide()">hide</button>
i think to replace the origin element which initial dropbox() is not a good solution,
because this will force you to rely on the implemention details of jQuery ui factory,
it is easy to make a mistake or introduce bugs, sometimes harder for other people to understand your code
if jquery ui factory change the implemention in the future, you have to modify all your code to make it work
(sorry for my limit understand of jquery ui)
i think we can put the <input/> into a container and initial dropbox() on the container which inturn
replace <input/> with <select> datepicker ..etc.. we can build modules easily by doing so:
<form>
<div class="dropbox"><label for="someID">aaaaaa</label><input id="someID"/></div>
<div class="datepicker"></div>
<div class="othermodule"></div>
</form>
js:
$(".dropbox").dropbox(); // init dropbox you defined
$(".datepicker").datepicker(); // ...
$(".othermodule").othermodule(); // ...
$(".dropbox").dropbox("errorText", "error"); // invoke it smoothly
here is a simple demo: http://jsfiddle.net/m4A3D/
#Wouter Huysentruit's answer provides a list of good suggestion for me
<form>
<div class="dropbox">
<label for="someID">aaaaaa</label>
<input id="someID"/>
</div>
<div class="datepicker"></div>
<div class="othermodule"></div>
</form>
<button id="button1">Press me</button>
<script>
(function ($){
$.widget("my.dropbox", {
_create: function () {
var $input = this.element.find("input");
var sID = $input.attr("id");
var $select = $("<select>");
$select.attr("id", sID);
$input.replaceWith($select);
this.element.append("<div class='errors'></div>");
}, // end _create()
errorText: function (text) {
this.element.find(".errors").text(text);
} // end errorText()
});
}(jQuery));
$(".dropbox").dropbox();
$("#button1").click(function () {
$(".dropbox").dropbox("errorText", "this is error");
});
</script>

Assigning some style from the styles-box in CKEditor through JavaScript

How can I simulate user-selection of some style from the styles-box, through JS? I want to put some shortcut buttons that assign some of the popular styles with one click.
EDIT:
I don't care if it'll be in-editor button or outer button.
I don't want css-style assignment; I want CKEditor-style assignment (those of the styles-box).
I haven't used CKEditor, but, I saw your question and thought "That would be fun to figure out." Well, here is what I figured out:
(yes, I found terrible documentation, but, that's not the point...I will give them props for commenting their code, though.)
///
// function to add buttons that trigger styles to be applied.
//
// editor - CKEDITOR - instance of editor you want command attached to.
// buttonName - String - name of the button
// buttonLabel - String - humane readable name of the button
// commandName - String - name of command, the way to call this command from CKEDITOR.execCommand()
// styleDefinition - StyleDefinition - obj defining the style you would like to apply when this command is called.
///
var addButtonCommand = function( editor, buttonName, buttonLabel, commandName, styleDefiniton )
{
var style = new CKEDITOR.style( styleDefiniton );
editor.attachStyleStateChange( style, function( state )
{
!editor.readOnly && editor.getCommand( commandName ).setState( state );
});
editor.addCommand( commandName, new CKEDITOR.styleCommand( style ) );
editor.ui.addButton( buttonName,
{
label : buttonLabel,
command : commandName
//adding an icon here should display the button on the toolbar.
//icon : "path to img",
});
};
//Get the editor instance you want to use. Normally the same as the ID of the textarea CKEditor binds to.
var editor1 = CKEDITOR.instances.editor1;
//If you look at ckeditor/_source/plugins/styles/default.js you will see that this selects the first element. That list is read into the array 'default'.
var blueTitleStyle = CKEDITOR.stylesSet.registered.default[0];
//Or, you can define the style like this: See http://dev.ckeditor.com/wiki/Components/Styles for more info on style definitions.
var blueTitleStyle = {
name : 'Blue Title',
element : 'h3',
styles : { 'color' : 'Blue' }
};
addButtonCommand(editor1, 'BlueTitle', 'BlueTitle', 'bluetitle', blueTitleStyle);
Here is a Javascript function to aid your click events:
//function used to execute the command. Only used for calling the command when not calling from a button. (Like an A with an onClick bound to it.)
//pulled this function right out of the api.html example in the ckeditor/_samples dir.
function ExecuteCommand( commandName )
{
// Get the editor instance that we want to interact with.
var oEditor = CKEDITOR.instances.editor1;
// Check the active editing mode.
if ( oEditor.mode == 'wysiwyg' )
{
// Execute the command.
// http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.editor.html#execCommand
oEditor.execCommand( commandName );
}
else
{
alert( 'You must be in WYSIWYG mode!' );
}
}
Now, you can create a link like this:
<a href='#' class='setBlueTitle'>Set Blue Title</a>
and use a bit of jQuery to spice it up:
<script type="text/javascript">
$(document).ready(function(){
$(".setBlueTitle").onClick(function(e){
//stops the click from changing the page and whatever other default action would happen.
e.preventDefault();
ExecuteCommand('bluetitle');
});
});
</script>
I am not 100% sure about the button icon part. I didn't have an icon to try it with. But, according to a few posts, it should work fine. Regardless, the jQuery click binding works.
That should be pretty much it! I had to do quite a bit of digging around to figure this out, but it certainly is satisfying to see it work!
Here's one option
First, you can setup the desired styles you want to try out in a CSS class. Then, you can set the className for the test div when you click that button. Here's a simple example:
test.css:
.bold {
font-weight: bold;
}
.italic {
font-style: italic;
}
test.html
<html>
<head>
<link rel="stylesheet" type="text/css" href="test.css" />
</head>
<body>
<input type="button" onclick="document.getElementById('testStyleDiv').className='bold'" value="bold"/>
<input type="button" onclick="document.getElementById('testStyleDiv').className='italic'" value="italic"/>
<div id="testStyleDiv">foo</div>
</body>
</html>

Using jQuery in a firefox extension

I'm trying to develop a firefox extension which draws a toolbar at the base of every webpage.
Until now i managed to make jQuery work and i proved it by running
$("body",mr.env).css("background","black");
in the mr.on=function().
This code just makes the background color of the webpage black whenever i click the menu item associated with the addon.
But, if i try to run
$('body',mr.env).append( ' <img src="img/check.png" /> ' );
it simply fails. It doesn't show any error in Error Console and the image isn't displayed.
Why is that?
This is my overlay XUL :
<script src="window.js"/>
<link href="style.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="jquery-1.4.4.min.js"></script>
<!-- Firefox Tools menu -->
<menupopup id="menu_ToolsPopup">
<menuitem id="menu_crypt_demo" class="" image=""
label="Use DnsResolver?" insertbefore="javascriptConsole" accesskey="o"
oncommand="DnsResolver.onMenuItemCommand(event);">
</menuitem>
</menupopup>
This is the JavaScript file (window.js):
var DnsResolver = {
onLoad: function() {
// initialization code
this.initialized = true;
},
onMenuItemCommand: function() {
testextension.on();
window.open("chrome://dnsresolver/content/window.xul", "", "chrome");
}
};
window.addEventListener("load", function(e) { DnsResolver.onLoad(e); }, false);
if(!testextension){ var testextension={};}
(function(){
var mr=testextension;
mr.on=function(){
mr.loadLibraries(mr);
var jQuery = mr.jQuery;
var $ = function(selector,context){ return new jQuery.fn.init(selector,context||window._content.document); };
$.fn = $.prototype = jQuery.fn;
mr.env=window._content.document;
/*$("body",mr.env).css("background","black");*/
$('body',mr.env).append('<img src="img/check.png" />');
$(mr.env).ready(function(){
// hide and make visible the show
$("span.close a",mr.env).click(function() {
$("#tbar"),mr.env.slideToggle("fast");
$("#tbarshow",mr.env).fadeIn("slow");
});
// show tbar and hide the show bar
$("span.show a",mr.env).click(function() {
$("#tbar",mr.env).slideToggle("fast");
$("#tbarshow",mr.env).fadeOut();
});
});
/*$("body",mr.env).css("background","black");*/
}
// Loading the Jquery from the mozilla subscript method
mr.loadLibraries = function(context){
var loader = Components.classes["#mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://dnsresolver/content/jquery-1.4.4.min.js",context);
var jQuery = window.jQuery.noConflict(true);
if( typeof(jQuery.fn._init) == 'undefined') { jQuery.fn._init = jQuery.fn.init; }
mr.jQuery = jQuery;
}
})();
Starting with Firefox 3, chrome resources can no longer be referenced from within <img>, <script>, or other elements contained in, or added to, content that was loaded from an untrusted source. This restriction applies to both elements defined by the untrusted source and to elements added by trusted extensions. If such references need to be explicitly allowed, set the contentaccessible flag to yes to obtain the behaviour found in older versions of Firefox.
Use the HTML tab in FireFox to know actually if the img element was added. It probably was added and the problem is with your URL.
I remember when building my FireFox extensions, that files are located through a special protocol (chrome:// I think), where you put the name of the extension and can browse through it.

Categories

Resources