How to use custom fonts in TinyMCE using tinymce-vue package for VueJS? - vue.js

I'm trying to use custom fonts stored in my assets/fonts folder with TinyMCE, but it seems like it can't render the font, except for the format selector. The content doesn't display the font correctly, although the font selector shows the font is being applied (in the title, for example).
Here's the code so far:
<template>
<tinymce-editor
:key="id"
:initial-value="initialValue"
:resize="false"
:init="{
selector: 'textarea#format-custom',
height: height,
plugins: 'table wordcount link lists',
menubar : false,
statusbar : false,
toolbar: 'undo redo | bold italic| styleselect | alignleft aligncenter alignright alignjustify | numlist bullist | fontselect',
content_css: [ '//www.tiny.cloud/css/codepen.min.css' ],
content_style: 'body { font-family: Roboto Light; font-size: 16px; }' + '.left { text-align: left; }' + 'img.left { float: left; }' + 'table.left { float: left; }' + '.right { text-align: right; }' + 'img.right { float: right; }' + 'table.right { float: right; }' + '.center { text-align: center; }' + 'img.center { display: block; margin: 0 auto; }' + 'table.center { display: block; margin: 0 auto; }' + '.full { text-align: justify; }' + 'img.full { display: block; margin: 0 auto; }' + 'table.full { display: block; margin: 0 auto; }' + '.bold { font-weight: bold; }' + '.italic { font-style: italic; }' + '.underline { text-decoration: underline; }' + '.title { font-family: Raleway Bold; font-size: 26px; }' + '.subtitle { font-family: Roboto Medium; font-size: 20px; }',
formats: {
alignleft: { selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'left' },
aligncenter: { selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'center' },
alignright: { selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'right' },
alignfull: { selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'full' },
bold: { inline: 'span', classes: 'bold' }, italic: { inline: 'span', classes: 'italic' },
titleformat: { inline: 'span', attributes: { title: 'Title'} , classes: 'title', },
subtitleformat: { inline: 'span', attributes: { title: 'SubTitle'} , classes: 'subtitle' } },
style_formats: [ { title: 'Title', format: 'titleformat' }, { title: 'SubTitle', format: 'subtitleformat' } ]
}"
api-key="no-api-key"
model-events="change keydown blur focus paste"
#input="handleInput"
#error="handleError"
/>
</template>
<script>
import Editor from '#tinymce/tinymce-vue'
export default {
components: {
'tinymce-editor': Editor
},
props: {
initialValue: {
type: String,
default: null
},
id: {
type: String,
default: null
},
height: {
type: String,
default: '100%'
}
},
methods: {
handleInput (value) {
this.$emit('input', value)
},
handleError (err) {
console.error(err)
}
}
}
</script>
Here's a print:

If you want to use custom fonts you need to:
Load them into TinyMCE via content_css
Include the fonts via the font_formats configuration option: https://www.tiny.cloud/docs/configure/editor-appearance/#font_formats
You appear to be loading the CSS from the TinyMCE demo site:
content_css: [ '//www.tiny.cloud/css/codepen.min.css' ],
This is likely not something you want to do as our demo CSS would likely not be appropriate for your web site/application. I would change that to use an appropriate CSS for your site. In that CSS you can load a custom font. For example:
#import url('https://fonts.googleapis.com/css?family=Lato');
Note: The reason the TinyMCE menu can render the font is you are likely loading the font on the page that includes TinyMCE but not in TinyMCE itself. The editor content is in its own iframe but the menus are part of the main page. If you inspect the DOM you will be able to see this layout. Using content_css allows you to inject CSS into the editor's iframe.

Related

Media Embed Videos not display

I installed ckeditor5 & mediaembed on Laravel 8
If you actually put a YouTube link, the preview will be activated.
Here is ckeditor.js
ClassicEditor.defaultConfig = {
extraPlugins: [ ImageResize ],
toolbar: {
items: [
......
]
},
image: {
toolbar: [
....
],
},
table: {
contentToolbar: [
'tableColumn',
'tableRow',
'mergeTableCells'
]
},
// This value must be kept in sync with the language defined in webpack.config.js.
language: 'ko',
mediaEmbed: {
// configuration...
previewsInData: true
}
};
After entering the YouTube link and completing the post,
In fact, when the post is opened, the code below appears and the video is not visible.
<figure class="media"><oembed url="https://www.youtube.com/watch?v=4lA57PRYxhI"></oembed></figure>
I've read the documentation several times but I can't figure out what I'm doing wrong.
I also want to add a Provider
I'm writing the code as below, and I'm wondering if this is the right approach to do it?
mediaEmbed: {
previewsInData: true,
removeProviders: ['youtube'],
providers: [
{
name: 'youtube',
url: [
/^(?:m\.)?youtube\.com\/watch\?v=([\w-]+)/,
/^(?:m\.)?youtube\.com\/v\/([\w-]+)/,
/^youtube\.com\/embed\/([\w-]+)/,
/^youtu\.be\/([\w-]+)/
],
html: match => {
const id = match[ 1 ];
return (
'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' +
`<iframe src="https://www.youtube.com/embed/${ id }" ` +
'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>' +
'</iframe>' +
'</div>'
);
}
},
{
name: 'afreecaTV',
url: [
/^v\.afree\.ca\/ST\/([\w-]+)/,
/^vod\.afreecatv\.com\/([\w-]+)/,
/^play\.afreecatv\.com\/([\w-]+)/,
],
html: match => {
const id = match[ 1 ];
return (
'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' +
`<iframe src="https://openapi.afreecatv.com/oembed/embedinfo/${ id }" ` +
'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen>' +
'</iframe>' +
'</div>'
);
}
},
]
}
$(document).ready(function () {
// $(document).load(function () {
//adjust modal body sizes
document.querySelectorAll( 'oembed' ).forEach( element => {
// console.log(element);
// console.log(element.firstChild);
// Discard the static media preview from the database (empty the <div data-oembed-url="...">).
while ( element.firstChild ) {
element.removeChild( element.firstChild );
}
// Generate the media preview using Iframely.
iframely.load( element, element.dataset.oembedUrl ) ;
} );
});
So we want to help.
When I open a post, I want to show the video
When adding ExtraProvider, is it correct to do as above?

Vue 3 cli-service app: "Slot "default" invoked outside of the render function" warning when component with slots is imported from other component

MCVE
I have a Tabpane component that takes slots as input. When imported from the template it works as expected.
<Tabpane>
<div caption="I am div 1">Div 1</div>
<div caption="I am div 2">Div 2</div>
</Tabpane>
However when imported from an other component ( Composite in the example ), then it triggers the following warning:
Slot "default" invoked outside of the render function:
this will not track dependencies used in the slot. Invoke the slot function inside the render function instead.
// src/components/Composite.js
import { defineComponent, h } from "vue";
import Tabpane from "./Tabpane.vue";
export default defineComponent({
name: "Composite",
setup() {
const slots = [
h("div", { caption: "I am div 1" }, ["Div 1"]),
h("div", { caption: "I am div 2" }, ["Div 2"])
];
return () => h(Tabpane, {}, () => slots);
}
});
Solved.
The problem was that I called slots.default() from within setup, but not within the returned render function.
Also this component reflected a very beginner approach to reactivity. By now I know better. The old problematic solution is still there in src/components/Tabpane.vue.
The right solution that triggers no warning is:
// src/components/Tabpane2.vue
<script>
import { defineComponent, h, reactive } from "vue";
export default defineComponent({
name: "Tabpane2",
props: {
width: {
type: Number,
default: 400,
},
height: {
type: Number,
default: 200,
},
},
setup(props, { slots }) {
const react = reactive({
selectedTab: 0,
});
return () =>
h("div", { class: ["vertcont"] }, [
h(
"div",
{
class: ["tabs"],
},
slots.default().map((tab, i) =>
h(
"div",
{
class: {
tab: true,
selected: i === react.selectedTab,
},
onClick: () => {
react.selectedTab = i;
},
},
[tab.props.caption]
)
)
),
h(
"div",
{
class: ["slotscont"],
style: {
width: `${props.width}px`,
height: `${props.height}px`,
},
},
slots.default().map((slot, i) =>
h(
"div",
{
class: {
slot: true,
active: react.selectedTab === i,
},
},
[slot]
)
)
),
]);
},
});
</script>
<style>
.tab.selected {
background-color: #efe;
border: solid 2px #afa !important;
border-bottom: transparent !important;
}
.tab {
background-color: #eee;
}
.tabs .tab {
padding: 5px;
margin: 2px;
border: solid 2px #aaa;
border-radius: 8px;
border-bottom: transparent;
cursor: pointer;
user-select: none;
transition: all 0.5s;
color: #007;
}
.tabs {
display: flex;
align-items: center;
margin-left: 5px;
}
.vertcont {
display: flex;
flex-direction: column;
margin: 3px;
}
.slotscont {
position: relative;
overflow: scroll;
padding: 5px;
border: solid 1px #777;
}
.slot {
visibility: hidden;
position: absolute;
}
.slot.active {
visibility: visible;
}
</style>
Slots need to be invoked within the render function and or the <template> box to ensure they keep their reactivity.
A full explanation can be found in this post: https://zelig880.com/how-to-fix-slot-invoked-outside-of-the-render-function-in-vue-3

Add Font Style and font size in Vue2-editor

I wanted to add font style here like there is an option if you want the font to be arial,san-serif etc... But now the font and font sizes not display in vue2-editor. Can anyone help me?
You can access the code here :
https://codesandbox.io/s/liz23?file=/src/App.vue:0-553
this is the code:
<template>
<div id="app">
<vue-editor v-model="content"></vue-editor>
<div v-html="content"></div>
</div>
</template>
<script>
import { VueEditor } from "vue2-editor";
export default {
components: {
VueEditor
},
data() {
return {
content: "<p>Some initial content</p>"
};
}
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* text-align: center; */
color: #2c3e50;
margin-top: 60px;
}
</style>
My answer is based of the excellent answers over at How to add font types on Quill js with toolbar options?. The only thing that I need to change is to use const fonts = Quill.import('formats/font');. vue2-editor exports the Quill object, so you can simply import it as such:
import { VueEditor, Quill } from "vue2-editor";
Then, it is mostly copy-and-pasting the solution in this posted answer. The vue2-editor unfortunately does not expose the default toolbar settings, so you will need to copy it verbatim from the source code and then add your font dropdown manually:
customToolbar: [
// Copied from source
// https://github.com/davidroyer/vue2-editor/blob/master/src/helpers/default-toolbar.js
[{ header: [false, 1, 2, 3, 4, 5, 6] }],
["bold", "italic", "underline", "strike"], // toggled buttons
[
{ align: "" },
{ align: "center" },
{ align: "right" },
{ align: "justify" }
],
["blockquote", "code-block"],
[{ list: "ordered" }, { list: "bullet" }, { list: "check" }],
[{ indent: "-1" }, { indent: "+1" }],
[{ color: [] }, { background: [] }],
["link", "image", "video"],
["clean"],
// This is what I have added
[{ 'font': fonts.whitelist }],
]
See proof-of-concept here: https://codesandbox.io/s/vue2-editor-demo-forked-35wo1?file=/src/App.vue

How to create the perfect autosize textarea?

I do auto expanding textarea.
The principle is this: I create a hidden div in which I place the input text, then in the updated () method I define the height of the div and apply the value to the textarea.
But there is one problem - there is text twitching, because First, the text crawls up, and then when the field is expanded, it returns to its place. As if the updated () method works late. By the same principle, I made a text field in the ReactJS there was no such effect.
What can do with it?
How it works: https://jsbin.com/zakavehewa/1/edit?html,css,js,console,output
<template>
<div class="textarea_wrap">
<textarea class="text_input textarea" v-model="value" ref="textarea"></textarea>
<div v-if="autoRow" class="text_input textarea shadow" ref="shadow">{{ value }}!</div>
</div>
</template>
<script>
export default {
props: {
autoRow: {
type: Boolean,
default: false
},
default: String
},
data () {
return {
value: this.default,
}
},
mounted() {
this.updateHeight()
},
updated() {
this.updateHeight()
},
methods: {
updateHeight() {
if (this.autoRow && this.$refs.shadow) {
this.$refs.textarea.style.height = this.$refs.shadow.clientHeight + 5 + 'px'
}
}
}
}
</script>
<style lang="scss" scoped>
.textarea_wrap {
position: relative;
}
.textarea {
line-height: 1.5;
min-height: 31px;
width: 100%;
font-family: inherit;
}
.shadow {
position: absolute;
left: -9999px;
pointer-events: none;
white-space: pre-wrap;
word-wrap: break-word;
resize: none;
}
</style>
Checkout https://github.com/wrabit/vue-textarea-autogrow-directive, it caters for rendering in hidden divs plus copying and pasting text.

Unexpected behaviour removing a child component (row)

Description:
I have a table with some products, each row is a custom vue <row> component.
Each element has a closing (removing) button that triggers the custom "remove" event. The main app listens to this event and removes the children (by index)
The row a part from some static text it contains an input with a number.
The problem:
The parent (Vue app) removes the row, but the value of the input is then moved (and replaces its previous value) to the input in the next row.
Expected behaviour:
I want to simply remove the item I do not care about the value of the text input once it's removed. It should not move its value to the next sibling.
I attach an example.
let row = Vue.component('row', {
name: 'row',
props: ['number', 'name', 'sq'],
data: () => ({
quantity: 0
}),
template: '<tr>' +
'<td>{{number}}</td>' +
'<td>{{name}}</td>' +
'<td><button v-on:click="quantity--">-</button><input type="text" :value="quantity"><button v-on:click="quantity++">+</button></td>' +
'<td><button v-on:click="remove">×</button></td>' +
'</tr>',
methods: {
remove: function() {
this.$emit('remove', this.quantity)
}
},
beforeMount() {
this.quantity = this.sq
}
})
new Vue({
el: "#app",
data: {
out: [],
rows: [{
name: "Icecream",
sq: 0
},
{
name: "Sugar cube",
sq: 50
},
{
name: "Peanut butter",
sq: 0
},
{
name: "Heavy cream",
sq: 0
},
{
name: "Cramberry juice",
sq: 0
}
]
},
methods: {
removeRow: function(index, quantity) {
this.out.push(`Removing row ${index} (${this.rows[index].name} | ${quantity} units)`)
this.rows.splice(index, 1)
}
},
computed: {
log: function() {
return this.out.join("\r\n")
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
h2 {
font-weight: bold;
margin-bottom: 10px;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
td {
padding: 4px 5px;
}
input {
width: 40px;
text-align: center;
}
h4 {
margin-top: 20px;
margin-bottom: 5px;
}
#log {
padding: 10px;
background: #20262E;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<h2>Cart:</h2>
<table>
<row v-for="(row, index) in rows" :number="index" :name="row.name" :sq="row.sq" v-on:remove="removeRow(index, $event)"></row>
</table>
<h4>Log</h4>
<pre id="log" v-html="log"></pre>
</div>
As #Bert mentioned in the comments.
The problem was that I was missing a key.
https://v2.vuejs.org/v2/api/#key
Adding it solved the problem
Thanks