I'm trying to add a user profile picture and username to a twitter card when someone shares their profile link. For that I'm doing this on the guest-profile page:
getUser() {
this.authService.getSingleUser(null, this.username).then(user => {
if (user && !this.user) {
const image = user?.photoURL != 'assets/no-avatar.png' ? user?.photoURL : 'https://nonicapp.web.app/assets/no-avatar.png'
this.meta.updateTag(
{ name: 'og:title', content: user?.displayName + ' on NONIC' }
)
this.meta.updateTag(
{ name: 'og:description', content: 'Ask ' + user?.displayName + ' anonymous questions!' }
)
this.meta.updateTag(
{ name: 'og:image', content: image }
)
this.meta.updateTag(
{ name: 'twitter:title', content: user?.displayName + ' on NONIC' }
)
this.meta.updateTag(
{ name: 'twitter:description', content: 'Ask ' + user?.displayName + ' anonymous questions!' }
)
this.meta.updateTag(
{ name: 'twitter:image', content: image }
)
(...)
I'm updating the tags because it already exists in case the link is being shared and not connected to a user account.
Problem is, twitter reads metadata long before my data is fetch from firebase so the twitter card just comes as default. Any way I can make twitter wait for me to fetch and update the meta tags before it reads it?
Related
I am new to grape and backbonejs.I want to make a template builder which apart from having
default components provided by grapejs like the label, image etc will also have custom components
for ex - Top 5 products today having an image and product name and it will get the fresh data from the server. In this scenario I am not able to figure out where should I make the API call and how can I use the fetched results to show in components. Link to the code which I have tried is in the comments.
if you move your code to your prop change handler, instead of the onRender function of the view you will be able to use the values from the API call as you want.
Check this small change:
https://jsfiddle.net/jvas28/8sb3tn94/1/
const editor = grapesjs.init({
container: '#gjs',
fromElement: 1,
height: '100%',
storageManager: { type: 0 },
});
editor.DomComponents.addType('test-component', {
model: {
defaults: {
testprop: '12345',
url: 'https://jsonplaceholder.typicode.com/posts/1'
},
init() {
console.log('Local hook: model.init', this.attributes.testprop);
fetch(this.attributes.url)
.then(response => response.json())
.then(commits => {
this.set('testprop', 'Test');
console.log(this.attributes.testprop);
});
this.listenTo(this, 'change:testprop', this.handlePropChange);
// Here we can listen global hooks with editor.on('...')
},
updated(property, value, prevValue) {
console.log('Local hook: model.updated',
'property', property, 'value', value, 'prevValue', prevValue);
},
removed() {
console.log('Local hook: model.removed');
},
handlePropChange() {
let prop = this.get('testprop');
// Here inside view it is getting the old value. of "testprop" i.e '12345' but not
//the new value
//which is being fetched from server in the init() of model.
let comp1 = '<div>' +
'<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/1200px-Image_created_with_a_mobile_phone.png" />' +
'<span title="foo">' + prop + '</span>' + '</div>';
const component = editor.addComponents(comp1);
return component
}
},
view: {
init() {
console.log('Local hook: view.init');
},
},
});
// A block for the custom component
editor.BlockManager.add('test-component', {
label: 'Test Component',
content: `<div data-gjs-type="test-component"></div>`,
});
I hope someone can help me with this.
I have a Backbone based SPA for a responsive website with a .net WebAPI providing all of the data.
I've recently found a weird problem. I've added a search box, which searches one of the catalogues on the system. This works fine on desktop browsers and on Android. On iOS, executing a search seems to take you back to the sign in page.
You can execute a search in various ways, you can either hit enter or you can click the search icon. Both of these then trigger a method that navigates the router to the URL for the search result.
My first thought was that it was some button weirdness, but I don't think that's the problem as both methods of search execution are causing the same problem.
The search results are displayed in a view that is secured (It requires a username to be present - this is stored in a hidden field on the page). There are two search boxes on the site - one on the home page and one on the search results page itself (it shows a default set when you load it first time - which it does load first time fine). Both search boxes are exhibiting the same behaviour.
My site is set up in such a way that when Backbone pulls back a model, if it gets a 401 back from the API then it will send you back to the login page, so I can only think it's happening here.
Here's my view code...
function (SiansPlan, ErrorManager, RecipeSearchResult, Header, Components, TemplateSource) {
var recipeSearchView = SiansPlan.SecureView.extend({
name: 'Recipe Search',
sectionName: 'Recipes',
queryText: '',
template: Handlebars.compile(TemplateSource),
headerView: new Header({ text: 'Recipes', swatch: 'e' }),
searchBoxRegion: undefined,
$searchWrapper: undefined,
$queryHeaderMobile: undefined,
$queryHeaderDesktop: undefined,
$searchButton: undefined,
$searchInput: undefined,
$recipeSearch : undefined,
events: {
'click .link-container': 'showRecipe',
'click #searchWrapper': 'showSearch',
'click #searchButton': 'showOrPerformSearch',
'keydown #searchButton': 'performSearchOnEnter',
'keydown #recipeSearch': 'performSearchOnEnter'
},
initialize: function (options) {
this.options = options || {};
SiansPlan.SecureView.prototype.initialize.call(this, options);
this.queryText = Object.exists(this.options.query) ? this.options.query : '';
},
bindData: function () {
this.$el.html(this.template({ results: this.collection.toJSON() }));
},
render: function () {
var that = this;
if (this.isSecured()) {
this.trigger('rendering');
var params = {
success: function () {
that.bindData();
that.trigger('rendered');
},
error: function (model, xhr) {
if (Object.exists(xhr) && xhr.status == 401) {
that.applyTimedOutSecureLoginPrompt();
} else {
that.$el.html('Unable to fetch search results');
ErrorManager.handleXhr('Search failed', xhr);
}
that.trigger('rendered');
}
};
if (!Object.exists(this.collection)) {
this.collection = new RecipeSearchResult.Collection({ username: SiansPlanApp.session.username(), query: this.queryText });
}
this.collection.fetch(params);
} else {
this.applySecureLoginPrompt();
}
return this;
},
postRender: function () {
var that = this;
var queryHeader = "All recipes";
if (Object.hasValue(this.queryText)) {
queryHeader = this.collection.length + " results for '" + this.queryText + "'";
}
this.$searchWrapper = $('#searchWrapper');
this.$queryHeaderMobile = $('#queryHeaderMobile');
this.$queryHeaderDesktop = $('#queryHeaderDesktop');
this.$searchButton = $('#searchWrapper');
this.$searchInput = $('#searchInput');
this.$recipeSearch = $('#recipeSearch');
this.$queryHeaderMobile.html(queryHeader);
this.$queryHeaderDesktop.html(queryHeader);
this.$recipeSearch.val(this.queryText);
SiansPlanApp.session.waitForLoad(30, function () {
that.searchBoxRegion = new SiansPlan.Region({ el: '.recipe-search-box-container' });
that.searchBoxRegion.renderView(new Components.RecipeSearchBox({ username: SiansPlanApp.session.username(), query: that.queryText, title: 'Search' }));
});
},
performSearchOnEnter: function (e) {
if (e.keyCode == 13) {
this.showOrPerformSearch(e);
}
},
showOrPerformSearch: function (e) {
if (!this.$searchInput.is(':visible')) {
this.showSearch(e);
} else {
e.preventDefault();
var url = '/recipes/search/' + this.$recipeSearch.val();
window.SiansPlanApp.router.navigate(url, true);
}
return false;
},
showRecipe: function (e) {
e.preventDefault();
var url = $(e.target).find('a').first().attr('href');
window.SiansPlanApp.router.navigate(url, true);
},
showSearch: function (e) {
e.preventDefault();
if (!this.$searchInput.is(':visible')) {
this.$queryHeaderMobile.hide();
this.$searchInput.show();
this.$recipeSearch.focus();
this.$recipeSearch.select();
}
return false;
}
});
return recipeSearchView;
});
UPDATES
I've set up some alerts as follows in the script to see what's going on and I've discovered the following...
render: function () {
var that = this;
if (this.isSecured()) {
this.trigger('rendering');
var params = {
success: function () {
alert('Bind has succeeded!');
that.bindData();
that.trigger('rendered');
},
error: function (model, xhr) {
alert('Bind has failed!');
if (Object.exists(xhr) && xhr.status == 401) {
that.applyTimedOutSecureLoginPrompt();
} else {
that.$el.html('Unable to fetch search results');
ErrorManager.handleXhr('Search failed', xhr);
}
that.trigger('rendered');
alert(xhr.status + ' ' + xhr.responseText);
}
};
if (!Object.exists(this.collection)) {
alert('Binding new collection: ' + SiansPlanApp.session.username() + ' - ' + this.queryText);
this.collection = new RecipeSearchResult.Collection({ username: SiansPlanApp.session.username(), query: this.queryText });
}
alert('About to fetch using ' + this.collection.url());
this.collection.fetch(params);
} else {
alert('I dont appear to be secured??');
this.applySecureLoginPrompt();
}
return this;
},
When I first load the page (to show all the results) it loads fine and 'Bind Succeeded!' appears. The API call made is /api/recipes/search/{username}/
When I submit search criteria it fails ('Bind failed!') with the API call of /api/recipes/search/{username}/{query} and returns a 401.
This has me even more befuddled than before as this now looks like an API issue, but other devices are working fine and if I submit the same queries into Fiddler everything is, as expected, fine.
I've found the answer in the smallest place...
The issue was that the search criteria had an upper case letter. So, for example, when searching with 'Fish', The API generated a 301 which redirected to /api/recipes/search/{username}/fish. iOS didn't like that and reported it as a 401 (Which truly sucks!)
Using a "Poll" app on Facebook, it would post something like this to my facebook Page (Kid Task):
How can I do this using Facebook javascript SDK? As best as I can tell, this allows it https://developers.facebook.com/docs/guides/attachments/ but this says "for REST API" and that is deprecated right?
Code i've tried:
FB.api(
'https://graph.facebook.com/kidtask/feed/',
'post',
{
message: 'test question',
actions: { name: 'Vote Now', link: 'http://test.com' },
properties: { text: "test answer 1", href: "http://test.com" }
},
function(response) {
if (!response) {
alert('Error occurred.');
} else if (response.error) {
document.getElementById('result').innerHTML =
'Error: ' + response.error.message;
} else {
document.getElementById('result').innerHTML =
'<a href=\"https://www.facebook.com/kidtask/' +
response.id + '\">' +
'Story created. ID is ' +
response.id + '</a>';
}
}
);
This posts without errors, but the properties are ignored.
Figured it out finally, although i'm not sure what it was exactly. My guess is, I was trying to post as a text post and not a link post.
FB.api(
'/kidtask/feed/',
'post',
{
name: 'whatever',
link: 'http://url',
actions: { name: 'Vote Now', link: 'http://url' },
description:"\u003Cbr/\u003E",
picture:"http://imageurl",
caption:"\u003Cbr/\u003E",
properties: [{"text":"test 1","href":"http://url"},{"text":"test 2","href":"http://url"},{"text":"test 3","href":"http://url"}]
},
I am using Azure Mobile Services for sending push notifications to my client app. I send both square and tile notification using the push.wns object (first square and then wide). Below is how the server-side code that sends push notifications looks like (this is basically called whenever a record is updated in my DB table):
function update(item, user, request) {
request.execute({
success: function() {
request.respond();
sendNotifications();
}
});
function sendNotifications() {
var channelTable = tables.getTable('channel');
channelTable.read({
success: function(channels) {
channels.forEach(function(channel) {
push.wns.sendTileSquarePeekImageAndText02(channel.pushuri,
{image1src: '<imgPath>',
text1: 'New Game',
text2: item.playername },
{
success: function(pushResponse) { console.log("Sent Square:", pushResponse); },
error: function(error) {
console.log("error sending push notification to ", channel.pushuri);
if (error.statusCode == 410) {
console.log("Deleting ", channel);
channelTable.del(channel.id);
}
}
});
push.wns.sendTileWidePeekImage01(channel.pushuri,
{image1src: <imgPath>,
text1: 'New Game',
text2: item.playername },
{
success: function(pushResponse) { console.log("Sent Square:", pushResponse); },
error: function(error) {
console.log("error sending push notification to ", channel.pushuri);
if (error.statusCode == 410) {
console.log("Deleting ", channel);
channelTable.del(channel.id);
}
}
});
});
}
});
}
}
I notice that the wide notification is displayed correctly when my app tile is wide. However, when I make the tile size of my app to square, the square notification is not displayed. How can I correct this?
Here's sample which you can use to send one update and update two kind of tiles at once.
push.wns.send(item.channel,
'<tile><visual>' +
'<binding template="TileSquarePeekImageAndText02">' +
'<image id="1" src="imageUri" />' +
'<text id="1">Template: TileSquarePeekImageAndText02</text>' +
'<text id="2">' + item.text + '</text>' +
'</binding>' +
'<binding template="TileWidePeekImage01">' +
'<image id="1" src="imageUri" />' +
'<text id="1">Template: TileWidePeekImage01</text>' +
'<text id="2">' + item.text + '</text>' +
'</binding>' +
'</visual></tile>',
"wns/toast", {
success: function(pushResponse) {
console.log("Sent push:", pushResponse);
}
}
);
The tile notification with the wide content is replacing the tile notification containing the square content. A single notification should be sent containing both square and wide tile content.
I am trying to change the "Say something..." text part of a post to feed function of the FB.ui
All else shows but it doesn't change the text in the text area.
My code is below :
function postToFeed() {
// calling the API ...
var obj = {
method: 'feed',
link: 'http://solarhelper.net',
picture: 'http://solarhelper.net/icon',
name: 'Find your result using Solar Helper',
caption: 'Click the link to download the app or get your result online.',
description: 'I could save ' + incomeYear25 + ' over 25 years and save ' + totalCO2Level + ' tonnes of CO2!',
message: 'Testing'
};
function callback(response) {
if (response && response.post_id) {
alert('Post was published.');
} else {
alert('Post was not published.');
}
}
FB.ui(obj, callback);
}
Thanks for your help
Unfortunately, you're not and you will not be able to change this part of a dialog.
I suppose this prevention should restrict event more spam ?