Add RTL condition in head with Nuxt Js i18n - javascript

I would like to add a condition as follows
if(app.i18n.locale == 'ar')
I use Arabic with English in this project. If the current language is Arabic, bootstrap-rtl.css is added in the head, and if the current language en is called bootstrap.css, I have tried more than one method and did not succeed.

Add a file in plugins folder for example direction-control.js
export default ({ app }, inject) => {
const dir = () =>
app.i18n.locales.find((x) => x.code === app.i18n.locale)?.dir;
inject('dir', dir);
};
While your nuxt.config.js will need similar code to this
plugins: [
'~/plugins/direction-control', // your plugins file name
// other plugins
],
modules: [
[
'nuxt-i18n',
{
locales: [
{
code: 'en',
file: 'en.json',
dir: 'ltr',
name: 'English',
},
{
code: 'ar',
file: 'ar.json',
dir: 'rtl', // that will be passed to your app
name: 'عربي',
},
],
langDir: 'translations/',
defaultLocale: 'ar',
},
],
],
Now it's time to get dir
export default {
mounted() {
console.log(this.$dir()); // logs your direction 'ltr' or 'rtl'
console.log(this.$i18n.locale);
if (this.$i18n.locale == "ar") {
// make some action
}
},
}
or inside template like this
<template>
<div :dir="$dir()">
<Nuxt />
</div>
</template>

Related

PWA - Frontend doesn't update even after a hard refresh

Below is the configuration of our nuxt and nuxt-pwa configuration.
Nuxt pwa is recognising a new version available and we prompt user to do a hard refresh/reload.
And on reload - new UI also starts to work.
However, if we open the site in a new tab. A spinner gets shown & the latest frontend fails to load. And again, a hard refresh is required.
our frontend redirects to /dashboard on visiting localhost:8080 by default and this is getting loaded from serviceworker with cached data.
Please help us resolve this since this has been a critical issue for us.
Spinner seen on new tab opening :
export default {
ssr: false,
target: 'static',
head: {
titleTemplate: '',
title: 'NocoDB',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: './favicon-32.png' }
]
},
plugins: [
// plugins
],
buildModules: [
'#nuxtjs/vuetify',
'#nuxtjs/pwa'
],
modules: [
// Doc: https://axios.nuxtjs.org/usage
'#nuxtjs/axios',
'vue-github-buttons/nuxt',
'#nuxtjs/toast'
],
axios: {
baseURL: process.env.NC_BACKEND_URL || (process.env.NODE_ENV === 'production' ? '..' : 'http://localhost:8080')
},
router: {
mode: 'hash',
base: process.env.NODE_ENV === 'production' ? './' : '',
middleware: ['auth']
},
vuetify: {
defaultAssets: {
icons: false
},
optionsPath: '#/config/vuetify.options.js',
treeShake: true,
customVariables: ['./config/variables.scss']
},
build: {
parallel: true,
plugins: [
new MonacoEditorWebpackPlugin({
languages: ['sql', 'json', 'javascript'],
features: ['!gotoSymbol']
})
],
extend(config, { isDev, isClient }) {
if (isDev) {
config.devtool = isClient ? 'source-map' : 'inline-source-map'
}
config.externals = config.externals || {}
config.externals['#microsoft/typescript-etw'] = 'FakeModule'
return config
}
},
pwa: {
workbox: {
assetsURLPattern: /\/_nuxt\//,
config: { debug: true }
},
icon: {
publicPath: './'
},
manifest: {
name: 'NocoDB',
start_url: '../?standalone=true',
theme_color: '#ffffff'
}
}
}
Lighthouse report :
Github issue reference : https://github.com/nuxt-community/pwa-module/issues/501
You need to set service worker which clears cache based on versions.
Setup service-worker.js as below. and update LATEST_VERSION on changes you deploy.
// service-worker.js
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
workbox.core.setCacheNameDetails({ prefix: 'pwa' })
//Change this value every time before you build to update cache
const LATEST_VERSION = 'v1.0.1'
self.addEventListener('activate', (event) => {
console.log(`%c ${LATEST_VERSION} `, 'background: #ddd; color: #0000ff')
if (caches) {
caches.keys().then((arr) => {
arr.forEach((key) => {
if (key.indexOf('pwa-precache') < -1) {
caches.delete(key).then(() => console.log(`%c Cleared ${key}`, 'background: #333; color: #ff0000'))
} else {
caches.open(key).then((cache) => {
cache.match('version').then((res) => {
if (!res) {
cache.put('version', new Response(LATEST_VERSION, { status: 200, statusText: LATEST_VERSION }))
} else if (res.statusText !== LATEST_VERSION) {
caches.delete(key).then(() => console.log(`%c Cleared Cache ${LATEST_VERSION}`, 'background: #333; color: #ff0000'))
} else console.log(`%c Great you have the latest version ${LATEST_VERSION}`, 'background: #333; color: #00ff00')
})
})
}
})
})
}
})
workbox.core.skipWaiting();
workbox.core.clientsClaim();
self.__precacheManifest = [].concat(self.__precacheManifest || [])
// workbox.precaching.suppressWarnings()
workbox.precaching.precacheAndRoute(self.__precacheManifest, {})
Don't know for nuxt, but for angular we need to bump the version element of ngsw-config.json on each deploy (1, 2, 3, ...) and also call SwUpdate.checkForUpdate() on startup. Only then the new version of the app will be retrieved from the server.

How to enforce first Node to be heading H1 in tiptap editor?

I am building a simple rich text editor for writing blogs. I want to enforce that first Node always be the title (h1 tag) which user should not be able to delete!
Is there a way to achieve this?
You can achieve this via Custom Document, where you can define the document structure. Tip tap has an example page here https://tiptap.dev/examples/custom-document.
You could put it in content when initializing:
const editor = useEditor({
extensions: [
StarterKit.configure({
heading: {
levels: [1, 2],
},
}),
Dropcursor.configure({
color: "#4B5563",
}),
Placeholder.configure({
showOnlyWhenEditable: true,
placeholder: "Write something or press Enter to add a new block",
}),
CodeBlockLowlight.configure({
lowlight,
}),
TaskList,
CustomTaskItem,
ListItem,
Blockquote,
CustomOrderedList,
CustomHorizontalRule,
Table.configure({
resizable: true,
}),
TableRow,
TableHeader,
TableCell,
],
editorProps: {
attributes: {
class: "focus:outline-none subpixel-antialiased",
},
},
autofocus: true,
// THIS
content: `
<h1>Hello there </h1>
`,
})
Extend Document:
const CustomDocument = Document.extend({
// https://tiptap.dev/api/schema#content
content: 'heading block*',
})
Load the CustomDocument and add default placeholder for Heading:
new Editor({
extensions: [
CustomDocument,
Placeholder.configure({
placeholder: ({ node }) => {
if (node.type.name === 'heading' && node.attrs.level == 1) {
return 'H1 placeholder here';
}
},
}),
]
})
After that, you have to find a solution for prevent bold/italic/H2/H3/etc. on your H1. If you have a solution for it, please give the solution in comment.

Fetching data from an API works locally but not live

First time building a site with Nuxt.js and I'm requesting data from an API that works great locally but when I access the site through Netlify, none of the code seems to run. I'm assuming this has something to do with my misunderstanding of how static sites work.
What I'm trying to do is check if there is a browser cookie, if not create one and fetch the API to get the user's general location and check their city against an array of cities in NY. If there is a match then close the v-banner. If there is already a browser cookie then close the v-banner.
Abridged index.vue:
<template>
<v-app v-bind:class="{ alertOpen: alertOpen }">
<v-main>
<v-banner
class="state-alert"
transition="slide-y-transition"
v-bind:class="{ alertOpen: alertOpen }"
>
By our calculations, it looks like you might be visiting our website from outside of New York. Unfortunately at this time, we can't sell our Granola outside of New York. If you are buying a gift for someone with a New York address then please proceed.
<template>
<v-btn
icon
color="alert"
v-on:click="alertOpen = false"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</template>
</v-banner>
</v-main>
</v-app>
</template>
<script>
let nyList = ['Albany', 'Amsterdam', 'Auburn', 'Batavia', 'Beacon', 'Binghamton', 'Buffalo', 'Canandaigua', 'Cohoes', 'Corning', 'Cortland', 'Dunkirk', 'Elmira', 'Fulton', 'Geneva', 'Glen Cove', 'Glens Falls', 'Gloversville', 'Hornell', 'Hudson', 'Ithaca', 'Jamestown', 'Johnstown', 'Kingston', 'Lackawanna', 'Little Falls', 'Lockport', 'Long Beach', 'Mechanicville', 'Middletown', 'Mount Vernon', 'New Rochelle', 'New York', 'Newburgh', 'Niagara Falls', 'North Tonawanda', 'Norwich', 'Ogdensburg', 'Olean', 'Oneida', 'Oneonta', 'Oswego', 'Peekskill', 'Plattsburgh', 'Port Jervis', 'Poughkeepsie', 'Rensselaer', 'Rochester', 'Rome', 'Rye', 'Salamanca', 'Saratoga Springs', 'Schenectady', 'Sherrill', 'Syracuse', 'Tonawanda', 'Troy', 'Utica', 'Watertown', 'Watervliet', 'White Plains', 'Yonkers'];
export default ({
head() {
return {
script: [{
body: true
}]
}
},
data() {
return {
geoData: [],
alertOpen: true
}
},
async fetch() {
const locationCookie = this.$cookies.get('location-cookie');
if(!locationCookie) {
console.log('no cookie');
this.$cookies.set('location-cookie', 'true', {
path: '/',
maxAge: 31556952
});
this.geoData = await fetch(
'https://ipgeolocation.abstractapi.com/v1/?api_key=1eef312cdda9428cac26815c9d3bdd26'
).then(res => res.json());
var vm = this;
compareCity(this.geoData.city);
function compareCity(city) {
var i;
for(i = 0; i < nyList.length; i++) {
if(nyList[i].toLowerCase().replace(/\s/g, '') == city.toLowerCase().replace(/\s/g, '')) {
console.log(city);
vm.alertOpen = false;
}
}
}
} else {
console.log('yes cookie');
this.alertOpen = false;
}
}
});
</script>
Not sure if ya'll also need to see the nuxt.config.js:
export default {
mode: "universal",
// Global page headers (https://go.nuxtjs.dev/config-head)
head: {
title: 'homemade-crunch',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' }
],
link: [
{ rel: "preconnect", href: "https://app.snipcart.com" },
{ rel: "preconnect", href: "https://cdn.snipcart.com" },
{ rel: 'stylesheet', href: 'https://cdn.snipcart.com/themes/v3.0.23/default/snipcart.css', defer: true },
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Anton&family=Open+Sans&display=swap' }
],
script: [
{ src: "https://cdn.snipcart.com/themes/v3.0/default/snipcart.js", defer: true }
]
},
// Global CSS (https://go.nuxtjs.dev/config-css)
css: [
],
// Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)
plugins: [
'#plugins/vuetify'
],
// Auto import components (https://go.nuxtjs.dev/config-components)
components: true,
// Modules for dev and build (recommended) (https://go.nuxtjs.dev/config-modules)
buildModules: [],
// Modules (https://go.nuxtjs.dev/config-modules)
modules: [
'vue-scrollto/nuxt',
'cookie-universal-nuxt'
],
// Build Configuration (https://go.nuxtjs.dev/config-build)
build: {
},
server: {
port: 3333,
host: '0.0.0.0'
}
}
The git repo: https://github.com/mat148/Homemade-crunch
and live site: https://www.homemadecrunch.com/
Let me know if I've forgotten something or done something silly.
Any help would be greatly appreciated.
Thank you!
I decided to move my logic from the index.vue to a javascript file

What should my next.config.js file look like when exporting static HTML and using next-sass plugin?

I am trying to render my Next.js site to static HTML, but I don't know how to add both exportPathMap and the next-sass plugin to module.exports.
Here is my current next.config.js file:
module.exports = {
exportPathMap: async function (defaultPathMap) {
return {
'/': { page: '/' },
'/releases': { page: '/releases' },
'/release?=schlagen': { page: '/release', query: { slug: 'schlagen' }},
'/release?=nicholas-k-pt-1': { page: '/release', query: { slug: 'nicholas-k-pt-1' }},
'/release?=static-and-shades': { page: '/release', query: { slug: 'static-and-shades' }},
'/release?=digital-romance': { page: '/release', query: { slug: 'digital-romance' }},
'/release?=2011': { page: '/release', query: { slug: '2011' }},
'/artwork': { page: '/artwork' },
'/artwork/weekend-mixtapes': { page: '/weekendMixtapes' },
'/artwork/posters': { page: '/posters' }
}
}
}
const withSass = require('#zeit/next-sass')
module.exports = withSass()
How can I add both to my configuration?
According to the documentation at https://nextjs.org/docs/#customizing-babel-config
Multiple configurations can be combined together with function
composition. For example:
module.exports = (phase, {defaultConfig}) => {
return withSass({
exportPathMap: ...
})
}

Configure durandal for multiple SPA

I have a site, which technically consists of a number of separate SPAs.
App/Admin
App/Signup
App/MainApp
How should I organize the App folder and the gulp build to make this into 3-4 separate files / modules that are loaded when required.
Since I had to spend a whole day getting this to work, I will answer my own question.
First, prepare a loader site. This would be the main site that is actually loaded that is responsible from loading all the other sites.
App/main.js - Contains the requirejs setup
In this we would run app.setRoot('shell', 'entrance');
App/shell.js - contains the router config
return router.map([
{ route: 'Admin/*all', moduleId: "Admin/index", nav: true },
{ route: 'Signup/*all', moduleId: "Signup/index", nav: true },
{ route: '*all', moduleId: "MainApp/index", nav: true }
])
.buildNavigationModel()
.activate();
App/shell.html - contains the router usage and common header/footer/loading shield
<div style="height: 100%" data-bind="css: { loading: isLoading }">
<!-- ko compose: 'loading.html'--><!-- /ko -->
<div class="header">...</div>
<!-- ko router: { transition:'entrance' } --><!-- /ko -->
</div>
App/*.js - Files shared among all the SPAs
App/MainApp/index.js - Contains the sub router for that SPA
define(["require", "exports", "plugins/router"], function (require, exports, router) {
"use strict";
var childRouter = router.createChildRouter()
.makeRelative({
moduleId: 'MainApp',
fromParent: false //<!----- FALSE for MainApp, TRUE for other SPA
})
.map([
{ route: '', moduleId: "viewmodels/test", nav: true },
])
.mapUnknownRoutes("viewmodels/err", 'err')
.buildNavigationModel();
return (function () {
function class_1() {
this.router = childRouter;
}
return class_1;
}());
});
App/MainApp/index.html - Uses the router, and performs any rendering of submenues or whatever. Here the bare minimum:
<div data-bind="router: { }"></div>
App/Admin/index.js
Is identical to the MainApp.js, except for the fromParent set to true and moduleId set to the SPAs name.
This should work fine in debug mode now.
The gulp setup becomes:
gulpfile.js
var durandal = require('gulp-durandal');
gulp.task('build', [], function () {
return gulp.start(['build-admin', 'build-signup', 'build-mainapp', 'build-base']);
});
function buildModule(name) {
var path = name + '/';
return durandal({
baseDir: 'App',
output: name + '.js',
minify: true,
almond: false,
verbose: false,
moduleFilter: function (moduleName) {
return moduleName.indexOf(path) > -1;
},
rjsConfigAdapter: function (config) {
config.stubModules = config.exclude;
config.exclude = ["main"];
config.stubModules.push('text');
return config;
}
})
.pipe(gulp.dest('dist'));
}
gulp.task('build-admin', [], function () {
return buildModule('Admin');
});
gulp.task('build-signup', [], function () {
return buildModule('Signup');
});
gulp.task('build-mainapp', [], function () {
return buildModule('MainApp');
});
gulp.task('build-base', [], function () {
return durandal({
baseDir: 'App',
output: 'main.js',
minify: true,
verbose: false,
moduleFilter: function (moduleName) {
return moduleName.indexOf('Admin/') === -1 && moduleName.indexOf('MainApp/') === -1 && moduleName.indexOf('Signup/') === -1;
},
rjsConfigAdapter: function (config) {
config.stubModules = config.exclude;
config.exclude = [];
return config;
}
})
.pipe(gulp.dest('dist'));
});
This will now result in
dist/main.js
dist/Admin.js
dist/MainApp.js
dist/Signup.js
In your default.html (or whatever you call it), you need to tell it how to load these files:
<script>window.require = {
bundles: { 'MainApp': ['MainApp/index'],'Signup': ['Signup/index'], 'Admin': ['Admin/index'] }
};</script>
<script type="text/javascript" data-main="main" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.2.0/require.min.js"></script>
The reason for this being in the html files rather than the main.js, is that the bundles are only used for the compiled version.

Categories

Resources