CodeMirror on Vue3 has a problem when setValue is kicked - vuejs3

I'm trying to use CodeMirror on Vue3 and the problem occurs when I call doc.setValue().
The Problem is following:
Cursor position is broken when doc.setValue() is called
CodeMirror throws an exception when continuing editing
The exception is here.
Uncaught TypeError: Cannot read property 'height' of undefined
at lineLength (codemirror.js:1653)
at codemirror.js:5459
at LeafChunk.iterN (codemirror.js:5623)
at Doc.iterN (codemirror.js:5725)
at Doc.iter (codemirror.js:6111)
at makeChangeSingleDocInEditor (codemirror.js:5458)
at makeChangeSingleDoc (codemirror.js:5428)
at makeChangeInner (codemirror.js:5297)
at makeChange (codemirror.js:5288)
at replaceRange (codemirror.js:5502)
How should I solve this?
~~~
Versions are CodeMirror: 5.61.1, Vue.js: 3.0.11
My code is following:
index.html
<div id="app"></div>
<script src="./index.js"></script>
index.js
import { createApp } from 'vue';
import App from './App';
const app = createApp(App);
app.mount('#app');
App.vue
<template>
<div>
<button #click="click">Push Me</button>
<textarea id="codemirror"></textarea>
</div>
</template>
<script>
import CodeMirror from 'codemirror/lib/codemirror.js';
import 'codemirror/lib/codemirror.css';
// import codemirror resources
import 'codemirror/addon/mode/overlay.js';
import 'codemirror/mode/markdown/markdown.js';
import 'codemirror/mode/gfm/gfm.js';
export default {
data () {
return {
cm: null
}
},
mounted () {
this.cm = CodeMirror.fromTextArea(document.getElementById('codemirror'), {
mode: 'gfm',
lineNumbers: true,
});
},
methods: {
click (event) {
this.cm.getDoc().setValue('foo\nbar');
}
}
}
</script>
Thanks.
UPDATES
First, this problem also occurs when I used replaceRange() with multiline.
Unfortunately, I couldn't find any solution. So I tried to find another way.
My solution is recreating Codemirror instance with a textarea that has new content.
It works well.
// Remove old editor
this.cm.toTextArea();
// Get textarea
const textarea = document.getElementById('codemirror');
// Set new content
textarea.value = 'foo\nbar';
// Create new editor
this.cm = CodeMirror.fromTextArea(textarea, { /** options */ });

I found a method, you can use toRaw to get the original Object from Proxy,and this method can be also used in monaco-editor
import { toRaw } from 'vue'
import CodeMirror from 'codemirror/lib/codemirror.js';
import 'codemirror/lib/codemirror.css';
// import codemirror resources
import 'codemirror/addon/mode/overlay.js';
import 'codemirror/mode/markdown/markdown.js';
import 'codemirror/mode/gfm/gfm.js';
export default {
data () {
return {
cm: null
}
},
mounted () {
this.cm = CodeMirror.fromTextArea(document.getElementById('codemirror'), {
mode: 'gfm',
lineNumbers: true,
});
},
methods: {
click (event) {
toRaw(this.cm).setValue('foo\nbar');
}
}
}
Another way,you don't have to define cm in data, just use this.cm
data () {
return {
//cm: null
}
},

Related

Event #change is fire but cant get it in Vue Component Verte

can't catch the changed event
my template code:
<template>
<verte #change="changeColor(1)"></verte>
</template>
<script>
import Verte from 'verte';
export default {
components: { verte }
methods: {
changeColor(id) {
console.log(id)
},
}
</script>
in vue inspector event #change starts..
tried changing this line
from import Verte from 'verte';
to import VertePicker from 'verte';
...
but as a result, only the input event fires, but I just can’t catch the event on changed
maybe something else is needed to correctly trigger the event? and could this be a component bug?
Verte is not supporting change event.
You can call changeColor as below.
<template>
<verte v-model="colorVal"></verte>
</template>
<script>
import Verte from 'verte';
export default {
components: { Verte },
data: () => ({
colorVal: ''
}),
watch: {
// whenever colorVal changes, this function will run
colorVal(currentColor, previousColor) {
// you can call changeColor function here.
changeColor(currentColor);
}
},
methods: {
changeColor(id) {
console.log(id)
},
}
</script>

#vue/test-utils how to test v-if in vue3 when the parameter is imported

here are all code. parameter isInApp is imported from tools.ts. I had mount the vue component and add options ,In this case, how to mock isInApp value to finish the test
// a.vue
<template>
<div class="test" v-if="isInApp">test</div>
</template>
<script lang="ts">
import { isInApp } from './tools'
export default {
setup() {
return {
isInApp,
}
},
}
</script>
//tools.ts
export const isInApp = navigator.userAgent.indexOf('baidu') > -1
// a.spec.ts
import { mount } from '#vue/test-utils'
import a from './a.vue'
test('test',async ()=>{
const wrapper = mount(a,{
data(){
return{
isInApp: true, // I had set the data here, but it doesn't work, how to fixed it ?
}
}
})
expect(wrapper.find('.test').exists()).toBeTruthy() // Received: false
})

Stencil JS - How to also distribute a set of shared methods

I have this simple component that is compiled to a web-component by Stencil:
import { Component, h, Prop } from "#stencil/core";
import { IAuthLoginConfig } from "../../interfaces";
import { initAuth } from "../../services";
#Component({
tag: "login-button",
styleUrl: "login-button.css",
})
export class LoginButton {
#Prop() public baseUrl: string = "/oidc/v1/authorize";
#Prop() public config: IAuthLoginConfig;
render() {
return (
<button onClick={() => initAuth(this.config, this.baseUrl)}>
Sign in
</button>
);
}
}
On the button click a shared function initAuth(...) is called that this is imported from the services-directory:
import { IAuthLoginConfig } from "../interfaces";
export const initAuth = (authConfig: IAuthLoginConfig, baseUrl: string): void => {
const url = `${baseUrl}${buildParameters(authConfig)}`;
window.location.href = url
};
const buildParameters = ({ oauth2, oidc }: IAuthLoginConfig) => {
return 'some parameters';
};
Is there any (standard Stencil) way to also build and publish this file so that a user of our web-component library can import the exported functions and use/call them? In our use-case an end-user should be able to use methods in his/her own application directly that are also used in our web-components.
Other use-cases: shared variables, classes...
Thanks in advance!
You will have to manually export each object you want to be accessible in ./src/index.ts, e.g.:
export { initAuth } from './services';
// or
export * from './services';
This allows you to import it in the consuming project:
import { initAuth } from 'your-installed-package';
// or (depending on how you published)
import { initAuth } from 'your-installed-package/dist';

Decorators in Meteor 1.4

I am trying to understand how decorators work with Meteor 1.4. From what I read, this feature is supported.
Now, I am unsure how to actually implement it. From this blog, to decorate a class, I would require this code
export const TestDecorator = (target) => {
let _componentWillMount = target.componentWillMount;
target.componentWillMount = function () {
console.log("*** COMPONENT WILL MOUNT");
_componentWillMount.call(this, ...arguments);
}
return target;
}
Then use it as
import React, { Component } from 'react';
import { TestDecorator } from 'path/to/decorator.js';
#TestDecorator
export default class FooWidget extends Component {
//...
}
The code compiles, but nothing gets output when the component is being rendered.
What am I missing? How do I implement a decorator in Meteor? Is this the proper solution? What is the alternative?
Edit
I have tried this, and it still does not work
export const TestDecorator = (target) => {
console.log("*** THIS IS NOT EVEN DISPLAYED! ***");
target.prototype.componentWillMount = function () {
// ...
};
}
You are assigning your componentWillMount function to the class FooWidget instead of its prototype. Change that to target.prototype.componentWillMount = …. Besides, storing the previous componentWillMount is unnecessary in this case because it is undefined anyway.
Here is a full working example:
main.html
<head>
<title>decorators</title>
</head>
<body>
<div id="root"></div>
</body>
decorator.js
export const TestDecorator = (target) => {
console.log('Decorating…');
target.prototype.componentWillMount = function() {
console.log('Component will mount');
};
};
main.jsx
import React, { Component } from 'react';
import { render } from 'react-dom';
import { TestDecorator } from '/imports/decorator.js';
import './main.html';
#TestDecorator
class FooWidget extends Component {
render() {
return <h1>FooWidget</h1>;
}
}
Meteor.startup(function() {
render(<FooWidget/>, document.getElementById('root'));
});
.babelrc
{
"plugins": ["transform-decorators-legacy"]
}

How do I pass Meteor Subscription Data into React Component Props using ES6

Given this subscription, and the React Component below, how do I pass the subscription data in as props 'searchTerms'? Most of the documentation I can find refers to using mixins, but as far as I understand this is an anti pattern in ES6. Thanks!
constructor() {
super();
this.state = {
subscription: {
searchResult: Meteor.subscribe("search", searchValue)
}
}
}
render() {
return (
<div>
<SearchWrapper
searchTerms={this.state.subscription.searchResult}
/>
</div>
)
}
There are a couple options when it comes to creating containers in Meteor. My personal favorite is react-komposer.
Here's what your container would look like using react-komposer. Note that a container is simply a component that just passes data, and in the case of Meteor, provides reactivity.
After npm install --save react-komposer, create a container using:
import { Meteor } from 'meteor/meteor';
import React from 'react';
import { composeWithTracker } from 'react-komposer';
import Component from '../components/Component.jsx';
import { Collection } from '../../api/collection/collection.js';
// Creates a container composer with necessary data for component
const composer = ( props, onData ) => {
const subscription = Meteor.subscribe('Collection.list');
if (subscription.ready()) {
const collection = Collection.find().fetch(); // must use fetch
onData(null, {collection});
}
};
// Creates the container component and links to Meteor Tracker
export default composeWithTracker(composer)(Component);
The standard way of doing this is to use the react-meteor-data package.
meteor add react-meteor-data
Then create a container as follows:
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
import SearchWrapper from '../pages/SearchWrapper.jsx';
import { SearchResults } from '../../api/searchResults.js';
export default SearchResultContainer = createContainer(({ params }) => {
const { searchValue } = params;
const searchHandle = Meteor.subscribe('search', searchValue);
const loading = !searchHandleHandle.ready();
const results = SearchResults.find().fetch();
const resultsExist = !loading && !!list;
return {
loading,
results,
resultsExist,
};
}, SearchWrapper);
The returned object from the container is available as props in the wrapped component - SearchWrapper.

Resources