using afIocusing afRefluxusing gfxusing fwtusing fandoc** (View) - A HTML viewer for HTTP and file resources. class HtmlViewer : View { @Inject private IframeBlocker iframeBlocker @Inject private AppStash stash @Inject private Reflux refluxprivate Browser browserprivate Label statusBar @NoDocprotectednew make(|This| in) : super(in){ content = EdgePane(){it.center = browser = Browser(){it.onHyperlink.add |e| {this->onHyperlink(e)}it.onTitleText.add |e| {this->onTitleText(e)}it.onStatusText.add |e| {this->onStatusText(e)}it.onLoad.add |e| {this->onLoad(e)}}it.bottom = EdgePane(){it.top = BorderPane {it.border = Border("1,0,0 $Desktop.sysNormShadow")}it.center = statusBar = Label()}}}** Returns 'true'.override Bool reuseView(Resource resource){true}** Scrolls the page to an ID. Handy for when setting the HTML via the text attribute.** ** scrollToId("#myId") Void scrollToId(Str id){ browser.execute("var ele = document.getElementById(${id.toCode}); if (ele) window.scrollTo(ele.offsetLeft, ele.offsetTop);")} @NoDocoverride Void onDeactivate(){try{// we want to keep the scrollTop when switching between views, // but clear it when closing the tab - so when re-opened, we're at the top again!if(stash["${resource?.uri}.htmlViewer.clear"] == true) stash.remove("${resource.uri}.htmlViewer.clear")else{ scrollTop := browser.evaluate("return document.documentElement.scrollTop;") stash["${resource?.uri}.htmlViewer.scrollTop"] = scrollTop}}catch(Err err) typeof.pod.log.warn("JS Err: ${err.msg}")} @NoDocoverride Bool confirmClose(Bool force){ stash.remove("${resource.uri}.htmlViewer.scrollTop") stash["${resource?.uri}.htmlViewer.clear"] = truereturntrue} @NoDocoverride Void load(Resource resource){super.load(resource) res := resolveResource(resource)if(res is Uri) browser.url = reselseif(res is Str) browser.html = reselsethrow Err("Resource should resolve to either a URI or a Str, not: $res")// set focus so browser responds to scroll events// see http://fantom.org/sidewalk/topic/2024#c13355 Desktop.callLater(50ms) |->| { browser.focus}} @NoDocoverride Void refresh(Resource? resource := null){if(resource == null || resource == this.resource) browser.refresh}** Hook for subclasses to convert the resource into either a URI or a Str.** Returns 'resource.uri' by default.virtual Obj resolveResource(Resource resource){ resource.uri}** Callback when then page loads.protectedvirtual Void onLoad(Event event){ scrollTop := stash["${resource?.uri}.htmlViewer.scrollTop"]if(scrollTop != null) browser.execute("window.scrollTo(0, ${scrollTop});")}private Void onStatusText(Event event){try{ url := event.data.toStr.toUriif(url.scheme == "about") event.data = normaliseBrowserUrl(this.resource.uri, url).toStr}catch{} statusBar.text = event.data}private Void onTitleText(Event event){// don't show useless titles!if(event.data != "about:blank"){// set resource name first so it gets picked up by the windowif(resource is HttpResource)((HttpResource) resource).name = event.data// this triggers a frame update name = event.data}}private Void onHyperlink(Event event){// don't hyperlink in place, instead we route the hyperlink through reflux to save // the URI in the history and give consistent navigation url := (Uri) event.data// if a shitty url, cancel the eventif(iframeBlocker.block(url)){ event.data = nullreturn}// doesn't work 'cos stoopid Fandoc emits `className#wot` when it should be `#wot`// TODO: rewrite Fandoc generator// url = normaliseBrowserUrl(url)// if (Url(resource.uri).minusFrag == Url(url).minusFrag)// return// the other work aroundif(url.scheme == "about" && url.name == "blank" && url.frag != null)return// normalise AFTER the above fudge url = normaliseBrowserUrl(this.resource.uri, url)// anything beyond this point will be routed through `Reflux.load()` // so cancel the link event in the browser event.data = null// route the URI through reflux so it gets stored in the history reflux.load(url.toStr)}virtualprotected Uri normaliseBrowserUrl(Uri resourceUri, Uri url){// anchors on the same page are defined as `about:blank#anchor`if(url.scheme == "about" && url.name == "blank" && url.frag != null) url = (resourceUri.parent ?: resourceUri).plusName(resourceUri.name + "#" + url.frag)// IE gives relative links the scheme 'about' so resolve it relative to the current resource if(url.scheme == "about") url = Url(resourceUri + url.pathOnly).plusQuery(url.queryStr).plusFrag(url.frag).toUrireturn url}privatestatic Str fandocToHtml(Str fandoc, Uri? base := null){ writer := FandocWriter(base) doc := FandocParser().parseStr(fandoc) doc.write(writer)return writer.toHtml}}