You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Desktop, Mobile: Fixes #4119: Fixed links imported from ENEX as HTML
This commit is contained in:
		| @@ -1351,6 +1351,9 @@ packages/lib/uuid.js.map | ||||
| packages/lib/versionInfo.d.ts | ||||
| packages/lib/versionInfo.js | ||||
| packages/lib/versionInfo.js.map | ||||
| packages/renderer/HtmlToHtml.d.ts | ||||
| packages/renderer/HtmlToHtml.js | ||||
| packages/renderer/HtmlToHtml.js.map | ||||
| packages/renderer/InMemoryCache.d.ts | ||||
| packages/renderer/InMemoryCache.js | ||||
| packages/renderer/InMemoryCache.js.map | ||||
| @@ -1360,6 +1363,12 @@ packages/renderer/MarkupToHtml.js.map | ||||
| packages/renderer/MdToHtml.d.ts | ||||
| packages/renderer/MdToHtml.js | ||||
| packages/renderer/MdToHtml.js.map | ||||
| packages/renderer/MdToHtml/linkReplacement.d.ts | ||||
| packages/renderer/MdToHtml/linkReplacement.js | ||||
| packages/renderer/MdToHtml/linkReplacement.js.map | ||||
| packages/renderer/MdToHtml/linkReplacement.test.d.ts | ||||
| packages/renderer/MdToHtml/linkReplacement.test.js | ||||
| packages/renderer/MdToHtml/linkReplacement.test.js.map | ||||
| packages/renderer/MdToHtml/rules/checkbox.d.ts | ||||
| packages/renderer/MdToHtml/rules/checkbox.js | ||||
| packages/renderer/MdToHtml/rules/checkbox.js.map | ||||
| @@ -1393,6 +1402,9 @@ packages/renderer/MdToHtml/rules/mermaid.js.map | ||||
| packages/renderer/MdToHtml/rules/sanitize_html.d.ts | ||||
| packages/renderer/MdToHtml/rules/sanitize_html.js | ||||
| packages/renderer/MdToHtml/rules/sanitize_html.js.map | ||||
| packages/renderer/htmlUtils.d.ts | ||||
| packages/renderer/htmlUtils.js | ||||
| packages/renderer/htmlUtils.js.map | ||||
| packages/renderer/index.d.ts | ||||
| packages/renderer/index.js | ||||
| packages/renderer/index.js.map | ||||
| @@ -1402,4 +1414,7 @@ packages/renderer/noteStyle.js.map | ||||
| packages/renderer/pathUtils.d.ts | ||||
| packages/renderer/pathUtils.js | ||||
| packages/renderer/pathUtils.js.map | ||||
| packages/renderer/utils.d.ts | ||||
| packages/renderer/utils.js | ||||
| packages/renderer/utils.js.map | ||||
| # AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD | ||||
|   | ||||
							
								
								
									
										15
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1343,6 +1343,9 @@ packages/lib/uuid.js.map | ||||
| packages/lib/versionInfo.d.ts | ||||
| packages/lib/versionInfo.js | ||||
| packages/lib/versionInfo.js.map | ||||
| packages/renderer/HtmlToHtml.d.ts | ||||
| packages/renderer/HtmlToHtml.js | ||||
| packages/renderer/HtmlToHtml.js.map | ||||
| packages/renderer/InMemoryCache.d.ts | ||||
| packages/renderer/InMemoryCache.js | ||||
| packages/renderer/InMemoryCache.js.map | ||||
| @@ -1352,6 +1355,12 @@ packages/renderer/MarkupToHtml.js.map | ||||
| packages/renderer/MdToHtml.d.ts | ||||
| packages/renderer/MdToHtml.js | ||||
| packages/renderer/MdToHtml.js.map | ||||
| packages/renderer/MdToHtml/linkReplacement.d.ts | ||||
| packages/renderer/MdToHtml/linkReplacement.js | ||||
| packages/renderer/MdToHtml/linkReplacement.js.map | ||||
| packages/renderer/MdToHtml/linkReplacement.test.d.ts | ||||
| packages/renderer/MdToHtml/linkReplacement.test.js | ||||
| packages/renderer/MdToHtml/linkReplacement.test.js.map | ||||
| packages/renderer/MdToHtml/rules/checkbox.d.ts | ||||
| packages/renderer/MdToHtml/rules/checkbox.js | ||||
| packages/renderer/MdToHtml/rules/checkbox.js.map | ||||
| @@ -1385,6 +1394,9 @@ packages/renderer/MdToHtml/rules/mermaid.js.map | ||||
| packages/renderer/MdToHtml/rules/sanitize_html.d.ts | ||||
| packages/renderer/MdToHtml/rules/sanitize_html.js | ||||
| packages/renderer/MdToHtml/rules/sanitize_html.js.map | ||||
| packages/renderer/htmlUtils.d.ts | ||||
| packages/renderer/htmlUtils.js | ||||
| packages/renderer/htmlUtils.js.map | ||||
| packages/renderer/index.d.ts | ||||
| packages/renderer/index.js | ||||
| packages/renderer/index.js.map | ||||
| @@ -1394,4 +1406,7 @@ packages/renderer/noteStyle.js.map | ||||
| packages/renderer/pathUtils.d.ts | ||||
| packages/renderer/pathUtils.js | ||||
| packages/renderer/pathUtils.js.map | ||||
| packages/renderer/utils.d.ts | ||||
| packages/renderer/utils.js | ||||
| packages/renderer/utils.js.map | ||||
| # AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD | ||||
| @@ -9,7 +9,7 @@ const Folder = require('@joplin/lib/models/Folder.js'); | ||||
| const Note = require('@joplin/lib/models/Note.js'); | ||||
| const BaseModel = require('@joplin/lib/BaseModel').default; | ||||
| const shim = require('@joplin/lib/shim').default; | ||||
| const HtmlToHtml = require('@joplin/renderer/HtmlToHtml'); | ||||
| const HtmlToHtml = require('@joplin/renderer/HtmlToHtml').default; | ||||
| const { enexXmlToMd } = require('@joplin/lib/import-enex-md-gen.js'); | ||||
|  | ||||
| process.on('unhandledRejection', (reason, p) => { | ||||
|   | ||||
| @@ -3,15 +3,16 @@ import { FormNote, defaultFormNote, ResourceInfos } from './types'; | ||||
| import { clearResourceCache, attachedResources } from './resourceHandling'; | ||||
| import AsyncActionQueue from '@joplin/lib/AsyncActionQueue'; | ||||
| import { handleResourceDownloadMode } from './resourceHandling'; | ||||
| import HtmlToHtml from '@joplin/renderer/HtmlToHtml'; | ||||
| import Setting from '@joplin/lib/models/Setting'; | ||||
| import usePrevious from '../../hooks/usePrevious'; | ||||
| import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index'; | ||||
|  | ||||
| const { MarkupToHtml } = require('@joplin/renderer'); | ||||
| const HtmlToHtml = require('@joplin/renderer/HtmlToHtml'); | ||||
| const usePrevious = require('../../hooks/usePrevious').default; | ||||
| const Note = require('@joplin/lib/models/Note'); | ||||
| const Setting = require('@joplin/lib/models/Setting').default; | ||||
| const { reg } = require('@joplin/lib/registry.js'); | ||||
| const ResourceFetcher = require('@joplin/lib/services/ResourceFetcher.js'); | ||||
| const DecryptionWorker = require('@joplin/lib/services/DecryptionWorker.js'); | ||||
| const ResourceEditWatcher = require('@joplin/lib/services/ResourceEditWatcher/index').default; | ||||
|  | ||||
| export interface OnLoadEvent { | ||||
| 	formNote: FormNote; | ||||
|   | ||||
| @@ -11,7 +11,8 @@ | ||||
|     "tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json", | ||||
|     "watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json", | ||||
|     "start": "gulp build && electron . --env dev --log-level debug --no-welcome --open-dev-tools", | ||||
|     "test": "jest" | ||||
|     "test": "jest", | ||||
|     "test-ci": "test" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|   | ||||
| @@ -87,7 +87,6 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number, | ||||
| 				codeTheme: theme.codeThemeCss, | ||||
| 				postMessageSyntax: 'window.joplinPostMessage_', | ||||
| 				enableLongPress: shim.mobilePlatform() === 'android', // On iOS, there's already a built-on open/share menu | ||||
| 				longPressDelay: 500, // TODO use system value | ||||
| 			}; | ||||
|  | ||||
| 			// Whenever a resource state changes, for example when it goes from "not downloaded" to "downloaded", the "noteResources" | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| const htmlUtils = require('./htmlUtils'); | ||||
| const utils = require('./utils'); | ||||
| // const noteStyle = require('./noteStyle').default;
 | ||||
| import htmlUtils from './htmlUtils'; | ||||
| import linkReplacement from './MdToHtml/linkReplacement'; | ||||
| import utils from './utils'; | ||||
| 
 | ||||
| // TODO: fix
 | ||||
| // const Setting = require('@joplin/lib/models/Setting').default;
 | ||||
| @@ -13,9 +13,45 @@ const md5 = require('md5'); | ||||
| // relatively small.
 | ||||
| const inMemoryCache = new InMemoryCache(10); | ||||
| 
 | ||||
| class HtmlToHtml { | ||||
| 	constructor(options) { | ||||
| 		if (!options) options = {}; | ||||
| interface FsDriver { | ||||
| 	writeFile: Function; | ||||
| 	exists: Function; | ||||
| 	cacheCssToFile: Function; | ||||
| } | ||||
| 
 | ||||
| interface Options { | ||||
| 	ResourceModel: any; | ||||
| 	resourceBaseUrl?: string; | ||||
| 	fsDriver?: FsDriver; | ||||
| } | ||||
| 
 | ||||
| interface RenderOptions { | ||||
| 	splitted: boolean; | ||||
| 	bodyOnly: boolean; | ||||
| 	externalAssetsOnly: boolean; | ||||
| 	resources: any; | ||||
| 	postMessageSyntax: string; | ||||
| 	enableLongPress: boolean; | ||||
| } | ||||
| 
 | ||||
| interface RenderResult { | ||||
| 	html: string; | ||||
| 	pluginAssets: any[]; | ||||
| } | ||||
| 
 | ||||
| export default class HtmlToHtml { | ||||
| 
 | ||||
| 	private resourceBaseUrl_; | ||||
| 	private ResourceModel_; | ||||
| 	private cache_; | ||||
| 	private fsDriver_: any; | ||||
| 
 | ||||
| 	constructor(options: Options = null) { | ||||
| 		options = { | ||||
| 			ResourceModel: null, | ||||
| 			...options, | ||||
| 		}; | ||||
| 
 | ||||
| 		this.resourceBaseUrl_ = 'resourceBaseUrl' in options ? options.resourceBaseUrl : null; | ||||
| 		this.ResourceModel_ = options.ResourceModel; | ||||
| 		this.cache_ = inMemoryCache; | ||||
| @@ -36,7 +72,7 @@ class HtmlToHtml { | ||||
| 		return this.fsDriver_; | ||||
| 	} | ||||
| 
 | ||||
| 	splitHtml(html) { | ||||
| 	splitHtml(html: string) { | ||||
| 		const trimmedHtml = html.trimStart(); | ||||
| 		if (trimmedHtml.indexOf('<style>') !== 0) return { html: html, css: '' }; | ||||
| 
 | ||||
| @@ -49,17 +85,20 @@ class HtmlToHtml { | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	async allAssets(/* theme*/) { | ||||
| 	async allAssets(/* theme*/): Promise<any[]> { | ||||
| 		return []; // TODO
 | ||||
| 	} | ||||
| 
 | ||||
| 	// Note: the "theme" variable is ignored and instead the light theme is
 | ||||
| 	// always used for HTML notes.
 | ||||
| 	// See: https://github.com/laurent22/joplin/issues/3698
 | ||||
| 	async render(markup, _theme, options) { | ||||
| 		options = Object.assign({}, { | ||||
| 	async render(markup: string, _theme: any, options: RenderOptions): Promise<RenderResult> { | ||||
| 		options = { | ||||
| 			splitted: false, | ||||
| 		}, options); | ||||
| 			postMessageSyntax: 'postMessage', | ||||
| 			enableLongPress: false, | ||||
| 			...options, | ||||
| 		}; | ||||
| 
 | ||||
| 		const cacheKey = md5(escape(markup)); | ||||
| 		let html = this.cache_.value(cacheKey); | ||||
| @@ -67,7 +106,7 @@ class HtmlToHtml { | ||||
| 		if (!html) { | ||||
| 			html = htmlUtils.sanitizeHtml(markup); | ||||
| 
 | ||||
| 			html = htmlUtils.processImageTags(html, data => { | ||||
| 			html = htmlUtils.processImageTags(html, (data: any) => { | ||||
| 				if (!data.src) return null; | ||||
| 
 | ||||
| 				const r = utils.imageReplacement(this.ResourceModel_, data.src, options.resources, this.resourceBaseUrl_); | ||||
| @@ -85,6 +124,24 @@ class HtmlToHtml { | ||||
| 					}; | ||||
| 				} | ||||
| 			}); | ||||
| 
 | ||||
| 			html = htmlUtils.processAnchorTags(html, (data: any) => { | ||||
| 				if (!data.href) return null; | ||||
| 
 | ||||
| 				const r = linkReplacement(data.href, { | ||||
| 					resources: options.resources, | ||||
| 					ResourceModel: this.ResourceModel_, | ||||
| 					postMessageSyntax: options.postMessageSyntax, | ||||
| 					enableLongPress: options.enableLongPress, | ||||
| 				}); | ||||
| 
 | ||||
| 				if (!r) return null; | ||||
| 
 | ||||
| 				return { | ||||
| 					type: 'replaceElement', | ||||
| 					html: r, | ||||
| 				}; | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		this.cache_.setValue(cacheKey, html, 1000 * 60 * 10); | ||||
| @@ -98,13 +155,13 @@ class HtmlToHtml { | ||||
| 
 | ||||
| 		// const lightTheme = themeStyle(Setting.THEME_LIGHT);
 | ||||
| 		// let cssStrings = noteStyle(lightTheme);
 | ||||
| 		let cssStrings = []; | ||||
| 		let cssStrings: string[] = []; | ||||
| 
 | ||||
| 		if (options.splitted) { | ||||
| 			const splitted = this.splitHtml(html); | ||||
| 			cssStrings = [splitted.css].concat(cssStrings); | ||||
| 
 | ||||
| 			const output = { | ||||
| 			const output: RenderResult = { | ||||
| 				html: splitted.html, | ||||
| 				pluginAssets: [], | ||||
| 			}; | ||||
| @@ -124,5 +181,3 @@ class HtmlToHtml { | ||||
| 		}; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| module.exports = HtmlToHtml; | ||||
| @@ -1,6 +1,6 @@ | ||||
| import MdToHtml from './MdToHtml'; | ||||
| const HtmlToHtml = require('./HtmlToHtml'); | ||||
| const htmlUtils = require('./htmlUtils'); | ||||
| import HtmlToHtml from './HtmlToHtml'; | ||||
| import htmlUtils from './htmlUtils'; | ||||
| const MarkdownIt = require('markdown-it'); | ||||
|  | ||||
| export enum MarkupLanguage { | ||||
|   | ||||
| @@ -138,10 +138,6 @@ export interface RuleOptions { | ||||
| 	// to display a context menu. Used in `image.ts` and `link_open.ts` | ||||
| 	enableLongPress?: boolean; | ||||
|  | ||||
| 	// Used in mobile app when enableLongPress = true. Tells for how long | ||||
| 	// the resource should be pressed before the menu is shown. | ||||
| 	longPressDelay?: number; | ||||
|  | ||||
| 	// Use by `link_open` rule. | ||||
| 	// linkRenderingType = 1 is the regular rendering and clicking on it is handled via embedded JS (in onclick attribute) | ||||
| 	// linkRenderingType = 2 gives a plain link with no JS. Caller needs to handle clicking on the link. | ||||
|   | ||||
							
								
								
									
										53
									
								
								packages/renderer/MdToHtml/linkReplacement.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								packages/renderer/MdToHtml/linkReplacement.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| import linkReplacement from './linkReplacement'; | ||||
|  | ||||
| describe('linkReplacement', () => { | ||||
|  | ||||
| 	test('should handle non-resource links', () => { | ||||
| 		const r = linkReplacement('https://example.com/test'); | ||||
| 		expect(r).toBe('<a data-from-md href=\'https://example.com/test\' onclick=\'postMessage("https://example.com/test", { resourceId: "" }); return false;\'>'); | ||||
| 	}); | ||||
|  | ||||
| 	test('should handle non-resource links - simple rendering', () => { | ||||
| 		const r = linkReplacement('https://example.com/test', { linkRenderingType: 2 }); | ||||
| 		expect(r).toBe('<a data-from-md href=\'https://example.com/test\'>'); | ||||
| 	}); | ||||
|  | ||||
| 	test('should handle resource links - downloaded status', () => { | ||||
| 		const resourceId = 'f6afba55bdf74568ac94f8d1e3578d2c'; | ||||
|  | ||||
| 		const r = linkReplacement(`:/${resourceId}`, { | ||||
| 			ResourceModel: {}, | ||||
| 			resources: { | ||||
| 				[resourceId]: { | ||||
| 					item: {}, | ||||
| 					localState: { | ||||
| 						fetch_status: 2, // FETCH_STATUS_DONE | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}); | ||||
|  | ||||
| 		expect(r).toBe(`<a data-from-md data-resource-id='${resourceId}' href='#' onclick='postMessage("joplin://${resourceId}", { resourceId: "${resourceId}" }); return false;'><span class="resource-icon fa-joplin"></span>`); | ||||
| 	}); | ||||
|  | ||||
| 	test('should handle resource links - idle status', () => { | ||||
| 		const resourceId = 'f6afba55bdf74568ac94f8d1e3578d2c'; | ||||
|  | ||||
| 		const r = linkReplacement(`:/${resourceId}`, { | ||||
| 			ResourceModel: {}, | ||||
| 			resources: { | ||||
| 				[resourceId]: { | ||||
| 					item: {}, | ||||
| 					localState: { | ||||
| 						fetch_status: 0, // FETCH_STATUS_IDLE | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}); | ||||
|  | ||||
| 		// Since the icon is embedded as SVG, we only check for the prefix | ||||
| 		const expectedPrefix = `<a class="not-loaded-resource resource-status-notDownloaded" data-resource-id="${resourceId}"><img src="data:image/svg+xml;utf8`; | ||||
| 		expect(r.indexOf(expectedPrefix)).toBe(0); | ||||
| 	}); | ||||
|  | ||||
| }); | ||||
							
								
								
									
										104
									
								
								packages/renderer/MdToHtml/linkReplacement.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								packages/renderer/MdToHtml/linkReplacement.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| import utils from '../utils'; | ||||
| const Entities = require('html-entities').AllHtmlEntities; | ||||
| const htmlentities = new Entities().encode; | ||||
| const urlUtils = require('../urlUtils.js'); | ||||
| const { getClassNameForMimeType } = require('font-awesome-filetypes'); | ||||
|  | ||||
| export interface Options { | ||||
| 	title?: string; | ||||
| 	resources?: any; | ||||
| 	ResourceModel?: any; | ||||
| 	linkRenderingType?: number; | ||||
| 	plainResourceRendering?: boolean; | ||||
| 	postMessageSyntax?: string; | ||||
| 	enableLongPress?: boolean; | ||||
| } | ||||
|  | ||||
| export default function(href: string, options: Options = null) { | ||||
| 	options = { | ||||
| 		title: '', | ||||
| 		resources: {}, | ||||
| 		ResourceModel: null, | ||||
| 		linkRenderingType: 1, | ||||
| 		plainResourceRendering: false, | ||||
| 		postMessageSyntax: 'postMessage', | ||||
| 		enableLongPress: false, | ||||
| 		...options, | ||||
| 	}; | ||||
|  | ||||
| 	const resourceHrefInfo = urlUtils.parseResourceUrl(href); | ||||
| 	const isResourceUrl = options.resources && !!resourceHrefInfo; | ||||
| 	let title = options.title; | ||||
|  | ||||
| 	let resourceIdAttr = ''; | ||||
| 	let icon = ''; | ||||
| 	let hrefAttr = '#'; | ||||
| 	let mime = ''; | ||||
| 	let resourceId = ''; | ||||
| 	if (isResourceUrl) { | ||||
| 		resourceId = resourceHrefInfo.itemId; | ||||
|  | ||||
| 		const result = options.resources[resourceId]; | ||||
| 		const resourceStatus = utils.resourceStatus(options.ResourceModel, result); | ||||
|  | ||||
| 		if (result && result.item) { | ||||
| 			if (!title) title = result.item.title; | ||||
| 			mime = result.item.mime; | ||||
| 		} | ||||
|  | ||||
| 		if (result && resourceStatus !== 'ready' && !options.plainResourceRendering) { | ||||
| 			const icon = utils.resourceStatusFile(resourceStatus); | ||||
| 			return `<a class="not-loaded-resource resource-status-${resourceStatus}" data-resource-id="${resourceId}">` + `<img src="data:image/svg+xml;utf8,${htmlentities(icon)}"/>`; | ||||
| 		} else { | ||||
| 			href = `joplin://${resourceId}`; | ||||
| 			if (resourceHrefInfo.hash) href += `#${resourceHrefInfo.hash}`; | ||||
| 			resourceIdAttr = `data-resource-id='${resourceId}'`; | ||||
|  | ||||
| 			const iconType = mime ? getClassNameForMimeType(mime) : 'fa-joplin'; | ||||
|  | ||||
| 			// Icons are defined in lib/renderers/noteStyle using inline svg | ||||
| 			// The icons are taken from fork-awesome but use the font-awesome naming scheme in order | ||||
| 			// to be more compatible with the getClass library | ||||
| 			icon = `<span class="resource-icon ${iconType}"></span>`; | ||||
| 		} | ||||
| 	} else { | ||||
| 		// If the link is a plain URL (as opposed to a resource link), set the href to the actual | ||||
| 		// link. This allows the link to be exported too when exporting to PDF. | ||||
| 		hrefAttr = href; | ||||
| 	} | ||||
|  | ||||
| 	// A single quote is valid in a URL but we don't want any because the | ||||
| 	// href is already enclosed in single quotes. | ||||
| 	// https://github.com/laurent22/joplin/issues/2030 | ||||
| 	href = href.replace(/'/g, '%27'); | ||||
|  | ||||
| 	let js = `${options.postMessageSyntax}(${JSON.stringify(href)}, { resourceId: ${JSON.stringify(resourceId)} }); return false;`; | ||||
| 	if (options.enableLongPress && !!resourceId) { | ||||
| 		const onClick = `${options.postMessageSyntax}(${JSON.stringify(href)})`; | ||||
| 		const onLongClick = `${options.postMessageSyntax}("longclick:${resourceId}")`; | ||||
| 		const touchStart = `t=setTimeout(()=>{t=null; ${onLongClick};}, ${utils.longPressDelay});`; | ||||
| 		const cancel = 'if (!!t) {clearTimeout(t); t=null;'; | ||||
| 		const touchEnd = `${cancel} ${onClick};}`; | ||||
| 		js = `ontouchstart='${touchStart}' ontouchend='${touchEnd}' ontouchcancel='${cancel} ontouchmove="${cancel}'`; | ||||
| 	} else { | ||||
| 		js = `onclick='${js}'`; | ||||
| 	} | ||||
|  | ||||
| 	if (hrefAttr.indexOf('#') === 0 && href.indexOf('#') === 0) js = ''; // If it's an internal anchor, don't add any JS since the webview is going to handle navigating to the right place | ||||
|  | ||||
| 	const attrHtml = []; | ||||
| 	attrHtml.push('data-from-md'); | ||||
| 	if (resourceIdAttr) attrHtml.push(resourceIdAttr); | ||||
| 	if (title) attrHtml.push(`title='${htmlentities(title)}'`); | ||||
| 	if (mime) attrHtml.push(`type='${htmlentities(mime)}'`); | ||||
|  | ||||
| 	if (options.plainResourceRendering || options.linkRenderingType === 2) { | ||||
| 		icon = ''; | ||||
| 		attrHtml.push(`href='${htmlentities(href)}'`); | ||||
| 	} else { | ||||
| 		attrHtml.push(`href='${hrefAttr}'`); | ||||
| 		if (js) attrHtml.push(js); | ||||
| 	} | ||||
|  | ||||
| 	return `<a ${attrHtml.join(' ')}>${icon}`; | ||||
| } | ||||
| @@ -1,7 +1,6 @@ | ||||
| import { RuleOptions } from '../../MdToHtml'; | ||||
|  | ||||
| const htmlUtils = require('../../htmlUtils.js'); | ||||
| const utils = require('../../utils'); | ||||
| import htmlUtils from '../../htmlUtils'; | ||||
| import utils from '../../utils'; | ||||
|  | ||||
| function renderImageHtml(before: string, src: string, after: string, ruleOptions: RuleOptions) { | ||||
| 	const r = utils.imageReplacement(ruleOptions.ResourceModel, src, ruleOptions.resources, ruleOptions.resourceBaseUrl); | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import { RuleOptions } from '../../MdToHtml'; | ||||
|  | ||||
| const utils = require('../../utils'); | ||||
| const htmlUtils = require('../../htmlUtils.js'); | ||||
| import htmlUtils from '../../htmlUtils'; | ||||
| import utils from '../../utils'; | ||||
|  | ||||
| function plugin(markdownIt: any, ruleOptions: RuleOptions) { | ||||
| 	const defaultRender = markdownIt.renderer.rules.image; | ||||
| @@ -23,7 +22,7 @@ function plugin(markdownIt: any, ruleOptions: RuleOptions) { | ||||
| 				const id = r['data-resource-id']; | ||||
|  | ||||
| 				const longPressHandler = `${ruleOptions.postMessageSyntax}('longclick:${id}')`; | ||||
| 				const touchStart = `t=setTimeout(()=>{t=null; ${longPressHandler};}, ${ruleOptions.longPressDelay});`; | ||||
| 				const touchStart = `t=setTimeout(()=>{t=null; ${longPressHandler};}, ${utils.longPressDelay});`; | ||||
| 				const cancel = 'if (!!t) clearTimeout(t); t=null'; | ||||
|  | ||||
| 				js = ` ontouchstart="${touchStart}" ontouchend="${cancel}" ontouchcancel="${cancel}" ontouchmove="${cancel}"`; | ||||
|   | ||||
| @@ -1,95 +1,26 @@ | ||||
| import { RuleOptions } from '../../MdToHtml'; | ||||
| import linkReplacement from '../linkReplacement'; | ||||
| import utils from '../../utils'; | ||||
|  | ||||
| const Entities = require('html-entities').AllHtmlEntities; | ||||
| const htmlentities = new Entities().encode; | ||||
| const utils = require('../../utils'); | ||||
| const urlUtils = require('../../urlUtils.js'); | ||||
| const { getClassNameForMimeType } = require('font-awesome-filetypes'); | ||||
|  | ||||
| function plugin(markdownIt: any, ruleOptions: RuleOptions) { | ||||
| 	markdownIt.renderer.rules.link_open = function(tokens: any[], idx: number) { | ||||
| 		const token = tokens[idx]; | ||||
| 		let href = utils.getAttr(token.attrs, 'href'); | ||||
| 		const href = utils.getAttr(token.attrs, 'href'); | ||||
| 		const resourceHrefInfo = urlUtils.parseResourceUrl(href); | ||||
| 		const isResourceUrl = ruleOptions.resources && !!resourceHrefInfo; | ||||
| 		let title = utils.getAttr(token.attrs, 'title', isResourceUrl ? '' : href); | ||||
| 		const title = utils.getAttr(token.attrs, 'title', isResourceUrl ? '' : href); | ||||
|  | ||||
| 		let resourceIdAttr = ''; | ||||
| 		let icon = ''; | ||||
| 		let hrefAttr = '#'; | ||||
| 		let mime = ''; | ||||
| 		let resourceId = ''; | ||||
| 		if (isResourceUrl) { | ||||
| 			resourceId = resourceHrefInfo.itemId; | ||||
|  | ||||
| 			const result = ruleOptions.resources[resourceId]; | ||||
| 			const resourceStatus = utils.resourceStatus(ruleOptions.ResourceModel, result); | ||||
|  | ||||
| 			if (result && result.item) { | ||||
| 				title = utils.getAttr(token.attrs, 'title', result.item.title); | ||||
| 				mime = result.item.mime; | ||||
| 			} | ||||
|  | ||||
| 			if (result && resourceStatus !== 'ready' && !ruleOptions.plainResourceRendering) { | ||||
| 				const icon = utils.resourceStatusFile(resourceStatus); | ||||
| 				return `<a class="not-loaded-resource resource-status-${resourceStatus}" data-resource-id="${resourceId}">` + `<img src="data:image/svg+xml;utf8,${htmlentities(icon)}"/>`; | ||||
| 			} else { | ||||
| 				href = `joplin://${resourceId}`; | ||||
| 				if (resourceHrefInfo.hash) href += `#${resourceHrefInfo.hash}`; | ||||
| 				resourceIdAttr = `data-resource-id='${resourceId}'`; | ||||
|  | ||||
| 				let iconType = getClassNameForMimeType(mime); | ||||
| 				if (!mime) { | ||||
| 					iconType = 'fa-joplin'; | ||||
| 				} | ||||
| 				// Icons are defined in lib/renderers/noteStyle using inline svg | ||||
| 				// The icons are taken from fork-awesome but use the font-awesome naming scheme in order | ||||
| 				// to be more compatible with the getClass library | ||||
| 				icon = `<span class="resource-icon ${iconType}"></span>`; | ||||
| 			} | ||||
| 		} else { | ||||
| 			// If the link is a plain URL (as opposed to a resource link), set the href to the actual | ||||
| 			// link. This allows the link to be exported too when exporting to PDF. | ||||
| 			hrefAttr = href; | ||||
| 		} | ||||
|  | ||||
| 		// A single quote is valid in a URL but we don't want any because the | ||||
| 		// href is already enclosed in single quotes. | ||||
| 		// https://github.com/laurent22/joplin/issues/2030 | ||||
| 		href = href.replace(/'/g, '%27'); | ||||
|  | ||||
| 		let js = `${ruleOptions.postMessageSyntax}(${JSON.stringify(href)}, { resourceId: ${JSON.stringify(resourceId)} }); return false;`; | ||||
| 		if (ruleOptions.enableLongPress && !!resourceId) { | ||||
| 			const onClick = `${ruleOptions.postMessageSyntax}(${JSON.stringify(href)})`; | ||||
| 			const onLongClick = `${ruleOptions.postMessageSyntax}("longclick:${resourceId}")`; | ||||
| 			const touchStart = `t=setTimeout(()=>{t=null; ${onLongClick};}, ${ruleOptions.longPressDelay});`; | ||||
| 			const cancel = 'if (!!t) {clearTimeout(t); t=null;'; | ||||
| 			const touchEnd = `${cancel} ${onClick};}`; | ||||
| 			js = `ontouchstart='${touchStart}' ontouchend='${touchEnd}' ontouchcancel='${cancel} ontouchmove="${cancel}'`; | ||||
| 		} else { | ||||
| 			js = `onclick='${js}'`; | ||||
| 		} | ||||
|  | ||||
| 		if (hrefAttr.indexOf('#') === 0 && href.indexOf('#') === 0) js = ''; // If it's an internal anchor, don't add any JS since the webview is going to handle navigating to the right place | ||||
|  | ||||
| 		const attrHtml = []; | ||||
| 		attrHtml.push('data-from-md'); | ||||
| 		if (resourceIdAttr) attrHtml.push(resourceIdAttr); | ||||
| 		if (title) attrHtml.push(`title='${htmlentities(title)}'`); | ||||
| 		if (mime) attrHtml.push(`type='${htmlentities(mime)}'`); | ||||
|  | ||||
| 		if (ruleOptions.plainResourceRendering || ruleOptions.linkRenderingType === 2) { | ||||
| 			icon = ''; | ||||
| 			attrHtml.push(`href='${htmlentities(href)}'`); | ||||
|  | ||||
| 			// return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`; | ||||
| 		} else { | ||||
| 			attrHtml.push(`href='${hrefAttr}'`); | ||||
| 			if (js) attrHtml.push(js); | ||||
| 			// return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${hrefAttr}' ${js} type='${htmlentities(mime)}'>${icon}`; | ||||
| 		} | ||||
|  | ||||
| 		return `<a ${attrHtml.join(' ')}>${icon}`; | ||||
| 		return linkReplacement(href, { | ||||
| 			title, | ||||
| 			resources: ruleOptions.resources, | ||||
| 			ResourceModel: ruleOptions.ResourceModel, | ||||
| 			linkRenderingType: ruleOptions.linkRenderingType, | ||||
| 			plainResourceRendering: ruleOptions.plainResourceRendering, | ||||
| 			postMessageSyntax: ruleOptions.postMessageSyntax, | ||||
| 			enableLongPress: ruleOptions.enableLongPress, | ||||
| 		}); | ||||
| 	}; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { RuleOptions } from '../../MdToHtml'; | ||||
| import htmlUtils from '../../htmlUtils'; | ||||
|  | ||||
| const md5 = require('md5'); | ||||
| const htmlUtils = require('../../htmlUtils'); | ||||
|  | ||||
| export default { | ||||
| 	plugin: function(markdownIt: any, ruleOptions: RuleOptions) { | ||||
|   | ||||
| @@ -6,6 +6,8 @@ const htmlparser2 = require('@joplin/fork-htmlparser2'); | ||||
| // https://stackoverflow.com/a/16119722/561309
 | ||||
| const imageRegex = /<img([\s\S]*?)src=["']([\s\S]*?)["']([\s\S]*?)>/gi; | ||||
| 
 | ||||
| const anchorRegex = /<a([\s\S]*?)href=["']([\s\S]*?)["']([\s\S]*?)>/gi; | ||||
| 
 | ||||
| const selfClosingElements = [ | ||||
| 	'area', | ||||
| 	'base', | ||||
| @@ -30,7 +32,7 @@ const selfClosingElements = [ | ||||
| 
 | ||||
| class HtmlUtils { | ||||
| 
 | ||||
| 	attributesHtml(attr) { | ||||
| 	attributesHtml(attr: any) { | ||||
| 		const output = []; | ||||
| 
 | ||||
| 		for (const n in attr) { | ||||
| @@ -41,10 +43,10 @@ class HtmlUtils { | ||||
| 		return output.join(' '); | ||||
| 	} | ||||
| 
 | ||||
| 	processImageTags(html, callback) { | ||||
| 	processImageTags(html: string, callback: Function) { | ||||
| 		if (!html) return ''; | ||||
| 
 | ||||
| 		return html.replace(imageRegex, (v, before, src, after) => { | ||||
| 		return html.replace(imageRegex, (_v, before, src, after) => { | ||||
| 			const action = callback({ src: src }); | ||||
| 
 | ||||
| 			if (!action) return `<img${before}src="${src}"${after}>`; | ||||
| @@ -66,15 +68,40 @@ class HtmlUtils { | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	isSelfClosingTag(tagName) { | ||||
| 	processAnchorTags(html: string, callback: Function) { | ||||
| 		if (!html) return ''; | ||||
| 
 | ||||
| 		return html.replace(anchorRegex, (_v, before, href, after) => { | ||||
| 			const action = callback({ href: href }); | ||||
| 
 | ||||
| 			if (!action) return `<a${before}href="${href}"${after}>`; | ||||
| 
 | ||||
| 			if (action.type === 'replaceElement') { | ||||
| 				return action.html; | ||||
| 			} | ||||
| 
 | ||||
| 			if (action.type === 'replaceSource') { | ||||
| 				return `<img${before}href="${action.href}"${after}>`; | ||||
| 			} | ||||
| 
 | ||||
| 			if (action.type === 'setAttributes') { | ||||
| 				const attrHtml = this.attributesHtml(action.attrs); | ||||
| 				return `<img${before}${attrHtml}${after}>`; | ||||
| 			} | ||||
| 
 | ||||
| 			throw new Error(`Invalid action: ${action.type}`); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	isSelfClosingTag(tagName: string) { | ||||
| 		return selfClosingElements.includes(tagName.toLowerCase()); | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: copied from @joplin/lib
 | ||||
| 	stripHtml(html) { | ||||
| 		const output = []; | ||||
| 	stripHtml(html: string) { | ||||
| 		const output: string[] = []; | ||||
| 
 | ||||
| 		const tagStack = []; | ||||
| 		const tagStack: string[] = []; | ||||
| 
 | ||||
| 		const currentTag = () => { | ||||
| 			if (!tagStack.length) return ''; | ||||
| @@ -85,16 +112,16 @@ class HtmlUtils { | ||||
| 
 | ||||
| 		const parser = new htmlparser2.Parser({ | ||||
| 
 | ||||
| 			onopentag: (name) => { | ||||
| 			onopentag: (name: string) => { | ||||
| 				tagStack.push(name.toLowerCase()); | ||||
| 			}, | ||||
| 
 | ||||
| 			ontext: (decodedText) => { | ||||
| 			ontext: (decodedText: string) => { | ||||
| 				if (disallowedTags.includes(currentTag())) return; | ||||
| 				output.push(decodedText); | ||||
| 			}, | ||||
| 
 | ||||
| 			onclosetag: (name) => { | ||||
| 			onclosetag: (name: string) => { | ||||
| 				if (currentTag() === name.toLowerCase()) tagStack.pop(); | ||||
| 			}, | ||||
| 
 | ||||
| @@ -106,16 +133,16 @@ class HtmlUtils { | ||||
| 		return output.join('').replace(/\s+/g, ' '); | ||||
| 	} | ||||
| 
 | ||||
| 	sanitizeHtml(html, options = null) { | ||||
| 	sanitizeHtml(html: string, options: any = null) { | ||||
| 		options = Object.assign({}, { | ||||
| 			// If true, adds a "jop-noMdConv" class to all the tags.
 | ||||
| 			// It can be used afterwards to restore HTML tags in Markdown.
 | ||||
| 			addNoMdConvClass: false, | ||||
| 		}, options); | ||||
| 
 | ||||
| 		const output = []; | ||||
| 		const output: string[] = []; | ||||
| 
 | ||||
| 		const tagStack = []; | ||||
| 		const tagStack: string[] = []; | ||||
| 
 | ||||
| 		const currentTag = () => { | ||||
| 			if (!tagStack.length) return ''; | ||||
| @@ -135,7 +162,7 @@ class HtmlUtils { | ||||
| 
 | ||||
| 		const parser = new htmlparser2.Parser({ | ||||
| 
 | ||||
| 			onopentag: (name, attrs) => { | ||||
| 			onopentag: (name: string, attrs: any) => { | ||||
| 				tagStack.push(name.toLowerCase()); | ||||
| 
 | ||||
| 				if (disallowedTags.includes(currentTag())) return; | ||||
| @@ -171,7 +198,7 @@ class HtmlUtils { | ||||
| 				output.push(`<${name}${attrHtml}${closingSign}`); | ||||
| 			}, | ||||
| 
 | ||||
| 			ontext: (decodedText) => { | ||||
| 			ontext: (decodedText: string) => { | ||||
| 				if (disallowedTags.includes(currentTag())) return; | ||||
| 
 | ||||
| 				if (currentTag() === 'style') { | ||||
| @@ -184,7 +211,7 @@ class HtmlUtils { | ||||
| 				} | ||||
| 			}, | ||||
| 
 | ||||
| 			onclosetag: (name) => { | ||||
| 			onclosetag: (name: string) => { | ||||
| 				const current = currentTag(); | ||||
| 
 | ||||
| 				if (current === name.toLowerCase()) tagStack.pop(); | ||||
| @@ -206,6 +233,4 @@ class HtmlUtils { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| const htmlUtils = new HtmlUtils(); | ||||
| 
 | ||||
| module.exports = htmlUtils; | ||||
| export default new HtmlUtils(); | ||||
| @@ -1,9 +1,9 @@ | ||||
| import MarkupToHtml, { MarkupLanguage } from './MarkupToHtml'; | ||||
| import MdToHtml from './MdToHtml'; | ||||
| const HtmlToHtml = require('./HtmlToHtml'); | ||||
| import HtmlToHtml from './HtmlToHtml'; | ||||
| import utils from './utils'; | ||||
| const setupLinkify = require('./MdToHtml/setupLinkify'); | ||||
| const assetsToHeaders = require('./assetsToHeaders'); | ||||
| const utils = require('./utils'); | ||||
|  | ||||
| export { | ||||
| 	MarkupToHtml, | ||||
|   | ||||
							
								
								
									
										191
									
								
								packages/renderer/jest.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								packages/renderer/jest.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| // For a detailed explanation regarding each configuration property, visit: | ||||
| // https://jestjs.io/docs/en/configuration.html | ||||
|  | ||||
| module.exports = { | ||||
| 	// All imported modules in your tests should be mocked automatically | ||||
| 	// automock: false, | ||||
|  | ||||
| 	// Stop running tests after `n` failures | ||||
| 	// bail: 0, | ||||
|  | ||||
| 	// The directory where Jest should store its cached dependency information | ||||
| 	// cacheDirectory: "/tmp/jest_rs", | ||||
|  | ||||
| 	// Automatically clear mock calls and instances between every test | ||||
| 	// clearMocks: false, | ||||
|  | ||||
| 	// Indicates whether the coverage information should be collected while executing the test | ||||
| 	// collectCoverage: false, | ||||
|  | ||||
| 	// An array of glob patterns indicating a set of files for which coverage information should be collected | ||||
| 	// collectCoverageFrom: undefined, | ||||
|  | ||||
| 	// The directory where Jest should output its coverage files | ||||
| 	// coverageDirectory: undefined, | ||||
|  | ||||
| 	// An array of regexp pattern strings used to skip coverage collection | ||||
| 	// coveragePathIgnorePatterns: [ | ||||
| 	//   "/node_modules/" | ||||
| 	// ], | ||||
|  | ||||
| 	// Indicates which provider should be used to instrument code for coverage | ||||
| 	coverageProvider: 'v8', | ||||
|  | ||||
| 	// A list of reporter names that Jest uses when writing coverage reports | ||||
| 	// coverageReporters: [ | ||||
| 	//   "json", | ||||
| 	//   "text", | ||||
| 	//   "lcov", | ||||
| 	//   "clover" | ||||
| 	// ], | ||||
|  | ||||
| 	// An object that configures minimum threshold enforcement for coverage results | ||||
| 	// coverageThreshold: undefined, | ||||
|  | ||||
| 	// A path to a custom dependency extractor | ||||
| 	// dependencyExtractor: undefined, | ||||
|  | ||||
| 	// Make calling deprecated APIs throw helpful error messages | ||||
| 	// errorOnDeprecated: false, | ||||
|  | ||||
| 	// Force coverage collection from ignored files using an array of glob patterns | ||||
| 	// forceCoverageMatch: [], | ||||
|  | ||||
| 	// A path to a module which exports an async function that is triggered once before all test suites | ||||
| 	// globalSetup: undefined, | ||||
|  | ||||
| 	// A path to a module which exports an async function that is triggered once after all test suites | ||||
| 	// globalTeardown: undefined, | ||||
|  | ||||
| 	// A set of global variables that need to be available in all test environments | ||||
| 	// globals: {}, | ||||
|  | ||||
| 	// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. | ||||
| 	// maxWorkers: "50%", | ||||
|  | ||||
| 	// An array of directory names to be searched recursively up from the requiring module's location | ||||
| 	// moduleDirectories: [ | ||||
| 	//   "node_modules" | ||||
| 	// ], | ||||
|  | ||||
| 	// An array of file extensions your modules use | ||||
| 	// moduleFileExtensions: [ | ||||
| 	//   "js", | ||||
| 	//   "json", | ||||
| 	//   "jsx", | ||||
| 	//   "ts", | ||||
| 	//   "tsx", | ||||
| 	//   "node" | ||||
| 	// ], | ||||
|  | ||||
| 	// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module | ||||
| 	// moduleNameMapper: {}, | ||||
|  | ||||
| 	// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader | ||||
| 	// modulePathIgnorePatterns: [], | ||||
|  | ||||
| 	// Activates notifications for test results | ||||
| 	// notify: false, | ||||
|  | ||||
| 	// An enum that specifies notification mode. Requires { notify: true } | ||||
| 	// notifyMode: "failure-change", | ||||
|  | ||||
| 	// A preset that is used as a base for Jest's configuration | ||||
| 	// preset: undefined, | ||||
|  | ||||
| 	// Run tests from one or more projects | ||||
| 	// projects: undefined, | ||||
|  | ||||
| 	// Use this configuration option to add custom reporters to Jest | ||||
| 	// reporters: undefined, | ||||
|  | ||||
| 	// Automatically reset mock state between every test | ||||
| 	// resetMocks: false, | ||||
|  | ||||
| 	// Reset the module registry before running each individual test | ||||
| 	// resetModules: false, | ||||
|  | ||||
| 	// A path to a custom resolver | ||||
| 	// resolver: undefined, | ||||
|  | ||||
| 	// Automatically restore mock state between every test | ||||
| 	// restoreMocks: false, | ||||
|  | ||||
| 	// The root directory that Jest should scan for tests and modules within | ||||
| 	// rootDir: undefined, | ||||
|  | ||||
| 	// A list of paths to directories that Jest should use to search for files in | ||||
| 	// roots: [ | ||||
| 	//   "<rootDir>" | ||||
| 	// ], | ||||
|  | ||||
| 	// Allows you to use a custom runner instead of Jest's default test runner | ||||
| 	// runner: "jest-runner", | ||||
|  | ||||
| 	// The paths to modules that run some code to configure or set up the testing environment before each test | ||||
| 	// setupFiles: [], | ||||
|  | ||||
| 	// A list of paths to modules that run some code to configure or set up the testing framework before each test | ||||
| 	// setupFilesAfterEnv: [], | ||||
|  | ||||
| 	// The number of seconds after which a test is considered as slow and reported as such in the results. | ||||
| 	// slowTestThreshold: 5, | ||||
|  | ||||
| 	// A list of paths to snapshot serializer modules Jest should use for snapshot testing | ||||
| 	// snapshotSerializers: [], | ||||
|  | ||||
| 	// The test environment that will be used for testing | ||||
| 	testEnvironment: 'node', | ||||
|  | ||||
| 	// Options that will be passed to the testEnvironment | ||||
| 	// testEnvironmentOptions: {}, | ||||
|  | ||||
| 	// Adds a location field to test results | ||||
| 	// testLocationInResults: false, | ||||
|  | ||||
| 	// The glob patterns Jest uses to detect test files | ||||
| 	testMatch: [ | ||||
| 		'**/*.test.js', | ||||
| 	], | ||||
|  | ||||
| 	// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped | ||||
| 	// testPathIgnorePatterns: [ | ||||
| 	//   "/node_modules/" | ||||
| 	// ], | ||||
|  | ||||
| 	// The regexp pattern or array of patterns that Jest uses to detect test files | ||||
| 	// testRegex: [], | ||||
|  | ||||
| 	// This option allows the use of a custom results processor | ||||
| 	// testResultsProcessor: undefined, | ||||
|  | ||||
| 	// This option allows use of a custom test runner | ||||
| 	// testRunner: "jasmine2", | ||||
|  | ||||
| 	// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href | ||||
| 	// testURL: "http://localhost", | ||||
|  | ||||
| 	// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" | ||||
| 	// timers: "real", | ||||
|  | ||||
| 	// A map from regular expressions to paths to transformers | ||||
| 	// transform: undefined, | ||||
|  | ||||
| 	// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation | ||||
| 	// transformIgnorePatterns: [ | ||||
| 	//   "/node_modules/", | ||||
| 	//   "\\.pnp\\.[^\\/]+$" | ||||
| 	// ], | ||||
|  | ||||
| 	// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them | ||||
| 	// unmockedModulePathPatterns: undefined, | ||||
|  | ||||
| 	// Indicates whether each individual test should be reported during the run | ||||
| 	// verbose: undefined, | ||||
|  | ||||
| 	// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode | ||||
| 	// watchPathIgnorePatterns: [], | ||||
|  | ||||
| 	// Whether to use watchman for file crawling | ||||
| 	// watchman: true, | ||||
| }; | ||||
							
								
								
									
										5830
									
								
								packages/renderer/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5830
									
								
								packages/renderer/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -12,16 +12,20 @@ | ||||
|     "buildAssets": "node Tools/buildAssets.js", | ||||
|     "prepublishOnly": "npm run buildAssets", | ||||
|     "tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json", | ||||
|     "watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json" | ||||
|     "watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json", | ||||
|     "test": "jest", | ||||
|     "test-ci": "test" | ||||
|   }, | ||||
|   "author": "", | ||||
|   "license": "MIT", | ||||
|   "devDependencies": { | ||||
|     "@types/node": "^14.14.6", | ||||
|     "jest": "^26.6.3", | ||||
|     "typescript": "^4.0.5" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@joplin/fork-htmlparser2": "^4.1.8", | ||||
|     "@types/jest": "^26.0.15", | ||||
|     "font-awesome-filetypes": "^2.1.0", | ||||
|     "fs-extra": "^8.1.0", | ||||
|     "highlight.js": "^10.2.1", | ||||
|   | ||||
| @@ -9,9 +9,9 @@ const FetchStatuses = { | ||||
| 	FETCH_STATUS_ERROR: 3, | ||||
| }; | ||||
| 
 | ||||
| const utils = {}; | ||||
| const utils: any = {}; | ||||
| 
 | ||||
| utils.getAttr = function(attrs, name, defaultValue = null) { | ||||
| utils.getAttr = function(attrs: string[], name: string, defaultValue: string = null) { | ||||
| 	for (let i = 0; i < attrs.length; i++) { | ||||
| 		if (attrs[i][0] === name) return attrs[i].length > 1 ? attrs[i][1] : null; | ||||
| 	} | ||||
| @@ -63,12 +63,12 @@ utils.loaderImage = function() { | ||||
| 	`;
 | ||||
| }; | ||||
| 
 | ||||
| utils.resourceStatusImage = function(status) { | ||||
| utils.resourceStatusImage = function(status: string) { | ||||
| 	if (status === 'notDownloaded') return utils.notDownloadedResource(); | ||||
| 	return utils.resourceStatusFile(status); | ||||
| }; | ||||
| 
 | ||||
| utils.resourceStatusFile = function(status) { | ||||
| utils.resourceStatusFile = function(status: string) { | ||||
| 	if (status === 'notDownloaded') return utils.notDownloadedResource(); | ||||
| 	if (status === 'downloading') return utils.loaderImage(); | ||||
| 	if (status === 'encrypted') return utils.loaderImage(); | ||||
| @@ -77,7 +77,7 @@ utils.resourceStatusFile = function(status) { | ||||
| 	throw new Error(`Unknown status: ${status}`); | ||||
| }; | ||||
| 
 | ||||
| utils.resourceStatusIndex = function(status) { | ||||
| utils.resourceStatusIndex = function(status: string) { | ||||
| 	if (status === 'error') return -1; | ||||
| 	if (status === 'notDownloaded') return 0; | ||||
| 	if (status === 'downloading') return 1; | ||||
| @@ -87,7 +87,7 @@ utils.resourceStatusIndex = function(status) { | ||||
| 	throw new Error(`Unknown status: ${status}`); | ||||
| }; | ||||
| 
 | ||||
| utils.resourceStatusName = function(index) { | ||||
| utils.resourceStatusName = function(index: number) { | ||||
| 	if (index === -1) return 'error'; | ||||
| 	if (index === 0) return 'notDownloaded'; | ||||
| 	if (index === 1) return 'downloading'; | ||||
| @@ -97,7 +97,7 @@ utils.resourceStatusName = function(index) { | ||||
| 	throw new Error(`Unknown index: ${index}`); | ||||
| }; | ||||
| 
 | ||||
| utils.resourceStatus = function(ResourceModel, resourceInfo) { | ||||
| utils.resourceStatus = function(ResourceModel: any, resourceInfo: any) { | ||||
| 	if (!ResourceModel) return 'ready'; | ||||
| 
 | ||||
| 	let resourceStatus = 'ready'; | ||||
| @@ -122,7 +122,7 @@ utils.resourceStatus = function(ResourceModel, resourceInfo) { | ||||
| 	return resourceStatus; | ||||
| }; | ||||
| 
 | ||||
| utils.imageReplacement = function(ResourceModel, src, resources, resourceBaseUrl) { | ||||
| utils.imageReplacement = function(ResourceModel: any, src: string, resources: any, resourceBaseUrl: string) { | ||||
| 	if (!ResourceModel || !resources) return null; | ||||
| 
 | ||||
| 	if (!ResourceModel.isResourceUrl(src)) return null; | ||||
| @@ -151,4 +151,8 @@ utils.imageReplacement = function(ResourceModel, src, resources, resourceBaseUrl | ||||
| 	return null; | ||||
| }; | ||||
| 
 | ||||
| module.exports = utils; | ||||
| // Used in mobile app when enableLongPress = true. Tells for how long
 | ||||
| // the resource should be pressed before the menu is shown.
 | ||||
| utils.longPressDelay = 500; | ||||
| 
 | ||||
| export default utils; | ||||
		Reference in New Issue
	
	Block a user