mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	Add description and icon to preview URLs for non-document resources #1242
When pasting a URL in some app or website that allows previewing the link, for other resources than documents, you were offered an irrelevant description. This patches aims to give a generic description of what is Grist.
This commit is contained in:
		
							parent
							
								
									346c9fc0b4
								
							
						
					
					
						commit
						b2d0803cfe
					
				| @ -159,8 +159,8 @@ export function makeSendAppPage({ server, staticDir, tag, testLogin, baseDomain | ||||
|     const staticTag = options.tag || tag; | ||||
|     // If boot tag is used, serve assets locally, otherwise respect
 | ||||
|     // APP_STATIC_URL.
 | ||||
|     const staticOrigin = staticTag === 'boot' ? '' : (process.env.APP_STATIC_URL || ''); | ||||
|     const staticBaseUrl = `${staticOrigin}/v/${staticTag}/`; | ||||
|     const staticOrigin = staticTag === 'boot' ? '' : (process.env.APP_STATIC_URL || config.homeUrl || ''); | ||||
|     const staticBaseUrl = `${staticOrigin.replace(/\/*$/, '')}/v/${staticTag}/`; | ||||
|     const customHeadHtmlSnippet = server.create.getExtraHeadHtml?.() ?? ""; | ||||
|     const warning = testLogin ? "<div class=\"dev_warning\">Authentication is not enforced</div>" : ""; | ||||
|     // Preload all languages that will be used or are requested by client.
 | ||||
| @ -174,8 +174,8 @@ export function makeSendAppPage({ server, staticDir, tag, testLogin, baseDomain | ||||
|     ).join('\n'); | ||||
|     const content = fileContent | ||||
|       .replace("<!-- INSERT WARNING -->", warning) | ||||
|       .replace("<!-- INSERT TITLE -->", getPageTitle(req, config)) | ||||
|       .replace("<!-- INSERT META -->", getPageMetadataHtmlSnippet(config)) | ||||
|       .replace("<!-- INSERT TITLE -->", getPageTitle(config) ?? (translate(req, 'Loading') + "...")) | ||||
|       .replace("<!-- INSERT META -->", getPageMetadataHtmlSnippet(req, config, staticBaseUrl)) | ||||
|       .replace("<!-- INSERT TITLE SUFFIX -->", getPageTitleSuffix(server.getGristConfig())) | ||||
|       .replace("<!-- INSERT BASE -->", `<base href="${staticBaseUrl}">` + tagManagerSnippet) | ||||
|       .replace("<!-- INSERT LOCALE -->", preloads) | ||||
| @ -271,11 +271,10 @@ function configuredPageTitleSuffix() { | ||||
|  * | ||||
|  * Note: The string returned is escaped and safe to insert into HTML. | ||||
|  */ | ||||
| function getPageTitle(req: express.Request, config: GristLoadConfig): string { | ||||
| function getPageTitle(config: GristLoadConfig): string|null { | ||||
|   const maybeDoc = getDocFromConfig(config); | ||||
|   if (!maybeDoc) { return translate(req, 'Loading') + "..."; } | ||||
| 
 | ||||
|   return handlebars.Utils.escapeExpression(maybeDoc.name); | ||||
|   return maybeDoc && handlebars.Utils.escapeExpression(maybeDoc.name); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @ -286,25 +285,26 @@ function getPageTitle(req: express.Request, config: GristLoadConfig): string { | ||||
|  * | ||||
|  * Note: The string returned is escaped and safe to insert into HTML. | ||||
|  */ | ||||
| function getPageMetadataHtmlSnippet(config: GristLoadConfig): string { | ||||
| function getPageMetadataHtmlSnippet(req: express.Request, config: GristLoadConfig, staticBaseUrl: string): string { | ||||
|   const metadataElements: string[] = []; | ||||
|   const maybeDoc = getDocFromConfig(config); | ||||
| 
 | ||||
|   const description = maybeDoc?.options?.description; | ||||
|   if (description) { | ||||
|     const content = handlebars.Utils.escapeExpression(description); | ||||
|     metadataElements.push(`<meta name="description" content="${content}">`); | ||||
|     metadataElements.push(`<meta property="og:description" content="${content}">`); | ||||
|     metadataElements.push(`<meta name="twitter:description" content="${content}">`); | ||||
|   } | ||||
|   const description = maybeDoc?.options?.description ?? String(translate(req, 'gristMetaDescription')); | ||||
|   const escapedDescription = handlebars.Utils.escapeExpression(description); | ||||
|   metadataElements.push(`<meta name="description" content="${escapedDescription}">`); | ||||
|   metadataElements.push(`<meta property="og:description" content="${escapedDescription}">`); | ||||
|   metadataElements.push(`<meta name="twitter:description" content="${escapedDescription}">`); | ||||
| 
 | ||||
|   const icon = maybeDoc?.options?.icon; | ||||
|   if (icon) { | ||||
|     const content = handlebars.Utils.escapeExpression(icon); | ||||
|     metadataElements.push(`<meta name="thumbnail" content="${content}">`); | ||||
|     metadataElements.push(`<meta property="og:image" content="${content}">`); | ||||
|     metadataElements.push(`<meta name="twitter:image" content="${content}">`); | ||||
|   } | ||||
|   const icon = maybeDoc?.options?.icon ?? new URL('img/icon-grist.png', staticBaseUrl).href; | ||||
|   const escapedIcon = handlebars.Utils.escapeExpression(icon); | ||||
|   metadataElements.push(`<meta name="thumbnail" content="${escapedIcon}">`); | ||||
|   metadataElements.push(`<meta property="og:image" content="${escapedIcon}">`); | ||||
|   metadataElements.push(`<meta name="twitter:image" content="${escapedIcon}">`); | ||||
| 
 | ||||
|   const title = (getPageTitle(config) ?? translate(req, 'Welcome')) + getPageTitleSuffix(config); | ||||
|   // NB: We don't generate the content of the <title> tag here.
 | ||||
|   metadataElements.push(`<meta property="og:title" content="${title}">`); | ||||
|   metadataElements.push(`<meta name="twitter:title" content="${title}">`); | ||||
| 
 | ||||
|   return metadataElements.join('\n'); | ||||
| } | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								static/img/icon-grist.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/img/icon-grist.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.9 KiB | 
| @ -1,6 +1,8 @@ | ||||
| { | ||||
|   "sendAppPage": { | ||||
|     "Loading": "Loading" | ||||
|     "Loading": "Loading", | ||||
|     "gristMetaDescription": "Grist is the evolution of spreadsheets.", | ||||
|     "Welcome": "Welcome" | ||||
|   }, | ||||
|   "oidc": { | ||||
|     "emailNotVerifiedError": "Please verify your email with the identity provider, and log in again." | ||||
|  | ||||
| @ -29,6 +29,7 @@ describe('HomeIntro', function() { | ||||
|     }); | ||||
| 
 | ||||
|     it('should should intro screen for anon', () => testIntroScreen({anon: true, team: false})); | ||||
|     it('should should set meta tags for URL previews', testMetaTags); | ||||
|     it('should not show Other Sites section', testOtherSitesSection); | ||||
|     it('should allow create/import from intro screen', testCreateImport.bind(null, false)); | ||||
|     it('should link to examples page from the intro', testExamplesPage); | ||||
| @ -129,6 +130,22 @@ describe('HomeIntro', function() { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async function testMetaTags() { | ||||
|     const expectedTitle = 'Welcome - Grist'; | ||||
|     assert.equal(await driver.find('meta[name="twitter:title"]').getAttribute('content'), expectedTitle); | ||||
|     assert.equal(await driver.find('meta[property="og:title"]').getAttribute('content'), expectedTitle); | ||||
| 
 | ||||
|     const expectedDescription = 'Grist is the evolution of spreadsheets.'; | ||||
|     assert.equal(await driver.find('meta[name="description"]').getAttribute('content'), expectedDescription); | ||||
|     assert.equal(await driver.find('meta[name="twitter:description"]').getAttribute('content'), expectedDescription); | ||||
|     assert.equal(await driver.find('meta[property="og:description"]').getAttribute('content'), expectedDescription); | ||||
| 
 | ||||
|     const gristIconFileName = 'icon-grist.png'; | ||||
|     assert.include(await driver.find('meta[name="thumbnail"]').getAttribute('content'), gristIconFileName); | ||||
|     assert.include(await driver.find('meta[name="twitter:image"]').getAttribute('content'), gristIconFileName); | ||||
|     assert.include(await driver.find('meta[property="og:image"]').getAttribute('content'), gristIconFileName); | ||||
|   } | ||||
| 
 | ||||
|   async function testCreateImport(isLoggedIn: boolean) { | ||||
|     await checkIntroButtons(isLoggedIn); | ||||
| 
 | ||||
|  | ||||
| @ -18,7 +18,22 @@ describe('NewDocument.ntest', function() { | ||||
|     this.timeout(10000); | ||||
|     await gu.actions.createNewDoc('Untitled'); | ||||
|     assert.equal(await gu.actions.getDocTitle(), 'Untitled'); | ||||
|     assert.equal(await driver.getTitle(), 'Untitled - Grist'); | ||||
| 
 | ||||
|     const expectedTitle = 'Untitled - Grist'; | ||||
|     assert.equal(await driver.getTitle(), expectedTitle); | ||||
|     assert.equal(await driver.find('meta[name="twitter:title"]').getAttribute('content'), expectedTitle); | ||||
|     assert.equal(await driver.find('meta[property="og:title"]').getAttribute('content'), expectedTitle); | ||||
| 
 | ||||
|     const expectedDescription = 'Grist is the evolution of spreadsheets.'; | ||||
|     assert.equal(await driver.find('meta[name="description"]').getAttribute('content'), expectedDescription); | ||||
|     assert.equal(await driver.find('meta[name="twitter:description"]').getAttribute('content'), expectedDescription); | ||||
|     assert.equal(await driver.find('meta[property="og:description"]').getAttribute('content'), expectedDescription); | ||||
| 
 | ||||
|     const gristIconFileName = 'icon-grist.png'; | ||||
|     assert.include(await driver.find('meta[name="thumbnail"]').getAttribute('content'), gristIconFileName); | ||||
|     assert.include(await driver.find('meta[name="twitter:image"]').getAttribute('content'), gristIconFileName); | ||||
|     assert.include(await driver.find('meta[property="og:image"]').getAttribute('content'), gristIconFileName); | ||||
| 
 | ||||
|     assert.equal(await $('.active_section .test-viewsection-title').wait().text(), 'TABLE1'); | ||||
|     await gu.waitForServer(); | ||||
|   }); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user