How to use Google search on a docusaruas blog - docusaurus

I'm wondering how i might go about adding this search box snippet to a docusarus blog.
<script async src="https://cse.google.com/cse.js?cx=e2e7646659949450a">
</script>
<div class="gcse-search"></div>
I've googled lot but can't find any examples or anything close that I could hack on.
I've also tried swizzling the local search and also navbar items but could not figure it out.
I also tried to add it as a html item into the navbar but didn't notice any change.
I'm new to docusarus and not a front end developer, just trying to help get our blog off WordPress :)
Any help/pointers/references would be greatly appreciated.
Update
I seem to have one approach working by just using custom html item in the navbar.
Adding below script from Google:
scripts: [
{src:'https://cse.google.com/cse.js?cx=e2e7646659949450a', async: false, defer: false}
]
And then this item in the navbar:
items: [
...
{
type: "html",
position: "left",
value: '<div class="gcse-search"></div>',
},
...
],
This seems like the most simple to me as avoids having to swizzle anything. However i am having an issue in that i need to now f5 refresh the page for the search box to load for some reason. So i need to try figure that out before saying the html item in navbar approach 100% can work.
I am working in thie PR if ends up being useful to anyone: https://github.com/netdata/blog/pull/106/files

Probably looking at something like this:
export default function NavbarContent(): JSX.Element {
const mobileSidebar = useNavbarMobileSidebar();
const items = useNavbarItems();
const [leftItems, rightItems] = splitNavbarItems(items);
const searchBarItem = items.find((item) => item.type === 'search');
return (
<NavbarContentLayout
left={
// TODO stop hardcoding items?
<>
{!mobileSidebar.disabled && <NavbarMobileSidebarToggle />}
<NavbarLogo />
<NavbarItems items={leftItems} />
</>
}
right={
// TODO stop hardcoding items?
// Ask the user to add the respective navbar items => more flexible
<>
<NavbarItems items={rightItems} />
<NavbarColorModeToggle className={styles.colorModeToggle} />
<div className="gcse-search"></div>
</>
}
/>
);
}
In a swizzled Navbar/Content.
Then:
scripts: [
{
src: 'https://cse.google.com/cse.js?cx=<ID>',
async: true,
},
],
in your docusaurus.config.js. This is untested.

Related

Vue class components dynamically add component depending on answer from backend

So from the backend I get a array of objects that look kind of like this
ItemsToAdd
{
Page: MemberPage
Feature: Search
Text: "Something to explain said feature"
}
So i match these values to enums in the frontend and then on for example the memberpage i do this check
private get itemsForPageFeatures(): ItemsToAdd[] {
return this.items.filter(
(f) =>
f.page== Pages.MemberPage &&
f.feature != null
);
}
What we get from the backend will change a lot over time and is only the same for weeks at most. So I would like to avoid to have to add the components in the template as it will become dead code fast and will become a huge thing to have to just go around and delete dead code. So preferably i would like to add it using a function and then for example for the search feature i would have a ref on the parent like
<SearchBox :ref="Features.Search" />
and in code just add elements where the ItemsToAdd objects Feature property match the ref
is this possible in Vue? things like appendChild and so on doesn't work in Vue but that is the closest thing i can think of to kind of what I want. This function would basically just loop through the itemsForPageFeatures and add the features belonging to the page it is run on.
For another example how the template looks
<template>
<div class="container-fluid mt-3">
<div
class="d-flex flex-row justify-content-between flex-wrap align-items-center"
>
<div class="d-align-self-end">
<SearchBox :ref="Features.Search" />
</div>
</div>
<MessagesFilter
:ref="Features.MessagesFilter"
/>
<DataChart
:ref="Features.DataChart"
/>
So say we got an answer from backend where it contains an object that has a feature property DataChart and another one with Search so now i would want components to be added under the DataChart component and the SearchBox component but not the messagesFilter one as we didnt get that from the backend. But then next week we change in backend so we no longer want to display the Search feature component under searchbox. so we only get the object with DataChart so then it should only render the DataChart one. So the solution would have to work without having to make changes to the frontend everytime we change what we want to display as the backend will only be database configs that dont require releases.
Closest i can come up with is this function that does not work for Vue as appendChild doesnt work there but to help with kind of what i imagine. So the component to be generated is known and will always be the same type of component. It is where it is to be placed that is the dynamic part.
private showTextBoxes() {
this.itemsForPageFeatures.forEach((element) => {
let el = this.$createElement(NewMinorFeatureTextBox, {
props: {
item: element,
},
});
var ref = `${element.feature}`
this.$refs.ref.appendChild(el);
});
}
You can use dynamic components for it. use it like this:
<component v-for="item in itemsForPageFeatures" :is="getComponent(item.Feature)" :key="item.Feature"/>
also inside your script:
export default {
data() {
return {
items: [
{
Page: "MemberPage",
Feature: "Search",
Text: "Something to explain said feature"
}
]
};
},
computed: {
itemsForPageFeatures() {
return this.items.filter(
f =>
f.Page === "MemberPage" &&
f.Feature != null
);
}
},
methods: {
getComponent(feature) {
switch (feature) {
case "Search":
return "search-box";
default:
return "";
}
}
}
};

How to Fetch data to dynamically build out a custom Menu for a Layout?

What I would like to do is, call an endpoint to get the list of categories to display in the Sidebar's Menu. I'm not seeing anything in the Layout that would handle this. Am I missing something? What would be the correct way to do this?
Thanks #François, good to know I don't need to look any further in the Layout or Menu files.
Looking at the Demo source code, I see this approach in action using Reacts useState and useEffect.
Posted for others, this is in my Admin component:
const [categories, setCategories] = useState({categories: []});
useEffect(
() => {
dataProvider.getList('tools', {
sort: '',
pagination: {
page: 1,
perPage: 10
}
}).then(data =>
setCategories({categories: data['data']}))
},
[]
);

How do I get toolbar available items in CKEDITOR 5?

I wanted to configure the toolbar in CKEDITOR 5. I took a look at the documentation.
https://ckeditor5.github.io/docs/nightly/ckeditor5/latest/builds/guides/integration/configuration.html
Yet, the only script related to my question is:
Array.from( editor.ui.componentFactory.names );
It is way too difficult for a frontend programmer to understand. Where do I put this script? How do I output the results? Is there a detailed tutorial?
Matter fact, it would be nice if CKEDITOR simply put the available items in the documentation. That will save a hell lot of troubles.
Thanks!
You can put this code right in the body of code samples which you can find e.g. in CKEditor 5 Build's Basic API guide. For example:
ClassicEditor
.create( document.querySelector( '#editor' ) )
.then( editor => {
console.log( Array.from( editor.ui.componentFactory.names() ) );
} )
.catch( error => {
console.error( error );
} );
As #Szymon Cofalik mentioned in his answer – there's no single list of buttons which are available in all builds. CKEditor 5 builds may differ not only visually – they may also contain different plugins and hence different buttons. So, using that code snippet is the safest and future-proof solution.
you can use console.log( Array.from( editor.ui.componentFactory.names() ) ); which will give you:
["undo", "redo", "bold", "italic", "blockQuote", "ckfinder", "imageTextAlternative", "imageUpload", "heading", "imageStyle:full", "imageStyle:side", "link", "numberedList", "bulletedList", "mediaEmbed", "insertTable", "tableColumn", "tableRow", "mergeTableCells"]
Example code you can use to list available toolbar
var editor = ClassicEditor
.create(document.querySelector('#editor'), {
toolbar: ['headings', 'bold', 'italic', 'link', 'bulletedList', 'numberedList'],
heading: {
options: [
{modelElement: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph'},
{modelElement: 'heading1', viewElement: 'h1', title: 'Heading 1', class: 'ck-heading_heading1'},
{modelElement: 'heading2', viewElement: 'h2', title: 'Heading 2', class: 'ck-heading_heading2'},
{modelElement: 'heading', viewElement: 'h3', title: 'Heading 3', class: 'ck-heading_heading3'}
]
}
})
.then(function (editor) {
console.log(Array.from(editor.ui.componentFactory.names()));
});
For anyone coming here wondering how to make use of the Array.from(editor.ui.componentFactory.names()) solution (as described in the other answers) in Angular (e.g. Angular 8), here is a description. If you try to do it in ngOnInit or ngAfterViewInit, it is too early and you will get something like Cannot read property 'ui' of null. You need to listen for the ready event from the ckeditor and query the names at that point as follows.
In your component template code, give the editor an id and listen for the ready event:
<ckeditor
#editor
[editor]="Editor"
[config]="config"
(ready)="onEditorReady($event)">
</ckeditor>
Then in your component typescript code, add a #ViewChild annotation and implement onEditorReady as follows:
#ViewChild('editor', {static: false})
editorComponent: CKEditorComponent;
onEditorReady(event: any): void {
const toolbarItems = Array.from(this.editorComponent.editorInstance.ui.componentFactory.names());
console.log('Available toolbar items: ' + toolbarItems.join(', '));
}
You will then see something like this in the console:
Available toolbar items: undo, redo, bold, italic, blockQuote,
ckfinder, imageTextAlternative, imageUpload, heading, imageStyle:full,
imageStyle:side, indent, outdent, link, numberedList, bulletedList,
mediaEmbed, insertTable, tableColumn, tableRow, mergeTableCells
It is difficult to keep plugin names in one place in documentation because:
There are multiple builds which differs,
New plugins are developed and added.
If you want to check what toolbar items are available in the build you are currently using, open developer's console in the browser you are using and execute the quoted line of code
Array.from( editor.ui.componentFactory.names );
Of course, editor has to be the editor instance.
I hope this answers your question.
EDIT: Creating editor is described in the documentation too. But you have to assign editor instance to editor variable.
For example:
ClassicEditor
.create( document.querySelector( '#editor' ) )
.then( editor => {
window.editor = editor;
// Or alternatively you could paste that line here and look at console.
} );
Adding to #DestinyB answer - perhaps a simpler solution for Vue - just listen for #ready="onReady" on the ckeditor component, and in the onReady method:
onReady(event) {
console.log(Array.from(event.ui.componentFactory.names()));
},
Adding to #user2846469 Response, It can be achieved in vue.js simply by the sample below;
import ClassicEditorfrom '#ckeditor/ckeditor5-build-classic';
export default {
data() {
return {
editor: ClassicEditor,
editorData: '',
editorConfig: {}
},
mounted() {
console.log(this.editor.builtinPlugins.map( plugin => plugin.pluginName ));
}
}
}
In React
import { CKEditor } from '#ckeditor/ckeditor5-react';
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
export default class AddArticle extends Component {
render() {
return <CKEditor config={EditorConfig} editor={ClassicEditor} onReady={(event) => {
console.log(Array.from(event.ui.componentFactory.names()))}} />
}
}

Twitter typeahead.js not working in Vue component

I'm trying to use Twitter's typeahead.js in a Vue component, but although I have it set up correctly as tested out outside any Vue component, when used within a component, no suggestions appear, and no errors are written to the console. It is simply as if it is not there. This is my typeahead setup code:
var codes = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('code'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: contextPath + "/product/codes"
});
$('.typeahead').typeahead({
hint: true,
highlight: true,
minLength: 3
},
{
name: 'codes',
display: 'code',
source: codes,
templates: {
suggestion: (data)=> {
return '<div><strong>' + data.code + '</strong> - ' + data.name + '</div>';
}
}
});
I use it with this form input:
<form>
<input id="item" ref="ttinput" autocomplete="off" placeholder="Enter code" name="item" type="text" class="typeahead"/>
</form>
As mentioned, if I move this to a div outside Vue.js control, and put the Javascript in a document ready block, it works just fine, a properly formatted set of suggestions appears as soon as 3 characters are input in the field. If, however, I put the Javascript in the mounted() for the component (or alternatively in a watch, I've tried both), no typeahead functionality kicks in (i.e., nothing happens after typing in 3 characters), although the Bloodhound prefetch call is made. For the life of me I can't see what the difference is.
Any suggestions as to where to look would be appreciated.
LATER: I've managed to get it to appear by putting the typeahead initialization code in the updated event (instead of mounted or watch). It must have been some problem with the DOM not being in the right state. I have some formatting issues but at least I can move on now.
The correct place to initialize Twitter Typeahead/Bloodhound is in the mounted() hook since thats when the DOM is completely built. (Ref)
Find below the relevant snippet: (Source: https://digitalfortress.tech/js/using-twitter-typeahead-with-vuejs/)
mounted() {
// configure datasource for the suggestions (i.e. Bloodhound)
this.suggestions = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
identify: item => item.id,
remote: {
url: http://example.com/search + '/%QUERY',
wildcard: '%QUERY'
}
});
// get the input element and init typeahead on it
let inputEl = $('.globalSearchInput input');
inputEl.typeahead(
{
minLength: 1,
highlight: true,
},
{
name: 'suggestions',
source: this.suggestions,
limit: 5,
display: item => item.title,
templates: {
suggestion: data => `${data.title}`;
}
}
);
}
You can also find a working example: https://gospelmusic.io/
and a Reference Tutorial to integrate twitter typeahead with your VueJS app.

Vue.JS value tied on input having the focus

Is there a way to change a value in the model when an input gets/loses focus?
The use case here is a search input that shows results as you type, these should only show when the focus is on the search box.
Here's what I have so far:
<input type="search" v-model="query">
<div class="results-as-you-type" v-if="magic_flag"> ... </div>
And then,
new Vue({
el: '#search_wrapper',
data: {
query: '',
magic_flag: false
}
});
The idea here is that magic_flag should turn to true when the search box has focus. I could do this manually (using jQuery, for example), but I want a pure Vue.JS solution.
Apparently, this is as simple as doing a bit of code on event handlers.
<input
type="search"
v-model="query"
#focus="magic_flag = true"
#blur="magic_flag = false"
/>
<div class="results-as-you-type" v-if="magic_flag"> ... </div>
Another way to handle something like this in a more complex scenario might be to allow the form to track which field is currently active, and then use a watcher.
I will show a quick sample:
<input
v-model="user.foo"
type="text"
name="foo"
#focus="currentlyActiveField = 'foo'"
>
<input
ref="bar"
v-model="user.bar"
type="text"
name="bar"
#focus="currentlyActiveField = 'bar'"
>
...
data() {
return {
currentlyActiveField: '',
user: {
foo: '',
bar: '',
},
};
},
watch: {
user: {
deep: true,
handler(user) {
if ((this.currentlyActiveField === 'foo') && (user.foo.length === 4)) {
// the field is focused and some condition is met
this.$refs.bar.focus();
}
},
},
},
In my sample here, if the currently-active field is foo and the value is 4 characters long, then the next field bar will automatically be focused. This type of logic is useful when dealing with forms that have things like credit card number, credit card expiry, and credit card security code inputs. The UX can be improved in this way.
I hope this could stimulate your creativity. Watchers are handy because they allow you to listen for changes to your data model and act according to your custom needs at the time the watcher is triggered.
In my example, you can see that each input is named, and the component knows which input is currently focused because it is tracking the currentlyActiveField.
The watcher I have shown is a bit more complex in that it is a "deep" watcher, which means it is capable of watching Objects and Arrays. Without deep: true, the watcher would only be triggered if user was reassigned, but we don't want that. We are watching the keys foo and bar on user.
Behind the scenes, deep: true is adding observers to all keys on this.user. Without deep enabled, Vue reasonably does not incur the cost of maintaining every key reactively.
A simple watcher would be like this:
watch: {
user() {
console.log('this.user changed');
},
},
Note: If you discover that where I have handler(user) {, you could have handler(oldValue, newValue) { but you notice that both show the same value, it's because both are a reference to the same user object. Read more here: https://github.com/vuejs/vue/issues/2164
Edit: to avoid deep watching, it's been a while, but I think you can actually watch a key like this:
watch: {
'user.foo'() {
console.log('user foo changed');
},
},
But if that doesn't work, you can also definitely make a computed prop and then watch that:
computed: {
userFoo() {
return this.user.foo;
},
},
watch: {
userFoo() {
console.log('user foo changed');
},
},
I added those extra two examples so we could quickly note that deep watching will consume more resources because it triggers more often. I personally avoid deep watching in favour of more precise watching, whenever reasonable.
However, in this example with the user object, if all keys correspond to inputs, then it is reasonable to deep watch. That is to say it might be.
You can use a flat by determinate a special CSS class, for example this a simple snippet:
var vm = new Vue({
el: '#app',
data: {
content: 'click to change content',
flat_input_active: false
},
methods: {
onFocus: function(event) {
event.target.select();
this.flat_input_active = true;
},
onBlur: function(event) {
this.flat_input_active = false;
}
},
computed: {
clazz: function() {
var clzz = 'control-form';
if (this.flat_input_active == false) {
clzz += ' only-text';
}
return clzz;
}
}
});
#app {
background: #EEE;
}
input.only-text { /* special css class */
border: none;
background: none;
}
<!-- libraries -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<!-- html template -->
<div id='app'>
<h1>
<input v-model='content' :class='clazz'
#focus="onFocus($event)"
#blur="onBlur"/>
</h1>
<div>
Good luck
You might also want to activate the search when the user mouses over the input - #mouseover=...
Another approach to this kind of functionality is that the filter input is always active, even when the mouse is in the result list. Typing any letters modifies the filter input without changing focus. Many implementations actually show the filter input box only after a letter or number is typed.
Look into #event.capture.