diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 2 | ||||
-rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 24 | ||||
-rw-r--r-- | src/client/views/nodes/WebBoxRenderer.js | 183 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/DashDocView.tsx | 2 |
5 files changed, 111 insertions, 102 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index eb0812cba..1c9f89fa0 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -689,7 +689,7 @@ export class CurrentUserUtils { }; const reqdFuncs:{[key:string]:any} = { ...params.funcs, - backgroundColor: params.scripts?.onClick /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally + backgroundColor: params.btnType === ButtonType.ToggleButton ? params.scripts?.onClick:undefined /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally } return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index a48906372..04c7b96e3 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -222,7 +222,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF panelWidth = () => this.sizeProvider?.width || this.props.PanelWidth?.(); panelHeight = () => this.sizeProvider?.height || this.props.PanelHeight?.(); screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y); - focusDoc = (doc: Doc) => this.props.focus(doc); + focusDoc = (doc: Doc) => this.props.focus(doc, {}); returnThis = () => this; render() { TraceMobx(); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 460edb7c2..db493934a 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -156,6 +156,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.rootDoc.thumbLockout = true; // lock to prevent multiple thumb updates. CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop) .then((data_url: any) => { + if (data_url.includes('<!DOCTYPE')) { + console.log('BAD DATA IN THUMB CREATION'); + return; + } VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename => setTimeout( action(() => { @@ -369,10 +373,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps iframeScaling = () => 1 / this.props.ScreenToLocalTransform().Scale; addStyleSheet(document: any, styleType: string = 'text/css') { - const style = document.createElement('style'); - style.type = styleType; - const sheets = document.head.appendChild(style); - return (sheets as any).sheet; + if (document) { + const style = document.createElement('style'); + style.type = styleType; + const sheets = document.head.appendChild(style); + return (sheets as any).sheet; + } } addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') { const propText = @@ -381,7 +387,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps : Object.keys(css) .map(p => p + ':' + (p === 'content' ? "'" + css[p] + "'" : css[p])) .join(';'); - return sheet.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length); + return sheet?.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length); } _iframetimeout: any = undefined; @@ -394,7 +400,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.addStyleSheetRule(this.addStyleSheet(this._iframe?.contentDocument), '::selection', { color: 'white', background: 'orange' }, ''); - let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString()); + let href: Opt<string>; + try { + href = iframe?.contentWindow?.location.href; + } catch (e) { + href = undefined; + } + let requrlraw = decodeURIComponent(href?.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString()); if (requrlraw !== this._url.toString()) { if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) { const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g); diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js index f3f1bcf5c..cebb94d86 100644 --- a/src/client/views/nodes/WebBoxRenderer.js +++ b/src/client/views/nodes/WebBoxRenderer.js @@ -1,14 +1,13 @@ /** - * - * @param {StyleSheetList} styleSheets + * + * @param {StyleSheetList} styleSheets */ var ForeignHtmlRenderer = function (styleSheets) { - const self = this; /** - * - * @param {String} binStr + * + * @param {String} binStr */ const binaryStringToBase64 = function (binStr) { return new Promise(function (resolve) { @@ -16,7 +15,7 @@ var ForeignHtmlRenderer = function (styleSheets) { reader.readAsDataURL(binStr); reader.onloadend = function () { resolve(reader.result); - } + }; }); }; @@ -24,11 +23,11 @@ var ForeignHtmlRenderer = function (styleSheets) { return window.location.origin + extension; } function CorsProxy(url) { - return prepend("/corsProxy/") + encodeURIComponent(url); + return prepend('/corsProxy/') + encodeURIComponent(url); } /** - * - * @param {String} url + * + * @param {String} url * @returns {Promise} */ const getResourceAsBase64 = function (webUrl, inurl) { @@ -37,35 +36,30 @@ var ForeignHtmlRenderer = function (styleSheets) { //const url = inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl; //const url = CorsProxy(inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl);// inurl.startsWith("http") ? CorsProxy(inurl) : inurl; var url = inurl; - if (inurl.startsWith("/static")) { - url = (new URL(webUrl).origin + inurl); - } else - if ((inurl.startsWith("/") && !inurl.startsWith("//"))) { - url = CorsProxy(new URL(webUrl).origin + inurl); - } else if (!inurl.startsWith("http") && !inurl.startsWith("//")) { - url = CorsProxy(webUrl + "/" + inurl); - } - xhr.open("GET", url); + if (inurl.startsWith('/static')) { + url = new URL(webUrl).origin + inurl; + } else if (inurl.startsWith('/') && !inurl.startsWith('//')) { + url = CorsProxy(new URL(webUrl).origin + inurl); + } else if (!inurl.startsWith('http') && !inurl.startsWith('//')) { + url = CorsProxy(webUrl + '/' + inurl); + } + xhr.open('GET', url); xhr.responseType = 'blob'; xhr.onreadystatechange = async function () { if (xhr.readyState === 4 && xhr.status === 200) { const resBase64 = await binaryStringToBase64(xhr.response); - resolve( - { - "resourceUrl": inurl, - "resourceBase64": resBase64 - } - ); + resolve({ + resourceUrl: inurl, + resourceBase64: resBase64, + }); } else if (xhr.readyState === 4) { - console.log("COULDN'T FIND: " + (inurl.startsWith("/") ? webUrl + inurl : inurl)); - resolve( - { - "resourceUrl": "", - "resourceBase64": inurl - } - ); + console.log("COULDN'T FIND: " + (inurl.startsWith('/') ? webUrl + inurl : inurl)); + resolve({ + resourceUrl: '', + resourceBase64: inurl, + }); } }; @@ -74,8 +68,8 @@ var ForeignHtmlRenderer = function (styleSheets) { }; /** - * - * @param {String[]} urls + * + * @param {String[]} urls * @returns {Promise} */ const getMultipleResourcesAsBase64 = function (webUrl, urls) { @@ -87,13 +81,13 @@ var ForeignHtmlRenderer = function (styleSheets) { }; /** - * - * @param {String} str - * @param {Number} startIndex - * @param {String} prefixToken + * + * @param {String} str + * @param {Number} startIndex + * @param {String} prefixToken * @param {String[]} suffixTokens - * - * @returns {String|null} + * + * @returns {String|null} */ const parseValue = function (str, startIndex, prefixToken, suffixTokens) { const idx = str.indexOf(prefixToken, startIndex); @@ -111,17 +105,17 @@ var ForeignHtmlRenderer = function (styleSheets) { } return { - "foundAtIndex": idx, - "value": val - } + foundAtIndex: idx, + value: val, + }; }; /** - * - * @param {String} cssRuleStr + * + * @param {String} cssRuleStr * @returns {String[]} */ - const getUrlsFromCssString = function (cssRuleStr, selector = "url(", delimiters = [')'], mustEndWithQuote = false) { + const getUrlsFromCssString = function (cssRuleStr, selector = 'url(', delimiters = [')'], mustEndWithQuote = false) { const urlsFound = []; let searchStartIndex = 0; @@ -133,7 +127,7 @@ var ForeignHtmlRenderer = function (styleSheets) { searchStartIndex = url.foundAtIndex + url.value.length; if (mustEndWithQuote && url.value[url.value.length - 1] !== '"') continue; const unquoted = removeQuotes(url.value); - if (!unquoted /* || (!unquoted.startsWith('http')&& !unquoted.startsWith("/") )*/ || unquoted === 'http://' || unquoted === 'https://') { + if (!unquoted /* || (!unquoted.startsWith('http')&& !unquoted.startsWith("/") )*/ || unquoted === 'http://' || unquoted === 'https://') { continue; } @@ -144,24 +138,24 @@ var ForeignHtmlRenderer = function (styleSheets) { }; /** - * - * @param {String} html + * + * @param {String} html * @returns {String[]} */ const getImageUrlsFromFromHtml = function (html) { - return getUrlsFromCssString(html, "src=", [' ', '>', '\t'], true); + return getUrlsFromCssString(html, 'src=', [' ', '>', '\t'], true); }; const getSourceUrlsFromFromHtml = function (html) { - return getUrlsFromCssString(html, "source=", [' ', '>', '\t'], true); + return getUrlsFromCssString(html, 'source=', [' ', '>', '\t'], true); }; /** - * + * * @param {String} str * @returns {String} */ const removeQuotes = function (str) { - return str.replace(/["']/g, ""); + return str.replace(/["']/g, ''); }; const escapeRegExp = function (string) { @@ -169,37 +163,33 @@ var ForeignHtmlRenderer = function (styleSheets) { }; /** - * - * @param {String} contentHtml + * + * @param {String} contentHtml * @param {Number} width * @param {Number} height - * + * * @returns {Promise<String>} */ const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll, xoff) { - return new Promise(async function (resolve, reject) { - /* !! The problems !! - * 1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with) - * 2. Platform won't wait for external assets to load (fonts, images, etc.) - */ + * 1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with) + * 2. Platform won't wait for external assets to load (fonts, images, etc.) + */ // copy styles - let cssStyles = ""; + let cssStyles = ''; let urlsFoundInCss = []; for (let i = 0; i < styleSheets.length; i++) { try { - const rules = styleSheets[i].cssRules + const rules = styleSheets[i].cssRules; for (let j = 0; j < rules.length; j++) { const cssRuleStr = rules[j].cssText; urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr)); cssStyles += cssRuleStr; } - } catch (e) { - - } + } catch (e) {} } // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss); @@ -210,30 +200,32 @@ var ForeignHtmlRenderer = function (styleSheets) { // } // } - contentHtml = contentHtml.replace(/<source[^>]*>/g, "") // <picture> tags have a <source> which has a srcset field of image refs. instead of converting each, just use the default <img> of the picture - .replace(/noscript/g, "div").replace(/<div class="mediaset"><\/div>/g, "") // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s. But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag - .replace(/<link[^>]*>/g, "") // don't need to keep any linked style sheets because we've already processed all style sheets above - .replace(/srcset="([^ "]*)[^"]*"/g, "src=\"$1\""); // instead of converting each item in the srcset to a data url, just convert the first one and use that - let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith("data:")); + contentHtml = contentHtml + .replace(/<source[^>]*>/g, '') // <picture> tags have a <source> which has a srcset field of image refs. instead of converting each, just use the default <img> of the picture + .replace(/noscript/g, 'div') + .replace(/<div class="mediaset"><\/div>/g, '') // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s. But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag + .replace(/<link[^>]*>/g, '') // don't need to keep any linked style sheets because we've already processed all style sheets above + .replace(/srcset="([^ "]*)[^"]*"/g, 'src="$1"'); // instead of converting each item in the srcset to a data url, just convert the first one and use that + let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:')); const fetchedResources = webUrl ? await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml) : []; for (let i = 0; i < fetchedResources.length; i++) { const r = fetchedResources[i]; if (r.resourceUrl) { - contentHtml = contentHtml.replace(new RegExp(escapeRegExp(r.resourceUrl), "g"), r.resourceBase64); + contentHtml = contentHtml.replace(new RegExp(escapeRegExp(r.resourceUrl), 'g'), r.resourceBase64); } } - const styleElem = document.createElement("style"); - styleElem.innerHTML = cssStyles.replace(">", ">").replace("<", "<"); + const styleElem = document.createElement('style'); + styleElem.innerHTML = cssStyles.replace('>', '>').replace('<', '<'); - const styleElemString = new XMLSerializer().serializeToString(styleElem).replace(/>/g, ">").replace(/</g, "<"); + const styleElemString = new XMLSerializer().serializeToString(styleElem).replace(/>/g, '>').replace(/</g, '<'); // create DOM element string that encapsulates styles + content - const contentRootElem = document.createElement("body"); - contentRootElem.style.zIndex = "1111"; + const contentRootElem = document.createElement('body'); + contentRootElem.style.zIndex = '1111'; // contentRootElem.style.transform = "scale(0.08)" contentRootElem.innerHTML = styleElemString + contentHtml; - contentRootElem.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); + contentRootElem.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); //document.body.appendChild(contentRootElem); const contentRootElemString = new XMLSerializer().serializeToString(contentRootElem); @@ -256,17 +248,17 @@ var ForeignHtmlRenderer = function (styleSheets) { * @param {String} html * @param {Number} width * @param {Number} height - * + * * @return {Promise<Image>} */ this.renderToImage = async function (webUrl, html, width, height, scroll, xoff) { return new Promise(async function (resolve, reject) { const img = new Image(); - console.log("BUILDING SVG for:" + webUrl); + console.log('BUILDING SVG for:' + webUrl); img.src = await buildSvgDataUri(webUrl, html, width, height, scroll, xoff); img.onload = function () { - console.log("IMAGE SVG created:" + webUrl); + console.log('IMAGE SVG created:' + webUrl); resolve(img); }; }); @@ -276,7 +268,7 @@ var ForeignHtmlRenderer = function (styleSheets) { * @param {String} html * @param {Number} width * @param {Number} height - * + * * @return {Promise<Image>} */ this.renderToCanvas = async function (webUrl, html, width, height, scroll, xoff, oversample) { @@ -298,7 +290,7 @@ var ForeignHtmlRenderer = function (styleSheets) { * @param {String} html * @param {Number} width * @param {Number} height - * + * * @return {Promise<String>} */ this.renderToBase64Png = async function (webUrl, html, width, height, scroll, xoff, oversample) { @@ -307,24 +299,30 @@ var ForeignHtmlRenderer = function (styleSheets) { resolve(canvas.toDataURL('image/png')); }); }; - }; - export function CreateImage(webUrl, styleSheets, html, width, height, scroll, xoff = 0, oversample = 1) { - const val = (new ForeignHtmlRenderer(styleSheets)).renderToBase64Png(webUrl, html.replace(/docView-hack/g, 'documentView-hack').replace(/\n/g, "").replace(/<script((?!\/script).)*<\/script>/g, ""), width, height, scroll, xoff, oversample); - return val; + return new ForeignHtmlRenderer(styleSheets).renderToBase64Png( + webUrl, + html + .replace(/docView-hack/g, 'documentView-hack') + .replace(/\n/g, '') + .replace(/<script((?!\/script).)*<\/script>/g, ''), + width, + height, + scroll, + xoff, + oversample + ); } - - -var ClipboardUtils = new function () { +var ClipboardUtils = new (function () { var permissions = { 'image/bmp': true, 'image/gif': true, 'image/png': true, 'image/jpeg': true, - 'image/tiff': true + 'image/tiff': true, }; function getType(types) { @@ -387,9 +385,8 @@ var ClipboardUtils = new function () { callback(null, 'Clipboard is not supported.'); } }; -}; - +})(); export function pasteImageBitmap(callback) { return ClipboardUtils.readImage(callback); -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 73a711b9d..5f576be41 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -149,7 +149,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current); return new Transform(-translateX, -translateY, 1).scale(1 / scale); }; - outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target + outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document, {}); // ideally, this would scroll to show the focus target onKeyDown = (e: any) => { e.stopPropagation(); |