I have updated electron to version 19 but I have an error:
Cannot read properties of undefined (reading 'showOpenDialog')
I have a function to save file:
window.SaveFile = function (options, callBack) {
const {
name, //the name showed for the extension/s
extensions,
fileName
} = options;
let filters = [{ name: name, extensions: Array.isArray(extensions) ? extensions : [extensions] }];
dialog.showSaveDialog(null, { properties: ['saveFile'], name: [name], filters: filters, icon: 'main.ico', defaultPath: (fileName || name) + '.' + extensions }, (returnValue) => {
callBack(returnValue);
});
}
But dialog is undefined.
In index.js I have set the:
webPreferences: {
enableRemoteModule: true,
nodeIntegration: true
}
My required in electron is
const { ipcRenderer, dialog, remote, ipcMain } = require('electron');
but dialog and remote are undefined.
How can I solve this?
A while back, the remote module was deprecated (see Electron's breaking changes document). Because dialog is a main process module, you'll now have to open the dialog from the main process.
This, of course, complicates things a little bit, but with ipcRenderer and ipcMain this should be easy to achieve.
Assuming you have const { ipcRenderer } = require("electron"); in your renderer process:
window.SaveFile = function (options, callBack) {
ipcRenderer.invoke ("save-file", options).then (returnValue => {
callBack (returnValue);
});
}
And then in your main process:
ipcMain.handle ("save-file", async (event, options) => {
const {
name, //the name showed for the extension/s
extensions,
fileName
} = options;
let filters = [{ name: name, extensions: Array.isArray(extensions) ? extensions : [extensions] }];
var returnValue = await dialog.showSaveDialog(
null,
{
properties: ['saveFile'],
name: [name],
filters: filters,
icon: 'main.ico',
defaultPath: (fileName || name) + '.' + extensions
}
);
return returnValue;
});
Related
Bootcamp student here. I seem to be having trouble passing in the result of function renderBadge(license) to the generateREADME function. I am using inquirer to grab inputs, and generate a readme. The functions renderBadge() and licenseLink() is solely pertaining to license portion of the inquirer. However, I can't seem to pass this info along and display it to the generating function. Is there a way to do this? What am I doing wrong? Thanks in advance.
Upon function execution, the ${badge} seems to be undefined.
const inquirer = require("inquirer");
const fs = require("fs");
const generateREADME = ({ title, description, installation, usage, contributions, tests, license, github, email, badge,}) =>
`# ${title}
${badge}
## Description
${description}
(Rest of Readme Generation here)
`
inquirer
.prompt([
{
(other prompts here)
},
{
type: "list",
name: "license",
message: "What license is your project?",
choices: [
"Apache 2.0",
"Boost",
"GNU AGPL v3",
"MIT",
"Perl",
"other",
],
validate: (licenseInput) => {
if (licenseInput) {
return true;
} else {
console.log(`Please enter your project's license!`);
return false;
}
},
}
])
.then((answers) => {
const readmePageContent = generateREADME(answers);
renderBadge(answers)
fs.writeFile('README.md', readmePageContent, (err) => {
err ? console.log(err) : console.log('Successfully generated README!')
})
})
function renderBadge(license) {
let badge = ''
if (license === 'Apache 2.0') {
badge = `![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)]`
} else if (license === 'Boost') {
badge = `![License](https://img.shields.io/badge/License-Boost_1.0-lightblue.svg)]`
} else if (license === 'GNU APGL v3') {
badge = `![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)]`
} else if (license === 'MIT') {
badge = `![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)]`
} else if (license === 'Perl') {
badge = `![License: Artistic-2.0](https://img.shields.io/badge/License-Perl-0298c3.svg)]`
} else {
badge = ''
}
return badge;
generateREADME(badge)
}
The main issue here is the way you pass/accept arguments.
answers is an object containing the key/value-pairs an example could be:
const answers = {
title: "Hello World",
description: "Hello World, this is a test description",
// ...
license: "GNU AGPL v3",
};
You then pass the answers object to renderBadge.
renderBadge(answers)
However in renderBadge you expect license as the sole argument.
function renderBadge(license) {
// ...
}
Since you passed the whole answers object, that is what you will receive. Meaning that the licence parameter will contain the answers object.
To fix this you should pass just the license to renderBadge, not all the answers. So use renderBadge(answers.license) instead.
Alternatively you could also use object destructuring like you did in generateREADME, and define renderBadge as:
function renderBadge({ license }) {
// ...
}
If you choose to use object destructuring, you should still pass the full answers object to renderBadge, so renderBadge(answers).
The second, non-essential mistake is:
return badge;
generateREADME(badge) // never executed
The line after the return is never executed. This doesn't really break anything, since you didn't need that line anyways, so it can just be removed.
Lastly, and probably most importantly the order of the following lines are incorrect.
const readmePageContent = generateREADME(answers);
renderBadge(answers.license) // after the first fix
The renderBadge() call should be made before you render the readme file, the resulting contents should then be passed as argument to generateREADME().
const badge = renderBadge(answers.license);
const readmePageContent = generateREADME({ ...answers, badge });
This uses the spread syntax in object literals combined with the property definition shorthand to pass a single object, containing al the required arguments.
So the final result might look like this (with minimum changes):
const inquirer = require("inquirer");
const fs = require("fs");
const generateREADME = ({title, description, installation, usage, contributions, tests, license, github, email, badge,}) => (
`# ${title}
${badge}
## Description
${description}
(Rest of Readme Generation here)
`
);
inquirer.prompt([
{
(other prompts here)
},
{
type: "list",
name: "license",
message: "What license is your project?",
choices: [
"Apache 2.0",
"Boost",
"GNU AGPL v3",
"MIT",
"Perl",
"other",
],
validate: (licenseInput) => {
if (licenseInput) {
return true;
} else {
console.log(`Please enter your project's license!`);
return false;
}
},
}
]).then((answers) => {
const badge = renderBadge(answers.license); // pass only the license, not all the anwers
const readmePageContent = generateREADME({ ...answers, badge }); // pass the answers combined with the badge
fs.writeFile('README.md', readmePageContent, (err) => {
err ? console.log(err) : console.log('Successfully generated README!')
})
});
function renderBadge(license) {
let badge = ''
if (license === 'Apache 2.0') {
badge = `![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)]`
} else if (license === 'Boost') {
badge = `![License](https://img.shields.io/badge/License-Boost_1.0-lightblue.svg)]`
} else if (license === 'GNU APGL v3') {
badge = `![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)]`
} else if (license === 'MIT') {
badge = `![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)]`
} else if (license === 'Perl') {
badge = `![License: Artistic-2.0](https://img.shields.io/badge/License-Perl-0298c3.svg)]`
} else {
badge = ''
}
return badge; // removal of generateREADME
}
When you put a return statement in a function, all the code below it doesn't execute.
In your renderBadge function, you invoke generateREADME(badge) after the return statement. So it never runs:
function renderBadge(license) {
let badge = ''
...
} else {
badge = ''
}
return badge;
generateREADME(badge) // this line doesn't execute
}
To call generateREADME() with the renderBadge function output, you need to remove the generateREADME(badge) statement:
function renderBadge(license) {
let badge = ''
...
} else {
badge = ''
}
return badge;
}
After that, call the function in the relevant place and store the output in the variable like so:
...
.then((answers) => {
const readmePageContent = generateREADME(answers);
input = renderBadge(answers)
generateREADME(input)
Or you can do it succinctly like this:
...
generateREADME(renderBadge(answers))
...
I am trying to configure my Gatsby project like this starter https://github.com/gatsbyjs/gatsby-starter-blog
In my gatsby-node.js I have
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
const blogPost = path.resolve(`./src/templates/blog-post.js`)
return graphql(
`
{
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
limit: 1000
) {
edges {
node {
fields {
slug
}
frontmatter {
title
}
}
}
}
}
`
).then(result => {
if (result.errors) {
throw result.errors
}
// Create blog posts pages.
const posts = result.data.allMarkdownRemark.edges
posts.forEach((post, index) => {
const previous = index === posts.length - 1 ? null : posts[index + 1].node
const next = index === 0 ? null : posts[index - 1].node
createPage({
path: `blog${post.node.fields.slug}`,
component: blogPost,
context: {
slug: post.node.fields.slug,
previous,
next,
},
})
})
return null
})
}
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `allMarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
But I tried to run the dev server it output this error message
ERROR #85901 GRAPHQL
There was an error in your GraphQL query:
Cannot query field "fields" on type "MarkdownRemark".
File: gatsby-node.js:8:10
ERROR #11321 PLUGIN
"gatsby-node.js" threw an error while running the createPages lifecycle:
Cannot query field "fields" on type "MarkdownRemark".
GraphQL request:9:15
8 | node {
9 | fields {
| ^
10 | slug
But in fact what I had is allMarkdownRemark not MarkdownRemark. And I am totally copying what this starter is doing in its gatsby-node.js file
https://github.com/gatsbyjs/gatsby-starter-blog/blob/master/gatsby-node.js
Have no ideas how to fix it
My gatsby-config.js looks like this
"gatsby-plugin-page-transitions",
`gatsby-plugin-smoothscroll`,
`gatsby-plugin-netlify-cms`,
`gatsby-plugin-styled-components`,
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
`gatsby-plugin-offline`,
`gatsby-plugin-react-helmet`,
`gatsby-plugin-feed-mdx`,
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/content/blog`,
name: `blog`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/content/assets`,
name: `assets`,
},
},
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 590,
},
},
{
resolve: `gatsby-remark-responsive-iframe`,
options: {
wrapperStyle: `margin-bottom: 1.0725rem`,
},
},
`gatsby-remark-copy-linked-files`,
`gatsby-remark-smartypants`,
],
},
},
{
resolve: `gatsby-plugin-google-analytics`,
options: {
// edit below
// trackingId: `ADD YOUR TRACKING ID HERE`,
},
}
You are most likely seeing this issue because there are no markdown files found at any of the paths that gatsby-source-filesystem is pointing to in gatsby-config.js
According to nihgwu's comment on this issue:
the MarkdownRemark node type will only be created when there is a markdown node, or there will be no MarkdownRemark node type at all, so you can't query allMarkdownRemark
To solve your issue, make sure that there is as least one markdown file found in the ${__dirname}/content/blog folder.
If you do have markdown files in a different folder, make sure to add that location as another gatsby-source-filesystem entry in your gatsby-config.js.
In your gatsby-config.js file, make sure that gatsby-source-filesystem is able to detect a .md file in the directory where you put it. Then go to your gatsby-node.js file and change it to:
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
// Change the node internal type from 'allMarkdownRemark' to 'MarkdownRemark'
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
For more, check the docs on creating page programmatically.
In gatsby-node.js file, Change node.internal.type === allMarkdownRemark to MarkdownRemark in your oncreateNode function.
I was creating a gatsby plugin. On develop I don't get any errors or warning but I can't see my query in graphiql (or at http://localhost:8000/___graphql)
module.exports = {
siteMetadata: {
title: `Gatsby Default Starter`,
description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
author: `#gatsbyjs`,
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
{
resolve: 'gatsby-source-custom',
options: {
workspaceId: 'spaceyfi-dummy',
schemaId: 'custom-development'
}
},
// this (optional) plugin enables Progressive Web App + Offline functionality
// To learn more, visit: https://gatsby.dev/offline
// `gatsby-plugin-offline`,
],
}
Where this is my plugin folder structure in root
Inside the folder, index.js is empty file
and getsby-node.js looks something like this
const selfSdk = require('selfSdk')
function createNodeContent (data, id, type) {
const nodeId = createNodeId(id)
const nodeContent = JSON.stringify(data)
const contentDigest = createContentDigest(data)
const nodeMeta = {
id: nodeId,
parent: null,
children: [],
internal: {
type: type, // used to generate the resulting GraphQL query name
content: nodeContent,
contentDigest
}
}
const node = Object.assign({}, data, nodeMeta)
return node
}
exports.sourceNodes = async ({ actions, createNodeId, createContentDigest }, {
workspaceId,
schemaId
}) => {
const { createNode, setPluginStatus } = actions
const workspace = selfSdk.getWorkspace(workspaceId)
console.log(workspaceId, schemaId)
// If there is schemaId but we don't have itemId, load all posts
try {
const itemsList = await workspace.read(schemaId)
const type = `getPosts`
itemsList.forEach(({data, id}) => {
createNode(createNodeContent(data, id, type))
})
} catch (error) {
console.error(`Error Fetching data`)
console.error(error)
}
setPluginStatus({
status: {
lastFetched: Date.now()
}
})
}
This my grphiql
Gatsby is going to look for a gatsby-node.js in the root of your plugin folder.
plugins/
– your-plugin/
-- gatsby-node.js
-- package.json
https://www.gatsbyjs.org/docs/files-gatsby-looks-for-in-a-plugin/
https://www.gatsbyjs.org/docs/creating-a-source-plugin/#what-does-the-code-look-like
This is why its not showing up in your GraphiQL. There is no node to be generated from your plugin, since you have a blank index.js so it seems like an module that doesn't export anything.
I am looking the way how can I post out only "INFO" level (for user) to console, and "DEBUG" level post to the file. Currently I found only one one work solution - using few "getLogger()" functions. For example:
log4js.getLogger('debug');
log4js.getLogger('info');
where each parameter of the function separate category of the configuration from configure() function.
but I don't like it and suppose that there is better option with one getLogger() function.
Last think I tried was:
log4js.configure({
appenders: {
everything:{ type: 'stdout' },
file_log: { type: 'file' ,filename: 'all-the-logs.log' },
logLevelFilter: { type:'logLevelFilter',level: 'debug', appender: 'file_log' }
},
categories: {
default: {
appenders: [ 'logLevelFilter','everything'], level: 'info'},
}
});
I see in console out - only "INFO" level, but in file I also see only "INFO" level. In case If I add to appender - level "ALL" - I will see in console out all levels and the same will be in file
log4js provided special append typed categoryFilter and logLevelFilter, see Log4js - Appenders, step as below
Add appender and category based on each log level;
Add extra appender typed logLevelFilter pointing to the related appender;
Change config.categories.default.appenders using appenders in step 2;
Finally use log4js.getLogger() create logger and logging everywhere, log file separated by love level
Written in typescript
import {Appender, Configuration, LogLevelFilterAppender} from "log4js";
const fs = require('fs');
const path = require('path');
let levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
let conf: Configuration = {
appenders : {
default: {
type : "console",
layout: {
type : "colored",
pattern: "%m"
},
}
},
categories: {
default: {
appenders: levels,
level : 'all'
}
},
};
let logs = path.join(__dirname, 'logs');
if (!fs.existsSync(logs)) {
fs.mkdirSync(logs);
}
for (let level of levels) {
let appender: Appender = {
type : "file",
filename : path.join(logs, level + '.log'),
maxLogSize: 65536,
backups : 10,
layout : {
type : "pattern",
pattern: "%d{dd/MM hh:mm} %-5p %m"
}
};
conf.appenders[level] = appender;
conf.categories[level] = {
appenders: [level],
level : level.toUpperCase(),
}
}
let prefix = 'only-';
for (let level of levels) {
let appender: LogLevelFilterAppender = {
type : "logLevelFilter",
appender: level,
level : level,
maxLevel: level,
};
let name = prefix + level;
conf.appenders[name] = appender;
conf.categories[name] = {
appenders: [level],
level : level.toUpperCase(),
}
}
conf.categories.default.appenders = levels.map(x => prefix + x);
//console.log(JSON.stringify(conf, null, 2));
export default conf;
sample usage
// const log4js = require('log4js');
import * as log4js from 'log4js';
import log4jsConfig from './log4js.config';
log4js.configure(log4jsConfig );
(function () {
const logger = log4js.getLogger();
logger.trace('Entering cheese testing');
logger.debug('Got cheese.');
logger.info('Cheese is Gouda.');
logger.warn('Cheese is quite smelly.');
logger.error('Cheese is too ripe!');
logger.fatal('Cheese was breeding ground for listeria.');
log4js.shutdown(console.log);
})();
I'm trying to dynamically register custom about: URLs for a Firefox extension, relative to a variable.
Example:
- If var = 1, then create... about:123
- If var = 2, then create... about:abc
- If var = 3, then create... about:xxx
JavaScript code modules was suggested to me, specifically XPCOMUtils.jsm, but how would one go about implementing this in code?
I have gone through these pages to no avail:
https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules
https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/XPCOMUtils.jsm
I have already implemented a custom static about: URL using the chrome.manifest file. But I don't need static about: URLs. I need dynamic about: URLs, relative to a variable inside the Firefox extension.
Thanks for your help!
I wrote this addon-sdk module called, about-what, which will allow you to dynamically add/remove about:what uris to firefox.
This is the current source code for about-what:
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const { Cr, Cu, Ci, Cc, Cm } = require('chrome');
const { when: unload } = require('sdk/system/unload');
const { validateOptions : validate } = require('sdk/deprecated/api-utils');
const { uuid } = require('sdk/util/uuid');
const { URL, isValidURI } = require('sdk/url');
const tabs = require('sdk/tabs');
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const validOptions = {
what: {
is: ['string'],
ok: function(what) {
if (what.match(/^[a-z0-9-]+$/i))
return true;
return false;
},
map: function(url) url.toLowerCase()
},
url: {
is: ['string', 'undefined']
},
content: {
is: ['string', 'undefined']
},
useChrome: {
is: ['undefined', 'null', 'boolean'],
map: function(use) !!use
}
};
function add(options) {
let { what, url, content, useChrome } = validate(options, validOptions);
let baseURL = url;
if (content) {
url = encodeURI('data:text/html;charset=utf-8,' + content.replace(/\{\s*page\.baseurl\s*\}/, baseURL));
}
let classID = uuid();
let aboutModule = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
newChannel: function (aURI) {
let chan = Services.io.newChannel(url, null, null);
if (useChrome)
chan.owner = Services.scriptSecurityManager.getSystemPrincipal();
return chan;
},
getURIFlags: function () Ci.nsIAboutModule.ALLOW_SCRIPT
};
let factory = {
createInstance: function(aOuter, aIID) {
if (aOuter)
throw Cr.NS_ERROR_NO_AGGREGATION;
return aboutModule.QueryInterface(aIID);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
};
// register about:what
Cm.QueryInterface(Ci.nsIComponentRegistrar).
registerFactory(classID, '', '#mozilla.org/network/protocol/about;1?what='+what, factory);
let remover = unloader.bind(null, what, factory, classID);
unload(remover);
return undefined;
}
exports.add = add;
function unloader(what, factory, classID) {
// unregister about:what
Cm.QueryInterface(Ci.nsIComponentRegistrar).unregisterFactory(classID, factory);
let regEx = new RegExp('^' + what, 'i');
// AMO policy, see http://maglione-k.users.sourceforge.net/bootstrapped.xhtml
// close about:what tabs
for each (let tab in tabs) {
let url = URL(tab.url);
if (url.scheme === 'about' && url.path.match(regEx)) {
tab.close();
}
}
}