I am trying to interact with an iframe located in a chrome-extension popup. I know content.js can be injected in all frame using the manifest.json but it's working with frame inside a webpage and not inside the extension's popup.
Is it doable ? I tried many things but I didn't find a solution yet.
my manifest :
{
"name" :"test",
"version": "1.0",
"manifest_version": 2,
"description" :"Scraping Facebook",
"permissions": [
"cookies",
"background",
"tabs",
"http://*/*",
"https://*/*",
"storage",
"unlimitedStorage"
],
"icons": { "128": "images/pint.png" },
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": ["jquery-3.1.0.min.js","content.js"],
"run_at":"document_end"
}
],
"web_accessible_resources": [
"http://*/*",
"https://*/*",
"styles/*",
"fonts/*"
],
"background": {
"scripts": ["background.js"]
},
"browser_action" :
{
"default_popup": "popup.html",
"default_title": "test"
}
}
Use "all_frames": true in your content script declaration to inject it into an iframe:
"content_scripts": [{
"matches": [ "http://example.com/*" ],
"js": [ "content.js" ],
"all_frames": true
}],
To differentiate this iframe from normal tabs you can add a dummy parameter to the URL when you create the iframe e.g. http://example.com/?foo so you can match it in manifest.json like "http://example.com/*foo*" for example.
Then you can use messaging: the content script initiates it, and the extension script registers a listener.
Trivial one-time sendMessage:
content.js:
chrome.runtime.sendMessage('test', response => {
console.log(response);
});
popup.js (or background.js and so on):
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
console.log('popup got', msg, 'from', sender);
sendResponse('response');
});
Long-lived port:
content.js:
let port = chrome.runtime.connect({name: 'test'});
port.onMessage.addListener((msg, port) => {
console.log(msg);
});
port.postMessage('from-iframe');
popup.js (or background.js and so on):
let iframePort; // in case you want to alter its behavior later in another function
chrome.runtime.onConnect.addListener(port => {
iframePort = port;
port.onMessage.addListener((msg, port) => {
console.log(msg);
});
port.postMessage('from-popup');
});
An example of popup.html is really straightforward:
<html>
<body>
<iframe width="500" height="500" src="http://example.com"></iframe>
<script src="popup.js"></script>
</body>
</html>
Of course you can also add the iframe(s) programmatically using DOM manipulation.
Related
I have a very simple extension:
{
"manifest_version": 2,
"name": "User Style Sheet Workaround",
"version": "1",
"content_scripts": [
{
"matches": ["<all_urls>"],
"css": ["default.css"]
}
]
}
I want default.css to be injected as user origin, not as author origin. How can I do it?
Use chrome.tabs.insertCSS with cssOrigin: 'user' in the background script's URL change event.
For example, chrome.webNavigation.onCommitted (requires webNavigation permission) or chrome.tabs.onUpdated (doesn't require any special permissions).
The target sites to allow CSS injection should be added to permissions.
manifest.json:
{
"manifest_version": 2,
"name": "User Style Sheet Workaround",
"version": "1",
"background": {
"scripts": ["bg.js"]
},
"permissions": ["<all_urls>"]
}
bg.js:
chrome.tabs.onUpdated.addListener((tabId, info) => {
if (info.status === 'loading') {
chrome.tabs.insertCSS(tabId, {
file: 'style.css',
cssOrigin: 'user',
runAt: 'document_start',
// allFrames: true,
// matchAboutBlank: true,
}, () => chrome.runtime.lastError); // ignoring errors
}
});
I've search for several hours how to hide a specific content type.
I found some post but they are too older and their solutions doesn't work in the actual's strapi.
For precisions, my collection type is declared inside a local plugin. I juste want to manage my collection inside the plugin page and I doesn't want it appear in the content type in the left menu.
If someone has a solution, it's can be really helpfull.
In new version of Strapi v3.6.6 — Community Edition there is an option in model
{
"kind": "collectionType",
"collectionName": "test",
"info": {
"name": "test"
},
"options": {
"increments": true,
"timestamps": true,
"draftAndPublish": true
},
**"pluginOptions": {
"content-manager": {
"visible": false
}
},**
"attributes": {
"name": {
"type": "string",
"required": true
},
}
}
They are working on this: https://github.com/strapi/rfcs/pull/22
But for now, based on official docs (plugin customization), you can overwrite file in content-manager plugin.
Be sure to check this file on strapi updates to avoid overwriting important code.
Copy file strapi-plugin-content-manager/services/data-mapper.js from your app node_modules into extensions/content-manager/services/
Now edit this file in your project and add your content type to array HIDDEN_CONTENT_TYPES following this pattern: plugins::[plugin-name].[content-type]
For example: plugins::ecommerce.product
...
const HIDDEN_CONTENT_TYPES = [
'plugins::upload.file',
'plugins::users-permissions.permission',
'plugins::users-permissions.role',
'plugins::ecommerce.product',
];
...
You can extend the plugin to make updates to the content-type's schema.
Copy the content-type schema from the plugin to your src folder.
In my case, I copied /strapi-plugin-navigation/server/content-types/audience/schema.json to /src/extensions/navigation/content-types/audience/schema.json (notice that the strapi-plugin- part of the folder name is removed) and added the following to it to hide the "Audience" content type from the content manager and content-type builder:
"pluginOptions": {
"content-manager": {
"visible": false
},
"content-type-builder": {
"visible": false
}
},
Official documentation here.
In Strapi v4 it is "visible": false
{
"kind": "collectionType",
"collectionName": "bookmark",
"info": {
"singularName": "bookmark",
"pluralName": "bookmarks",
"displayName": "Bookmark",
"description": ""
},
"options": {
"increments": true,
"timestamps": true,
"draftAndPublish": true
},
"pluginOptions": {},
"attributes": {
"index": {
"type": "integer",
"unique": false,
"visible": false
},
}
}
First of all, I tried to write how to save users input data to google sheet after developing the simple codes. It's able to work. Thank Mr.Master for providing this tutorial(Below the link).
Reference Mr.Master: https://www.youtube.com/watch?v=huwUpJZsTok
Next, I bumped into the problem below the code. I didn't know how to write it in Fulfillment. Could someone realize it to teach me?
Tool: Dialogflow, Google sheet, Firebase.
Theme: Order process
I tried to write Forhere() there. However, it didn't work.(First code)
function Forhere(agent){
const{
forhere, howmanypeople, whattime, namelist
} = agent.parameters;
const data1 = [{
Forhere: forhere,
HowManyPeople: howmanypeople,
Time: whattime,
Name: namelist
}];
axios.post('......', data1);
}
{/*....This code is a result of test(second one)
"responseId": "d0f44937-e58a-4b71-b6dc-ec2d6c39337b-f308a5c4",
"queryResult": {
"queryText": "黃大哥",
"parameters": {
"forhere": [
"內用"
],
"howmanypeople": [
2
],
"whattime": [
**{
"date_time": "2019-09-19T14:00:00+08:00"
}**
],
"namelist": [
"黃大哥"
]
},
"allRequiredParamsPresent": true,
"outputContexts": [
{
"name": "projects/test-tyrpxs/agent/sessions/5dd26d5c-bd99-072c-3693-41f95a3a348d/contexts/forhere",
"lifespanCount": 4,
"parameters": {
"howmanypeople": [
2
],
"namelist.original": [
"黃大哥"
],
"howmanypeople.original": [
"2"
],
"forhere": [
"內用"
],
"whattime.original": [
"明天下午2點"
],
"welcome": "嗨",
"whattime": [
{
"date_time": "2019-09-19T14:00:00+08:00"
}
],
"namelist": [
"黃大哥"
],
"welcome.original": "hi",
"forhere.original": [
"內用"
]
}
}
],
"intent": {
"name": "projects/test-tyrpxs/agent/intents/ec0f55c4-e9c9-401f-bce7-d2478c40fb85",
"displayName": "ForHere"
},
"intentDetectionConfidence": 1,
"diagnosticInfo": {
"webhook_latency_ms": 4992
},
"languageCode": "zh-tw"
},
"webhookStatus": {
"code": 4,
"message": "Webhook call failed. Error: Request timeout."
}
}
You can use below code
let forhere= agent.parameters.forhere;
let howmanypeople= agent.parameters.howmanypeople;
let whattime= agent.parameters.whattime;
let namelist= agent.parameters.namelist;
then use this variables in your api call.
To T.Ali:
Dialogflowfirebasefulfillment&Error message:
Although I think this error didn't show where these mistakes are.
Dialogflow Request body: {"responseId":"ab277bc6-3bcc-4c4b-9a94-192b9ecfb8af-f308a5c4","queryResult":{"queryText":"黃大哥","parameters":{"forhere":"內用","whattime":{"date_time":"2019-09-20T12:00:00+08:00"},"howmanypeople":3,"namelist":"黃大哥"},"allRequiredParamsPresent":true,"outputContexts":[{"name":"projects/test-tyrpxs/agent/sessions/5dd26d5c-bd99-072c-3693-41f95a3a348d/contexts/forhere","lifespanCount":4,"parameters":{"welcome":"嗨","welcome.original":"hi","forhere":"內用","forhere.original":"內用","whattime":{"date_time":"2019-09-20T12:00:00+08:00"},"whattime.original":"明天中午","howmanypeople":3,"howmanypeople.original":"3","namelist":"黃大哥","namelist.original":"黃大哥"}}],"intent":{"name":"projects/test-tyrpxs/agent/intents/ec0f55c4-e9c9-401f-bce7-d2478c40fb85","displayName":"ForHere"},"intentDetectionConfidence":1,"languageCode":"zh-tw"},"originalDetectIntentRequest":{"payload":{}},"session":"projects/test-tyrpxs/agent/sessions/5dd26d5c-bd99-072c-3693-41f95a3a348d"}
Error: No handler for requested intent
at WebhookClient.handleRequest (/srv/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:317:29)
at exports.dialogflowFirebaseFulfillment.functions.https.onRequest (/srv/index.js:105:9)
at cloudFunction (/srv/node_modules/firebase-functions/lib/providers/https.js:57:9)
at /worker/worker.js:783:7
at /worker/worker.js:766:11
at _combinedTickCallback (internal/process/next_tick.js:132:7)
at process._tickDomainCallback (internal/process/next_tick.js:219:9)
Furthermore, I've write below the code worked formally(input users data to google sheet).
enter image description here
I'm working on a process similar to the one described in https://nystudio107.com/blog/a-better-package-json-for-the-frontend but want to modify it to be able to compile different SCSS files to CSS files of the same name, e.g. fileA.scss compiles to fileA.css.
What I have at the moment works fine if I'm just working with one SCSS file master.scss compiling to site.combined.min.css.
From package.json
"paths": {
"src": {
"base": "./src/",
"css": "",
"img": "./src/img/",
"js": "./src/js/",
"scss": "./src/scss/"
},
"dist": {
"base": "./public/",
"css": "./public/assets/css/",
"js": "./public/assets/js/",
"fonts": "./public/assets/fonts/",
"img": "./public/assets/img/"
},
"build": {
"base": "./build/",
"css": "./build/css/",
"js": "./build/js/",
"html": "./build/html/",
"img": "./build/img/"
},
"tailwindcss": {
"src": "./build/css/master.css",
"conf": "./tailwind.config.js"
},
"scss": [
{
"src": "./src/scss/master.scss"
}
],
"templates": "./public/site/templates/"
},
"vars": {
"siteCssName": "site.combined.min.css",
"scssName": "master.scss",
"cssName": "master.css"
},
"globs": {
"distCss": [
"./node_modules/normalize.css/normalize.css",
"./build/css/*.css"
],
"purgecss": [
"./html/index.html",
"./html/site/templates/**/*.{twig}",
"./src/js/site.js",
"./html/assets/js/*.js"
],
"purgecssWhitelist": []
},
And the Gulp tasks
gulp.task("scss", () => {
$.fancyLog("-> Compiling scss");
// return gulp.src(pkg.paths.src.scss + pkg.vars.scssName)
return gulp.src(pkg.paths.src.scss + '*.scss')
.pipe($.plumber({errorHandler: onError}))
.pipe($.sourcemaps.init({loadMaps: true}))
.pipe($.sass({
includePaths: pkg.paths.scss
})
.on("error", $.sass.logError))
.pipe($.cached("sass_compile"))
.pipe($.autoprefixer())
.pipe($.sourcemaps.write("./"))
.pipe($.size({gzip: true, showFiles: true}))
.pipe(gulp.dest(pkg.paths.build.css));
});
gulp.task("tailwind", () => {
$.fancyLog("-> Compiling tailwind css");
return gulp.src(pkg.paths.tailwindcss.src)
.pipe($.postcss([
$.tailwindcss(pkg.paths.tailwindcss.conf),
require("autoprefixer"),
]))
.pipe($.if(process.env.NODE_ENV === "production",
$.purgecss({
extractors: [{
extractor: TailwindExtractor,
extensions: ["twig", "scss", "css", "js"]
}],
whitelist: pkg.globs.purgecssWhitelist,
content: pkg.globs.purgecss
})
))
.pipe(gulp.dest(pkg.paths.build.css));
});
class TailwindExtractor {
static extract(content) {
return content.match(/[A-z0-9-:\/]+/g);
}
}
gulp.task("css", ["tailwind", "scss"], () => {
$.fancyLog("-> Building css");
return gulp.src(pkg.globs.distCss)
.pipe($.plumber({errorHandler: onError}))
.pipe($.newer({dest: pkg.paths.dist.css + pkg.vars.siteCssName}))
.pipe($.print())
.pipe($.sourcemaps.init({loadMaps: true}))
.pipe($.concat(pkg.vars.siteCssName))
.pipe($.if(process.env.NODE_ENV === "production",
$.cssnano({
discardComments: {
removeAll: true
},
discardDuplicates: true,
discardEmpty: true,
minifyFontValues: true,
minifySelectors: true
})
))
.pipe($.header(banner, {pkg: pkg}))
.pipe($.sourcemaps.write("./"))
.pipe($.size({gzip: true, showFiles: true}))
.pipe(gulp.dest(pkg.paths.dist.css))
.pipe($.filter("**/*.css"))
.pipe($.livereload());
});
You can see I've replaced the 3rd line of the scss task so that it will deal with multiple sources and output multiple files to the build folder.
But what I'm getting stuck on is how does the tailwind task then know to use the file with the same name as used in the scss task and when it gets to the css task, how does it know not to use all the CSS files in the build folder, but only the one used in the previous tasks?
Is there a way of passing the file name to be used from one task to the next? Or should it be done a different way?
Instead of outputting the compiled scss into pkg.paths.build.css, you could output that into a different folder (ie ./build/scss/), and set that folder as your pkg.paths.tailwind.src?
Alternatively, you could try setting pkg.paths.tailwind.src to be an array of named files, like
"tailwindcss": {
"src":[pkg.paths.build.css + "fileA.css", pkg.paths.build.css + "fileB.css"],
"conf": "./tailwind.config.js"
},
Either way, it will depend on how the PostCSS part of the tailwind task handles being passed multiple files..
It turns out I was closer than I thought. The only changes I needed to make were in the css task.
return gulp.src(pkg.globs.distCss) became return gulp.src(pkg.paths.build.css + '**/*.css')
.pipe($.newer({dest: pkg.paths.dist.css + pkg.vars.siteCssName})) became .pipe($.newer({dest: pkg.paths.dist.css}))
and I removed .pipe($.concat(pkg.vars.siteCssName)) as I wasn't combining multiple CSS files anymore anyway.
This now saves a CSS file to the build folder with the same filename as the original SCSS file, and then it's compiled and saved to the dist folder, again with the same file name.
I'm trying to setup my app descriptor file (manifest.json) to include a named model, 'inputs' in its "models" object. From my understanding, after doing so, the model should be available throughout the app when provided the correct path (see XML View).
The reason I'd like to setup this manifest.json is because it's a best practice to configure all models in here.
In the controller, I'd like to get and then set the 'inputs' Model defined in the manifest.json --but how can this be done?
manifest.json (Where I have configured the 'inputs' model)
{
"_version": "1.1.0",
"sap.app": {
"_version": "1.1.0",
"id": "pricingTool",
"type": "application",
"applicationVersion": {
"version": "1.0.0"
},
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"ach": "ach",
"resources": "resources.json",
"sourceTemplate": {
"id": "ui5template.basicSAPUI5ApplicationProject",
"version": "1.30.3"
},
},
"sap.ui": {
"_version": "1.1.0",
"technology": "UI5",
"icons": {
"icon": "",
"favIcon": "",
"phone": "",
"phone#2": "",
"tablet": "",
"tablet#2": ""
},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
},
"supportedThemes": [
"sap_hcb",
"sap_bluecrystal"
]
},
"sap.ui5": {
"_version": "1.1.0",
"rootView": {
"viewName": "pricingTool.view.Main",
"type": "XML"
},
"dependencies": {
"minUI5Version": "1.30.0",
"libs": {
"sap.ui.core": {},
"sap.m": {},
"sap.ui.layout": {}
}
},
},
"contentDensities": {
"compact": true,
"cozy": true
},
"models": {
"inputs": {
"type": "sap.ui.model.json.JSONModel",
"uri": "model/inputs.json"
},
},
}
Main.controller.js (where the 'inputs' model should be set from the manifest file)
sap.ui.define([
'jquery.sap.global',
'sap/ui/core/mvc/Controller',
'sap/ui/model/json/JSONModel',
'sap/ui/model/Filter',
'sap/ui/model/FilterOperator',
'sap/m/MessageToast',
'pricingTool/model/viewControls',
'pricingTool/model/formatter',
'pricingTool/model/Utility',
'sap/ui/core/util/Export',
'sap/ui/core/util/ExportTypeCSV',
],
function (jQuery, Controller, JSONModel, Filter, FilterOperator, MessageToast, viewControls, formatter, Utility, Export, ExportTypeCSV) {
"use strict";
var mainController = Controller.extend("pricingTool.controller.Main", {
onInit: function(oEvent) {
//define named/default model(s)
var inputs = new JSONModel("./model/inputs.json");
//set model(s) to current xml view
this.getView().setModel(inputs, "inputs");
//this is one solution I have tried, but doesn't do anything:
// this.getView().setModel(this.getOwnerComponent().getModel("inputs"), "inputs");
//another solution I have tried:
//var inputs = this.getModel('input') <--I was hoping this would find the inputs defined in the manifest.json, but this doesn't work either
// this.getView().setModel(inputs, "inputs");
},
...
inputs.json
{
"propA" : "testVal"
}
XML View
<Button text="{inputs>propA}"></Button>
Components.js (Not sure what to do in the Component.js)
sap.ui.define([
'sap/ui/core/UIComponent'
],
function(UIComponent) {
"use strict";
var Component = UIComponent.extend("pricingTool.Component", {
metadata : {
metadata : {
manifest: "json"
},
rootView : "pricingTool.view.Main",
dependencies : {
libs : [
"sap.m",
"sap.ui.layout"
]
},
config : {
sample : {
files : [
"Main.view.xml",
"Main.controller.js"
]
}
}
},
init : function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
}
});
return Component;
});
The problem is that the model property ("propA") is not displaying when I test it on a button control. Can anyone tell me why the model is not displaying in the app?
Summarizing Question
How can I define a model in manifest.json, and then set that model in the controller so I can use it in my xml view?
try putting a forward slash before your property name...
<Button text="{inputs>/propA}"></Button>
...and update your manifest file so that your model definition points to your dataSource defined under sap.app as follows...
{
"_version": "1.1.0",
"sap.app": {
"_version": "1.1.0",
"id": "pricingTool",
"type": "application",
"applicationVersion": {
"version": "1.0.0"
},
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"ach": "ach",
"resources": "resources.json",
"sourceTemplate": {
"id": "ui5template.basicSAPUI5ApplicationProject",
"version": "1.30.3"
},
"dataSources": {
"inputsData": {
"type" : "JSON",
"uri": "model/inputs.json"
}
}
},
"sap.ui": {
"_version": "1.1.0",
"technology": "UI5",
"icons": {
"icon": "",
"favIcon": "",
"phone": "",
"phone#2": "",
"tablet": "",
"tablet#2": ""
},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
},
"supportedThemes": [
"sap_hcb",
"sap_bluecrystal"
]
},
"sap.ui5": {
"_version": "1.1.0",
"rootView": {
"viewName": "pricingTool.view.Main",
"type": "XML"
},
"dependencies": {
"minUI5Version": "1.30.0",
"libs": {
"sap.ui.core": {},
"sap.m": {},
"sap.ui.layout": {}
}
},
"contentDensities": {
"compact": true,
"cozy": true
},
"models": {
"products": {
"type": "sap.ui.model.json.JSONModel",
"uri": "model/products.json"
},
"inputs": {
"type": "sap.ui.model.json.JSONModel",
"dataSource" : "inputsData"
}
}
}
}
...change your Component.js file to point to your manifest file...
sap.ui.define([
'sap/ui/core/UIComponent'
],
function(UIComponent) {
"use strict";
var Component = UIComponent.extend("pricingTool.Component", {
metadata : {
manifest: "json",
},
init : function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
}
});
});
... and remove the onInit logic within your controller to set the model (this is handled by the component)
Those models you define in the manifest.json file are created in the Component context (if your app based on Component). To make it available in the XML view you have to obtain it from Component and then attach to the view. The code snippet you can use in onInit controller event looks like this:
this.getView().setModel(this.getOwnerComponent().getModel("<your_model_name>"), "<your_model_name>");
if you are using standard template then most likely you have a BaseController as an ancestor, in that case the code can look shorter:
this.setModel(this.getComponentModel("<your_model_name>"), "<your_model_name>");
Here is a minimal example of what you'd like to achieve: https://embed.plnkr.co/l1XF5O/
Models defined in manifest.json (aka. "app descriptor") will be set to the component (since v1.30).
If a descriptor is used, almost all properties of your component's metadata other than manifest: "json" are deprecated and should be avoided. Deprecated properties are listed here.
Views (and their controls inside) inside the root view instantiated by your component inherit models automatically from the component. Thus, setting models to your view explicitly is not needed anymore. Your view already knows the model that is set to the component.*
The binding syntax should be used correctly according to your situation:
Use relative binding syntax (modelName>property) only if a parent control has already a context bound (e.g. the parent control uses aggregation binding, or element binding).
In other cases, use absolute binding syntax. It starts with a slash (modelName>/property), so that the control doesn't look for the binding context of its parent.
*Although the model, which is set on the component, can be used seamlessly in the XMLView, retrieving the component model by calling view.getModel inside the onInit handler will return undefined. More about this: https://stackoverflow.com/a/43941380/5846045