Link: codesandbox
I'm having the following problem can anyone help me out?
Error:
Cannot read property 'getJsonFromDiff' of undefined
-> let outStr = Diff2Html.getJsonFromDiff(dd, {
CodeDiff.js:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { createPatch } from "diff";
import { Diff2Html } from "diff2html";
import { InlineMath } from "react-katex/dist/react-katex";
import "highlight.js/styles/googlecode.css";
import "diff2html/lib/diff2html";
function CodeDiff(props) {
const { oldStr, newStr, context, outputFormat } = props;
const createdHtml = (oldString, newString, context, outputFormat) => {
function hljs(html) {
return html.replace(
/<span class="d2h-code-line-ctn">(.+?)<\/span>/g,
'<span class="d2h-code-line-ctn"><code>$1</code></span>'
);
}
let args = [
"",
oldString || "",
newString || "",
"",
"",
{ context: context }
];
let dd = createPatch(...args);
let outStr = Diff2Html.getJsonFromDiff(dd, {
inputFormat: "diff",
outputFormat: outputFormat,
showFiles: false,
matching: "lines"
});
let html = Diff2Html.getPrettyHtml(outStr, {
inputFormat: "json",
outputFormat: outputFormat,
showFiles: false,
matching: "lines"
});
return hljs(html);
};
const html = () => createdHtml(oldStr, newStr, context, outputFormat);
return (
<div id="code-diff" dangerouslySetInnerHTML={{ __html: html() }}></div>
);
}
CodeDiff.propTypes = {
oldStr: PropTypes.string.isRequired,
newStr: PropTypes.string.isRequired,
context: PropTypes.number,
outputFormat: PropTypes.string
};
CodeDiff.defaultProps = {
oldStr: "",
newStr: "",
context: 5,
outputFormat: "line-by-line"
};
export default CodeDiff;
Well, "diff2html" library exposes only "html" and "parse" functions, so in order to use it from single object Diff2Html like you want you have to import it diffrently, like so:
import * as Diff2Html from "diff2html";
But then there are no such things there as getJsonFromDiff and getPrettyHtml
Not sure where you got those from, getJsonFromDiff is actually a test name in their github - https://github.com/rtfpessoa/diff2html/search?q=getJsonFromDiff
But it is not a function. And there is not at all such thing as getPrettyHtml
So i suppose you wanted to use parse (instead of getJsonFromDiff) and html (instead of getPrettyHtml) that way it works fine as far as i can tell - https://codesandbox.io/s/material-demo-forked-jhljq?file=/CodeDiff.js:114-153
I had the same question, I was able to get access to these functions like this:
import * as Diff2HtmlLib from 'diff2html';
Diff2HtmlLib.Diff2Html.getJsonFromDiff(diff, diffOptions)
Related
I am trying to develop a simple plugin for CKEditor 5. My ultimate goal is to have the plugin emit the following HTML:
<div class="icms_note">
<div class="span-6 module-note">
<p>User can edit here</p>
</div>
<div class="clear"></div>
</div>
I was having issues with the user editable area being in the wrong spot so I reduced the output to just this:
<div class="icms_note">
<p>User can edit here</p>
</div>
and the data model looks like this:
<icms_note>
<paragraph>User can edit here</paragraph>
</icms_note>
So I can get the data model created correctly and the HTML ends up in the editor the way I expect but I can't edit the text in the paragraph. I click on the text and the cursor just jumps out. I've tried looking at other examples and the tutorials but I can't seem to get it to work right. My plugin code is below. Any help would be appreciated.
note.js
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import NoteEditing from "./noteediting";
import NoteUI from "./noteui";
export default class Note extends Plugin {
static get requires() {
return [ NoteEditing, NoteUI ];
}
static get pluginName() {
return "IcmsNote";
}
}
noteui.js
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import { ButtonView } from "#ckeditor/ckeditor5-ui";
export default class NoteUI extends Plugin {
init() {
const editor = this.editor;
editor.ui.componentFactory.add( "icms_note", (locale) => {
const button = new ButtonView(locale);
button.set({
label: "Note",
withText: true,
tooltip: true
});
button.on( "execute", () => {
editor.execute("insertNote");
});
return button;
} );
}
}
noteediting.js
import Position from "#ckeditor/ckeditor5-engine/src/view/position";
import NoteCommand from "./notecommand";
export default class NoteEditing extends Plugin {
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add("insertNote", new NoteCommand(this.editor));
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register( "icms_note", {
inheritAllFrom: "$text",
allowIn: [ "$root", "$container" ],
isInline: true
});
}
_defineConverters() {
const conversion = this.editor.conversion;
conversion.for( "downcast" ).elementToElement({
model: "icms_note",
view: ( modelElementValue, conversionApi ) => {
const { writer } = conversionApi;
const outerDivElement = writer.createEditableElement("div", {class: "icms_note"});
return outerDivElement;
}
});//<div><div class=\"span-6 module-note\"><p>Enter note</p></div><div class=\"clear\"></div></div>
conversion.for( "upcast" ).elementToElement({
view: {
name: "div",
attributes: {
classes: [ "icms_note" ]
}
},
model: {
key: "icms_note",
value: viewElement => {
const val = viewElement.getChildren()[0].getChildren()[0].data;
return val;
}
}
});
}
}
notecommand.js
import Command from "#ckeditor/ckeditor5-core/src/command";
export default class NoteCommand extends Command {
constructor(editor) {
super(editor);
}
execute() {
console.log("NoteCommand#execute");
const model = this.editor.model;
const selection = model.document.selection;
model.change( modelWriter => {
let position = selection.getFirstPosition();
const icmsNote = modelWriter.createElement("icms_note");
const paragraph = modelWriter.createElement("paragraph");
modelWriter.insert(paragraph, icmsNote);
modelWriter.insertText("User can edit here", paragraph);
let positionElementName = position.parent.name;
while (positionElementName != "$root" && positionElementName != "$container") {
position = model.createPositionAfter(position.parent);
positionElementName = position.parent.name;
}
model.insertContent(icmsNote, position, null, {
setSelection: "after"
});
});
}
}
I'm trying to render a component only if the value accessed from my Pinia store is true -
// MessageArea.vue
import { useValidationStore } from '../../stores/ValidationStore.js'
data() {
return {
errors: {
english: useValidationStore().english.error,
},
}
},
<template>
<p v-if="errors.english">
<EnglishErrorMessage />
</p>
</template>
By default, it is false -
import { defineStore } from "pinia";
export const useValidationStore = defineStore("validation", {
state: () => {
return {
english: {
input: '',
error: false,
},
}
}
})
I'm also accessing that same value from another file to check for the error -
<script>
import { useValidationStore } from '../../../stores/ValidationStore';
export default {
data() {
return {
input: useValidationStore().english.message,
error: useValidationStore().english.error,
}
},
methods: {
validate(input) {
this.input = input.target.value
const legalChars = /^[A-Za-z\s]*$/.test(this.input);
if (!legalChars && this.input !== "") {
this.error = true;
} else if (this.input === "" || (legalChars && !legalChars)) {
this.error = false;
}
console.log(this.error)
console.log(this.input)
}
}
}
</script>
<template>
<input
#input="validate"
:value="input"
/>
</template>
The console log values are updating reactively. Also, this.error will be logged as true when an illegal character is entered into the input. The reactivity is behaving as expected. So, I'm not sure why '' will not render in this case?
Am I not accessing the pinia value correctly in the template?
I've tried to understand what 'mapState()' and 'mapStores()' from the docs and if they can help me but I'm still confused.
You need to mutate the store state in validate method.
If you want to use pinia in options api you can use mapState and mapWritableState in computed property to remain rective.
Take a look here codesandbox please.
The WebComponents do work fine with this custom script but I have problems to.
I want to be able to add event listener from outside to listen to the custom Vue events fired from within the component (in this example on click on the WC). Thus I created an event proxy but for some reason the listener is never triggered. Obviously I made a mistake somewhere. So I hope that someone can point me to the right solution.
I made a CodeSandbox for you to take a look and play around with the code.
(as CodeSandbox has some issues you'll need to click on the reload button on the right side of the layout in order to make it work)
//main.js
import { createApp } from "vue";
import App from "./App.vue";
import WebComponentService from "./services/wc";
WebComponentService.init();
createApp(App).mount("#app");
Vue Component used as source file for Web Component
//src/wcs/MyComp.vue
<template>
<div class="m" #click="click">
Hello My Comp {{ name }}
<div v-for="(i, index) in values" :key="index">ID: {{ i.title }}</div>
</div>
</template>
<script>
export default {
__useShadowDom: false,
emits: ["my-click"],
// emits: ["my-click", "myclick"],
props: {
name: {
default: "Test",
},
values: {
type: Object,
default: () => {
return [{ title: "A" }, { title: "B" }];
},
},
},
methods: {
click() {
// emit the event
this.$emit("my-click");
console.log("EMIT");
},
},
};
</script>
<style lang="less" scoped>
.m {
border: 5px solid red;
margin: 10px;
padding: 10px;
border-radius: 5px;
}
</style>
Index.html where I test the web component and added an event listener
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<h3>WC Test here</h3>
<div class="component-canvas">
<!-- web component testing -->
<xvue-my-comp
id="vtest"
name="MyComp HTML Rendering"
:values='[{"title": "Element 1"}, {"title": "Element"}]'
>Inner HTML Caption</xvue-my-comp
>
</div>
<script>
const mySelector = document.querySelector("xvue-my-comp");
//listen to the event
mySelector.addEventListener("my-click", function (ev) {
console.log("#LISTEN");
alert(ev);
});
</script>
</body>
</html>
Custom WebComponents wrapper
import HTMLParsedElement from "html-parsed-element";
import { createApp, h, toHandlerKey } from "vue";
import { snakeCase, camelCase } from "lodash";
let registeredComponents = {};
const tagPrefix = "xvue-"; // Prefix for Custom Elements (HTML5)
const vueTagPrefix = "xvue-init-"; // Prefix for VUE Components - Must not be tagPrefix to avoid loops
// We use only one file as web component for simplicity
// and because codesandbox doesen't work with dynamic requires
const fileName = "MyComp.vue";
const webComponent = require(`../wcs/MyComp.vue`);
const componentName = snakeCase(
camelCase(
// Gets the file name regardless of folder depth
fileName
.split("/")
.pop()
.replace(/.ce/, "")
.replace(/\.\w+$/, "")
)
).replace(/_/, "-");
// store our component
registeredComponents[componentName] = {
component: webComponent.default
};
export default {
init() {
// HTMLParsedElement is a Polyfil Library (NPM Package) to give a clean parsedCallback as the browser
// default implementation has no defined callback when all props and innerHTML is available
class VueCustomElement extends HTMLParsedElement {
// eslint-disable-next-line
constructor() {
super();
}
parsedCallback() {
console.log("Legacy Component Init", this);
if (!this.getAttribute("_innerHTML")) {
this.setAttribute("data-initialized", this.innerHTML);
}
let rootNode = this;
let vueTagName = this.tagName
.toLowerCase()
.replace(tagPrefix, vueTagPrefix);
let compBaseName = this.tagName.toLowerCase().replace(tagPrefix, "");
let compConfig = registeredComponents[compBaseName];
// Optional: Shadow DOM Mode. Can be used by settings __useShadowDom in component vue file
if (compConfig.component.__useShadowDom) {
rootNode = this.attachShadow({ mode: "open" });
document
.querySelectorAll('head link[rel=stylesheet][href*="core."]')
.forEach((el) => {
rootNode.appendChild(el.cloneNode(true));
});
}
if (vueTagName) {
// If we have no type we do nothing
let appNode = document.createElement("div");
// let appNode = rootNode;
appNode.innerHTML +=
"<" +
vueTagName +
">" +
this.getAttribute("data-initialized") +
"</" +
vueTagName +
">"; // #TODO: Some issues with multiple objects being created via innerHTML and slots
rootNode.appendChild(appNode);
// eslint-disable-next-line #typescript-eslint/no-this-alias
const self = this;
function createCustomEvent(name, args) {
return new CustomEvent(name, {
bubbles: false,
cancelable: false,
detail: args.length === 1 ? args[0] : args
});
}
const createEventProxies = (eventNames) => {
const eventProxies = {};
if (eventNames) {
console.log("eventNames", eventNames);
// const handlerName = toHandlerKey(camelCase(name));
eventNames.forEach((name) => {
const handlerName = name;
eventProxies[handlerName] = (...args) => {
this.dispatchEvent(createCustomEvent(name, args));
};
});
}
return eventProxies;
};
const eventProxies = createEventProxies(compConfig.component.emits);
this._props = {};
const app = createApp({
render() {
let props = Object.assign({}, self._props, eventProxies);
// save our attributes as props
[...self.attributes].forEach((attr) => {
let newAttr = {};
newAttr[attr.nodeName] = attr.nodeValue;
props = Object.assign({}, props, newAttr);
});
console.log("props", props);
delete props.dataVApp;
return h(compConfig.component, props);
},
beforeCreate: function () {}
});
// Append only relevant VUE components for this tag
app.component(vueTagPrefix + compBaseName, compConfig.component);
this.vueObject = app.mount(appNode);
console.log("appNode", app.config);
}
}
disconnectedCallback() {
if (this.vueObject) {
this.vueObject.$destroy(); // Remove VUE Object
}
}
adoptedCallback() {
//console.log('Custom square element moved to new page.');
}
}
// Register for all available component tags ---------------------------
// Helper to Copy Classes as customElement.define requires separate Constructors
function cloneClass(parent) {
return class extends parent {};
}
for (const [name, component] of Object.entries(registeredComponents)) {
customElements.define(tagPrefix + name, cloneClass(VueCustomElement));
}
}
};
If I whack this into your render method, it all works fine.
Thus your problem is not Web Components or Events related
document.addEventListener("foo", (evt) => {
console.log("FOOd", evt.detail, evt.composedPath());
});
self.dispatchEvent(
new CustomEvent("foo", {
bubbles: true,
composed: true,
cancelable: false,
detail: self
})
);
addendum after comment:
Your code has bubbles:false - that will never work
This code properly emits an Event
You need to look into how you trigger your proxy code
const createCustomEvent = (name, args = []) => {
return new CustomEvent(name, {
bubbles: true,
composed: true,
cancelable: false,
detail: !args.length ? self : args.length === 1 ? args[0] : args
});
};
const createEventProxies = (eventNames) => {
const eventProxies = {};
if (eventNames) {
eventNames.forEach((name) => {
const handlerName =
"on" + name[0].toUpperCase() + name.substr(1).toLowerCase();
eventProxies[handlerName] = (...args) => {
appNode.dispatchEvent(createCustomEvent(name));
};
});
document.addEventListener("foo",evt=>console.warn("foo!",evt));
appNode.dispatchEvent(createCustomEvent("foo"));
console.log(
"#eventProxies",
eventProxies,
self,
"appnode:",
appNode,
"rootNode",
rootNode
);
}
return eventProxies;
};
I guess you could try to use Vue3's (since 3.2) defineCustomElement
import CustomElement from './some/path/CustomElement.ce.vue'
const CustomElementCE = defineCustomElement(CustomElement)
customElements.define('custom-element', CustomElementCE);
More on that here: Vue web components
I found one possible solution. As stated in the vue docs we can use static event listeners that are passed as props. They have to be prefixed with "on" i.E. onMyevent for event name my-event. Though it works fine - the downside is that I can't pass any arguments as I don't see where to get them from.
One strange thing is that I cant add this event to the instance. Something like self.dispatchEvent() doesn't work when placed inside createEventProxies method. So I have to stick to document.dispatchEvent()
Working CodeSandbox.
const createCustomEvent = (name, args = []) => {
return new CustomEvent(name, {
bubbles: false,
composed: true,
cancelable: false,
detail: !args.length ? self : args.length === 1 ? args[0] : args
});
};
const createEventProxies = (eventNames) => {
const eventProxies = {};
if (eventNames) {
eventNames.forEach((name) => {
const handlerName =
"on" + name[0].toUpperCase() + name.substr(1).toLowerCase();
eventProxies[handlerName] = (...args) => {
document.dispatchEvent(createCustomEvent(name));
};
});
console.log("#eventProxies", eventProxies);
}
return eventProxies;
};
The method this.fillForm() of my Vue component C (EditComment) is called twice, but I'm having trouble understanding why. I tried using uuid, but don't know how it helps knowing that beforeCreate is called twice.
There are 3 components. Here are the relevant parts:
Component A:
showCommentDialog: function(recordNumber) {
this.$modal.show(
ShowComment,
{
commentRecId: recordNumber
},
{
draggable: true,
width: 400,
height: 250
},
{
closed: function(event) {}
}
);
Component B:
<EditComment v-bind:comment-rec-id="commentRecId" v-if="showEdit"></EditComment>
</div>
</template>
<script>
import * as $ from "jquery";
import EditComment from "./EditComment.vue";
export default {
props: ["commentRecId"],
data: function() {
with this function
editItem: function(){
this.showEdit = true;
console.log("editItem function() called!");
var playerID = this.$store.state.selectedPlayer.ID;
this.$modal.show(
EditComment,
{
text: playerID
},
{
draggable: true,
width: 400,
height: 400
})
}
Component C:
<script>
import * as $ from "jquery";
import DatePicker from "vue2-datepicker";
let uuid = 0;
export default {
props: ["text", "commentRecId"],
beforeCreate() {
this.uuid = uuid.toString();
uuid += 1;
console.log("beforeCreate() uuid: " + this.uuid);
},
components: { DatePicker },
data: function() {
return {
commentData: {
comment: "",
customDate: ""
},
selectedCategory: "",
lang: {
default: "en"
},
}
},
mounted: function() {
// console.log("this._uid: " + this._uid);
this.fillForm();
},
methods: {
fillForm: function(){
Any help is appreciated.
If I understand correctly your problem, you fired component C with this section of editItem method:
this.$modal.show(
EditComment,
{
text: playerID
},
{
draggable: true,
width: 400,
height: 400
})
if I'm right, you have a mistake in your method:
when you use v-if, vue fires your component and it resets all values which you passed it before, like props, data values (except your uuid because it's not a data property)
so in your method you fire your component twice with :
this.showEdit = true;
anyway...for solution, please try this way:
first, use "v-show" instead of "v-if"
then show your component by this.$modal.show()
I hope can help
So, I was working with React Native and found a very silly problem which I can't seem to get my head around. I have declared an array of objects (storeVar here) with some hardcoded value. When I try to loop through it using the javascript map function I get only the value only at the first index. The code is below -
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
TextInput,
ScrollView,
AsyncStorage,
TouchableOpacity
} from 'react-native';
import Note from './note.js'
// -------------------------------------------------------------------------
const storeVar = [
{
'd' : '',
'n' : 'Hello'
},
{
'd' : '',
'n' : 'Awesome'
},
];
export default class reactNativePractise extends Component {
constructor(props) {
super(props);
this.state = {
noteArray : [
{
'date' : '',
'note' : ''
}
],
};
}
componentDidMount() { this.onLoad(); }
onLoad = async()=> {
storeVar.map(async(value, index) => {
try {
var d = new Date();
// const storedVal = [await AsyncStorage.getItem(storeVar[index].n)];
alert("Key is : " + JSON.stringify(storeVar[index-1].n, null, 4));
// this.state.noteArray.push( {date :d.getFullYear() + "/" + (d.getMonth()+1) + "/" + d.getDate(), note : storedVal[index]} );
}
catch(error) { alert('Error' + error) }
});
}
Only Hello is displayed and not the text Awesome. Any help would be greatly appreciated.
Don't use index-1, use
JSON.stringify(storeVar[index].n, null, 4));
that index will start from 0, so doing -1 of 0 will result to -1, and it will fail to access -1th element of array, when index becomes 1 and 1 - 1 will be 0 at that time its printing the Hello (0th index element).