Create tooltips on Cytoscape Nodes Label using popper and tippy - cytoscape.js

I am trying to use cytoscape with tippy but it is not showing any tool tips. It throws an error that ele.popperRef is not a function.
Here is the stackblitz link: https://stackblitz.com/edit/dagre-tippy?file=src%2Fapp%2Fapp.component.ts
I have added all the packages required which includes popper.js, tippy.js but still it does not work

Check this https://stackblitz.com/edit/dagre-tippy-wgg8zz
You are not simply importing libraries properly and registering the cytoscape.js extensions.
You should register popper extension with cytoscape.use(popper);
You can use tippy.js with a function like
function makePopperWithTippy(node) {
let ref = node.popperRef(); // used only for positioning
// A dummy element must be passed as tippy only accepts dom element(s) as the target
// https://atomiks.github.io/tippyjs/v6/constructor/#target-types
let dummyDomEle = document.createElement("div");
let tip = tippy(dummyDomEle, {
// tippy props:
getReferenceClientRect: ref.getBoundingClientRect, // https://atomiks.github.io/tippyjs/v6/all-props/#getreferenceclientrect
trigger: "manual", // mandatory, we cause the tippy to show programmatically.
// your own custom props
// content prop can be used when the target is a single element https://atomiks.github.io/tippyjs/v6/constructor/#prop
content: () => {
let content = document.createElement("div");
content.innerHTML = "Tippy content";
return content;
}
});
tip.show();
}
Also, note that you don't have to use tipp.js. Just popper.js is enough actually.
function makePopper(ele) {
// create a basic popper on the first node
let popper1 = ele.popper({
content: () => {
let div = document.createElement("div");
div.innerHTML = "Popper content";
document.body.appendChild(div);
return div;
},
popper: {} // my popper options here
});
}
You can apply these functions below and see the tooltips. The event-based showing on and off is simple after this.
cy.elements().forEach(function(ele) {
makePopperWithTippy(ele);
// makePopper(ele);
});

Related

Manipulate innerText of a CKEditor ViewElement

I am creating a little custom plugin for the CKEditor5 for the #neoscms.
Neos is using the #ckeditor5 but with a custom view.
The plugin is more or less a placeholder plugin. The user can configure a data-source with a key value store for items (identifier and labels). The dropdown in the CKEditor is filled with the items and when the user selects an item from the dropdown, it creates a placeholder element that should end in a span element with some data-attributes.
The main idea was to have an empty element and just data-attributes to identify the element and being able to assign live data. But it turns out that the live data thing is tricky. When I manipulate the span with an extra JS snippet on the Website, the CKEditor cannot handle this.
Is it possible to manipulate a view element in the DOM and still have a working Editor?
The Plugin works fine if I just add inner Text in the downCasting and don't replace something. But the live data would be nice.
Neos Backend with a element
Maybe that code gives an idea of the package.
It is not ready yet as this is more or less the main feature ;)
import {Plugin, toWidget, viewToModelPositionOutsideModelElement, Widget,} from "ckeditor5-exports";
import PlaceholderCommand from "./placeHolderCommand";
export default class PlaceholderEditing extends Plugin {
static get requires() {
return [Widget];
}
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add(
"placeholder",
new PlaceholderCommand(this.editor)
);
this.editor.editing.mapper.on(
"viewToModelPosition",
viewToModelPositionOutsideModelElement(this.editor.model, (viewElement) =>
viewElement.hasClass("internezzo-placeholder")
)
);
this.editor.config.define("placeholderProps", {
types: ["name", "node", "nodePath"],
});
this.editor.config.define("placeholderBrackets", {
open: "[",
close: "]",
});
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register("placeholder", {
allowWhere: "$text",
isInline: true,
isObject: true,
allowAttributes: [
"name",
"node",
"nodePath",
"data-placeholder-identifier",
"data-node-identifier",
"data-node-path",
],
});
}
_defineConverters() {
const conversion = this.editor.conversion;
const config = this.editor.config;
conversion.for("upcast").elementToElement({
view: {
name: "span",
classes: ["foobar-placeholder"],
},
model: (viewElement, writer) => {
const name = viewElement.getAttribute('data-placeholder-identifier');
const node = viewElement.getAttribute('data-node-identifier');
const nodePath = viewElement.getAttribute('data-node-path');
const modelWriter = writer.writer || writer;
return modelWriter.createElement("placeholder", {name, node, nodePath, editable: false});
},
});
conversion.for("editingDowncast").elementToElement({
model: "placeholder",
view: (modelItem, writer) => {
const viewWriter = writer.writer || writer;
const widgetElement = createPlaceholderView(modelItem, viewWriter);
return toWidget(widgetElement, viewWriter);
},
});
conversion.for("dataDowncast").elementToElement({
model: "placeholder",
view: (modelItem, writer) => {
const viewWriter = writer.writer || writer;
return createPlaceholderView(modelItem, viewWriter);
},
});
// Helper method for downcast converters.
function createPlaceholderView(modelItem, viewWriter) {
const name = modelItem.getAttribute("name");
const node = modelItem.getAttribute("node");
const nodePath = modelItem.getAttribute("nodePath");
const placeholderView = viewWriter.createContainerElement("span", {
class: "foobar-placeholder",
"data-placeholder-identifier": name,
"data-node-identifier": node,
"data-node-path": nodePath,
});
// Would be nice to remove that and have just empty spans that get dynamic data
let innerText = config.get("placeholderBrackets.open") + name;
innerText += config.get("placeholderBrackets.close");
viewWriter.insert(
viewWriter.createPositionAt(placeholderView, 0),
viewWriter.createText(innerText)
);
return placeholderView;
}
}
}
So, the extra JS snippet that is used by the website is searching for spans with the class foobar-placeholder and writes a value with live data into the span. That works in the frontend, of course, but the backend of the CMS that uses CKEditor has issues with the changing data.
I could not find a solution with docs of CKEditor, and maybe I misuse the API somehow, but I now found a working solution for me.
My website snippet is now communicating with the Plugin via Broadcast messages. And then I search for placeholder elements and check if I need to change an attribute.
const broadcastChannel = new BroadcastChannel('placeholder:changeData');
broadcastChannel.postMessage({identifier: name, value});
And in the plugin
// Receive new values for placeholder via broadcast
const broadcastChannel = new BroadcastChannel('placeholder:changeData');
broadcastChannel.onmessage = (message) => {
const identifier = get('data.identifier', message);
const newValue = get('data.value', message);
this.editor.model.change( writer => {
if (identifier) {
this._replaceAttribute(writer, identifier, newValue);
}
});
};
Only downside now is that I need to reload the page, but already read that this is maybe cause by my element down casting and I change attributes.

simply replace a node's content in prosemirror

I'm in a function that receives a string as input:
(text) => {
}
I have access to the editor via Vue props (props.editor). I would like to replace the current node's content with this text. I cannot seem to find out how to do this. I'm using tiptap2, which is a wrapper around ProseMirror and has access to all of ProseMirror's api.
I'd rather not try to replace the whole node unless necessary, which I also tried, doing below – but cannot get that to work either:
(text) => {
props.editor
.chain()
.focus()
.command(({ tr }) => {
const node = props.editor.state.schema.nodes.paragraph.create(
{ content: text}
);
tr.replaceSelectionWith(node);
return true;
})
.run();
}
Much thanks
This solution works for me in Tiptap version 2.
A precondition for this to work is, that the text to be replaced is marked (highlighted).
const selection = editor.view.state.selection;
editor.chain().focus().insertContentAt({
from: selection.from,
to: selection.to
}, "replacement text").run();
I'm late to the party but this is the top result I came across when trying to find a solution for myself.
My code is in the context of a React NodeView, so I'm given a getPos() prop that gives the position of the React node in the Prosemirror document (I believe this number more-or-less means how many characters precede the React NodeView node). With that I was able to use this command chain to replace the content:
import { Node as ProsemirrorNode } from "prosemirror-model";
import { JSONContent, NodeViewProps } from "#tiptap/react";
const NodeViewComponent = (props: NodeViewProps) =>
// ...
/**
* Replace the current node with one containing newContent.
*/
const setContent = (newContent: JSONContent[]) => {
const thisPos = props.getPos();
props.editor
.chain()
.setNodeSelection(thisPos)
.command(({ tr }) => {
const newNode = ProsemirrorNode.fromJSON(props.editor.schema, {
type: props.node.type.name,
attrs: { ...props.attrs },
content: newContent,
});
tr.replaceSelectionWith(newNode);
return true;
})
.run();
};
// ...
};
Basically you want to:
Set the current selection to the node you want to replace the content of
Create and update a new node that is a copy of the current node
Replace your selection with the new node.

"orion" is undefined when integrating orion editor in dojo

I am new to dojo and I am trying to integrate orion editor(build downloaded from http://download.eclipse.org/orion/) in dojo but I get the error "orion" is undefined.
The code looks like below:
HTML file for placing editor
<div data-dojo-attach-point="embeddedEditor"></div>
A JS file
require([
"dojo/_base/declare",
"dijit/_WidgetBase",
"editorBuild/code_edit/built-codeEdit-amd",
"dijit/_TemplatedMixin",
"dojo/text!orionEditor.html"
], function(declare,_WidgetBase,
codeEditorAmd, _TemplatedMixin,template){
declare("orionEditor", [_WidgetBase,
_TemplatedMixin], {
templateString: template,
postCreate: function(){
var codeEdit = new orion.codeEdit();
var contents = '';
codeEdit.create({parent: this.embeddedEditor, contentType: "application/javascript", contents: contents}).
then(function(editorViewer) {
if (editorViewer.settings) {
editorViewer.settings.contentAssistAutoTrigger = true;
editorViewer.settings.showOccurrences = true;
}
});
}
});
});
The orion editor build is placed in editorBuild folder.
Standalone orion works fine - http://libingw.github.io/OrionCodeEdit/
When integrating with dojo I am not sure why orion is undefined.
Any help would be much appreciated.
If you want using orion name in amd module then it has to be defined as parameter in function passed as require's callback.
Check this guide - it has 2 solutions for using orion with amd modules.
Option 1 - define bundles once and use shorter name in all modules you need them:
require.config({
bundles: {
"editorBuild/code_edit/built-codeEdit-amd": ["orion/codeEdit", "orion/Deferred"]
}
});
require(
["orion/codeEdit", "orion/Deferred"],
function(mCodeEdit, Deferred) {
var codeEdit = new mCodeEdit();
var contents = 'var foo = "bar";';
codeEdit.create({parent: "embeddedEditor"/*editor parent node id*/})
.then(function(editorViewer) {
editorViewer.setContents(contents, "application/javascript");
});
});
Option 2 - nested require:
require(["editorBuild/code_edit/built-codeEdit-amd"], function() {
require(["orion/codeEdit", "orion/Deferred"], function(mCodeEdit, Deferred) {
var codeEdit = new mCodeEdit();
var contents = 'var foo = "bar";';
codeEdit.create({parent: "embeddedEditor"/*editor parent node id*/})
.then(function(editorViewer) {
editorViewer.setContents(contents, "application/javascript");
});
});
});
Note: you can replace mCodeEdit with any unique name (That wouldn't shadow other objects/modules)

vuejs2-ace-editor: accessing editor instance

I am using this Vue2 component for ACE editor:
https://github.com/chairuosen/vue2-ace-editor
This is how I add the component to my app
var app = new Vue({
el: '#vue_app',
data: {
message: 'Hello Vue!',
editor_content: 'somecontent'
},
methods:{
editorInit:function (el) {
require('brace/mode/json');
require('brace/theme/tomorrow');
}
},
components: {
editor:require('vue2-ace-editor')
}
});
And when I put this into my HTML everything works correctly:
<editor v-model="editor_content" #init="editorInit();" lang="json" theme="tomorrow" width="500" height="100"></editor>
However the editor renders with a gutter which I don't need, so I want to access the editor instance to tweak some properties.
The source code for this component says it emits an event on mount:
mounted: function () {
var vm = this;
var lang = this.lang||'text';
var theme = this.theme||'chrome';
require('brace/ext/emmet');
var editor = vm.editor = ace.edit(this.$el);
this.$emit('init',editor);
editor.$blockScrolling = Infinity;
editor.setOption("enableEmmet", true);
editor.getSession().setMode('ace/mode/'+lang);
editor.setTheme('ace/theme/'+theme);
editor.setValue(this.value,1);
editor.on('change',function () {
var content = editor.getValue();
vm.$emit('input',content);
vm.contentBackup = content;
});
}
How and where do I catch this event and access the editor object?
You are already listening to the init event and calling the editorInit method. However, you need to pass the data being emitted by the <editor> component.
You can either do that explicitly by using $event:
<editor v-model="editor_content" #init="editorInit($event)" ...></editor>
Or implicitly by simply providing the method name as the event handler:
<editor v-model="editor_content" #init="editorInit" ...></editor>
Then, in your editorInit method, the param being passed in will be the instance of the editor. And I believe you can specify to not render the gutter like so:
editorInit:function (editor) {
editor.renderer.setShowGutter(false)
require('brace/mode/json');
require('brace/theme/tomorrow');
}

Can I detect changes in a node's markup text using dojo?

I have a bunch of nodes that will contain markup in an unpredictable structure. I want to be able to watch these nodes and see if the html of the any of the child nodes or their descendants change, no matter how slightly. If they do, then I want to fire an event.
Can I do this through dojo? I'm using 1.10, the latest one.
Thanks.
It sounds like you're looking for dom mutations. As far as I'm aware dojo does not provide an api for this, but they're pretty simple to set up. The problem is different browsers have different ways of doing this.
var observeNode = document.getElementById('observeMe');
// Check for vendor-specific versions of MutationObserver.
MutationObserver = (function() {
var prefixes = ['WebKit', 'Moz', 'O', 'Ms', ''];
for (var i=0, il=prefixes.length; i<il; i++) {
if (prefixes[i] + 'MutationObserver' in window) {
return window[prefixes[i] + 'MutationObserver'];
}
}
}());
// Sniff for MutationObserver support
if (MutationObserver) {
var observer = new MutationObserver(function(mutations) {
alert('Something changed!');
});
observer.observe(observeNode, {attributes: true, childList: true, characterData: true});
} else {
// Fall back to mutation events
if (observeNode.addEventListener) {
observeNode.addEventListener('DOMSubtreeModified', function() {
alert('Something changed!');
});
}
// IE8 and below has its own little weird thing
else {
observeNode.onpropertychange = function() {
alert('Something Changed!');
}
}
}