From 19ba79a2be4fe2046fe6b7d7af7587d35560a545 Mon Sep 17 00:00:00 2001 From: Thomas Parslow Date: Sun, 5 Apr 2015 15:50:22 +0100 Subject: [PATCH] First commit --- .gitignore | 5 + AIBHTMLWebView.xcodeproj/project.pbxproj | 353 +++++++++++++++++++++++ LICENSE.txt | 21 ++ README.md | 80 +++++ iOS/AIBHTMLWebView.h | 19 ++ iOS/AIBHTMLWebView.m | 86 ++++++ iOS/AIBHTMLWebViewManager.h | 17 ++ iOS/AIBHTMLWebViewManager.m | 39 +++ index.ios.js | 91 ++++++ package.json | 31 ++ 10 files changed, 742 insertions(+) create mode 100644 .gitignore create mode 100644 AIBHTMLWebView.xcodeproj/project.pbxproj create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 iOS/AIBHTMLWebView.h create mode 100644 iOS/AIBHTMLWebView.m create mode 100644 iOS/AIBHTMLWebViewManager.h create mode 100644 iOS/AIBHTMLWebViewManager.m create mode 100644 index.ios.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af27b7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules +AIBHTMLWebView.xcodeproj/xcuserdata +AIBHTMLWebView.xcodeproj/project.xcworkspace \ No newline at end of file diff --git a/AIBHTMLWebView.xcodeproj/project.pbxproj b/AIBHTMLWebView.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d41fb52 --- /dev/null +++ b/AIBHTMLWebView.xcodeproj/project.pbxproj @@ -0,0 +1,353 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + E236CCDF1AD173A500D5FC85 /* libAIBHTMLWebView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E236CCD31AD173A500D5FC85 /* libAIBHTMLWebView.a */; }; + E236CCF71AD176C300D5FC85 /* AIBHTMLWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = E236CCF41AD176C300D5FC85 /* AIBHTMLWebView.m */; }; + E236CCF81AD176C300D5FC85 /* AIBHTMLWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E236CCF61AD176C300D5FC85 /* AIBHTMLWebViewManager.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + E236CCE01AD173A500D5FC85 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E236CCCB1AD173A500D5FC85 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E236CCD21AD173A500D5FC85; + remoteInfo = AIBHTMLWebView; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + E236CCD11AD173A500D5FC85 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + E236CCD31AD173A500D5FC85 /* libAIBHTMLWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAIBHTMLWebView.a; sourceTree = BUILT_PRODUCTS_DIR; }; + E236CCDE1AD173A500D5FC85 /* AIBHTMLWebViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AIBHTMLWebViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + E236CCF31AD176C300D5FC85 /* AIBHTMLWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AIBHTMLWebView.h; path = iOS/AIBHTMLWebView.h; sourceTree = ""; }; + E236CCF41AD176C300D5FC85 /* AIBHTMLWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AIBHTMLWebView.m; path = iOS/AIBHTMLWebView.m; sourceTree = ""; }; + E236CCF51AD176C300D5FC85 /* AIBHTMLWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AIBHTMLWebViewManager.h; path = iOS/AIBHTMLWebViewManager.h; sourceTree = ""; }; + E236CCF61AD176C300D5FC85 /* AIBHTMLWebViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AIBHTMLWebViewManager.m; path = iOS/AIBHTMLWebViewManager.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E236CCD01AD173A500D5FC85 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E236CCDB1AD173A500D5FC85 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E236CCDF1AD173A500D5FC85 /* libAIBHTMLWebView.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + E236CCCA1AD173A500D5FC85 = { + isa = PBXGroup; + children = ( + E236CCF31AD176C300D5FC85 /* AIBHTMLWebView.h */, + E236CCF41AD176C300D5FC85 /* AIBHTMLWebView.m */, + E236CCF51AD176C300D5FC85 /* AIBHTMLWebViewManager.h */, + E236CCF61AD176C300D5FC85 /* AIBHTMLWebViewManager.m */, + E236CCD41AD173A500D5FC85 /* Products */, + ); + sourceTree = ""; + }; + E236CCD41AD173A500D5FC85 /* Products */ = { + isa = PBXGroup; + children = ( + E236CCD31AD173A500D5FC85 /* libAIBHTMLWebView.a */, + E236CCDE1AD173A500D5FC85 /* AIBHTMLWebViewTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E236CCD21AD173A500D5FC85 /* AIBHTMLWebView */ = { + isa = PBXNativeTarget; + buildConfigurationList = E236CCE71AD173A500D5FC85 /* Build configuration list for PBXNativeTarget "AIBHTMLWebView" */; + buildPhases = ( + E236CCCF1AD173A500D5FC85 /* Sources */, + E236CCD01AD173A500D5FC85 /* Frameworks */, + E236CCD11AD173A500D5FC85 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AIBHTMLWebView; + productName = AIBHTMLWebView; + productReference = E236CCD31AD173A500D5FC85 /* libAIBHTMLWebView.a */; + productType = "com.apple.product-type.library.static"; + }; + E236CCDD1AD173A500D5FC85 /* AIBHTMLWebViewTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = E236CCEA1AD173A500D5FC85 /* Build configuration list for PBXNativeTarget "AIBHTMLWebViewTests" */; + buildPhases = ( + E236CCDA1AD173A500D5FC85 /* Sources */, + E236CCDB1AD173A500D5FC85 /* Frameworks */, + E236CCDC1AD173A500D5FC85 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + E236CCE11AD173A500D5FC85 /* PBXTargetDependency */, + ); + name = AIBHTMLWebViewTests; + productName = AIBHTMLWebViewTests; + productReference = E236CCDE1AD173A500D5FC85 /* AIBHTMLWebViewTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E236CCCB1AD173A500D5FC85 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = "Active Inbox"; + TargetAttributes = { + E236CCD21AD173A500D5FC85 = { + CreatedOnToolsVersion = 6.1.1; + }; + E236CCDD1AD173A500D5FC85 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = E236CCCE1AD173A500D5FC85 /* Build configuration list for PBXProject "AIBHTMLWebView" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = E236CCCA1AD173A500D5FC85; + productRefGroup = E236CCD41AD173A500D5FC85 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E236CCD21AD173A500D5FC85 /* AIBHTMLWebView */, + E236CCDD1AD173A500D5FC85 /* AIBHTMLWebViewTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + E236CCDC1AD173A500D5FC85 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E236CCCF1AD173A500D5FC85 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E236CCF71AD176C300D5FC85 /* AIBHTMLWebView.m in Sources */, + E236CCF81AD176C300D5FC85 /* AIBHTMLWebViewManager.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E236CCDA1AD173A500D5FC85 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + E236CCE11AD173A500D5FC85 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E236CCD21AD173A500D5FC85 /* AIBHTMLWebView */; + targetProxy = E236CCE01AD173A500D5FC85 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + E236CCE51AD173A500D5FC85 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + E236CCE61AD173A500D5FC85 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + E236CCE81AD173A500D5FC85 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/node_modules/react-native/React/**"; + }; + name = Debug; + }; + E236CCE91AD173A500D5FC85 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/node_modules/react-native/React/**"; + }; + name = Release; + }; + E236CCEB1AD173A500D5FC85 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = AIBHTMLWebViewTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + E236CCEC1AD173A500D5FC85 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = AIBHTMLWebViewTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E236CCCE1AD173A500D5FC85 /* Build configuration list for PBXProject "AIBHTMLWebView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E236CCE51AD173A500D5FC85 /* Debug */, + E236CCE61AD173A500D5FC85 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E236CCE71AD173A500D5FC85 /* Build configuration list for PBXNativeTarget "AIBHTMLWebView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E236CCE81AD173A500D5FC85 /* Debug */, + E236CCE91AD173A500D5FC85 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E236CCEA1AD173A500D5FC85 /* Build configuration list for PBXNativeTarget "AIBHTMLWebViewTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E236CCEB1AD173A500D5FC85 /* Debug */, + E236CCEC1AD173A500D5FC85 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = E236CCCB1AD173A500D5FC85 /* Project object */; +} diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5364048 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Thomas Parslow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9a23eff --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# react-native-html-webview + +Display (possibly untrusted) HTML using a UIWebView in React Native. + +Written by Thomas Parslow +([almostobsolete.net](http://almostobsolete.net) and +[tomparslow.co.uk](http://tomparslow.co.uk)) as part of Active Inbox +([activeinboxhq.com](http://activeinboxhq.com/)). + +A couple of similar projects are +[HTMLText](https://github.com/siuying/react-native-htmltext) and +[HTMLView](https://github.com/jsdf/react-native-htmlview) both of +which render a subset of HTML as React Native views. This project +takes a slightly different approach of using a UIWebView giving a full +HTML renderer, but that means it has to rely on an HTML sanitizer to +clean up untrusted HTML. + +## Installation + +Install using npm with `npm install --save react-native-html-webview` + +You then need to add the Objective C part to your XCode project. Drag +`AIBHTMLWebView.xcodeproj` from the +`node_modules/react-native-html-webview` folder into your XCode +projec. Click on the your project in XCode, goto `Build Phases` then +`Link Binary With Libraries` and add `libAIBHTMLWebView.a`. + +NOTE: Make sure you don't have the `AIBHTMLWebView` project open seperately in XCode otherwise it won't work. + +## Usage + +```javascript +var HTMLWebView = require('react-native-html-webview'); + +var testView = React.createClass({ + render: function() { + return ( + + + + ); + }, + onLink: function (href) { + // Link was clicked! + } +}); +``` + +## Properties + +- **html** : The html content to display as a string +- **makeSafe** (default: true) : Run the HTML through an HTML + sanitizer ([html-safe](http://github.com/almost/safe-html)) before + inserting it to remove script tags and similar unsafe things. Pass + in `true` to use the default options for html-safe, pass in + `false` to turn it off, or pass in an object to set config options + for html-safe. +- **autoHeight** (default: false) : Automatically adjust the height of + the webview to fit the contents (also turns off scrolling). +- **onLink** : Pass in a function to be called when the user clicks a + link, the function will be given the href. + +## Feedback Welcome! + +Feedback, questions, suggestions and most of all Pull Requests are +very welcome. This is an early version and I want to figure out the +best way to continue it. + +I'm also available for freelance work! + +I'm [@almostobsolete](http://twitter.com/almostobsolete) on Twitter my +email is [tom@almostobsolete.net](mailto:tom@almostobsolete.net) and +you can find me on the web at +[tomparslow.co.uk](http://tomparslow.co.uk) and +[almostobsolete.net](http://almostobsolete.net) \ No newline at end of file diff --git a/iOS/AIBHTMLWebView.h b/iOS/AIBHTMLWebView.h new file mode 100644 index 0000000..c715254 --- /dev/null +++ b/iOS/AIBHTMLWebView.h @@ -0,0 +1,19 @@ +// +// AIBHTMLWebView.h +// AIBHTMLWebView +// +// Created by Thomas Parslow on 05/04/2015. +// Copyright (c) 2015 Thomas Parslow. MIT License. +// + +#import "RCTView.h" + +@class RCTEventDispatcher; + +@interface AIBHTMLWebView : RCTView + +@property (nonatomic, strong) NSString *HTML; + +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; + +@end \ No newline at end of file diff --git a/iOS/AIBHTMLWebView.m b/iOS/AIBHTMLWebView.m new file mode 100644 index 0000000..2e4e75f --- /dev/null +++ b/iOS/AIBHTMLWebView.m @@ -0,0 +1,86 @@ +// +// AIBHTMLWebView.m +// AIBHTMLWebView +// +// Created by Thomas Parslow on 05/04/2015. +// Copyright (c) 2015 Thomas Parslow. MIT License. +// + +#import "AIBHTMLWebView.h" + +#import +#import "RCTEventDispatcher.h" +#import "UIView+React.h" +#import "RCTView.h" + +@interface AIBHTMLWebView () + +@end + +@implementation AIBHTMLWebView +{ + RCTEventDispatcher *_eventDispatcher; + UIWebView *_webView; +} + +- (void)setHTML:(NSString *)HTML +{ + // TODO: Do we need to ensure that duplicate sets are ignored? + [_webView loadHTMLString:HTML baseURL: [NSURL URLWithString: @""]]; + [self reportHeight]; +} + +- (void)setEnableScroll:(BOOL) enable +{ + _webView.scrollView.scrollEnabled = enable; +} + +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher +{ + if ((self = [super initWithFrame:CGRectZero])) { + _eventDispatcher = eventDispatcher; + _webView = [[UIWebView alloc] initWithFrame:self.bounds]; + _webView.delegate = self; + [self addSubview:_webView]; + } + return self; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + _webView.frame = self.bounds; + [self reportHeight]; +} + +- (void)reportHeight +{ + NSNumber *height =[NSNumber numberWithFloat: _webView.scrollView.contentSize.height]; + NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary: @{ + @"target": self.reactTag, + @"contentHeight": height + }]; + [_eventDispatcher sendInputEventWithName:@"contentHeight" body:event]; +} + +#pragma mark - UIWebViewDelegate methods + +- (BOOL)webView:(UIWebView *)webView :(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { + if ([[request.URL scheme] isEqual:@"file"] && navigationType==UIWebViewNavigationTypeOther) { + // When we load from HTML string it still shows up as a request, so let's let that through + return YES; + } else { + NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary: @{ + @"target": self.reactTag, + @"url": [request.URL absoluteString] + }]; + [_eventDispatcher sendInputEventWithName:@"link" body:event]; + return NO; // Tells the webView not to load the URL + } +} + +- (void)webViewDidFinishLoad:(UIWebView *)webView { + [self reportHeight]; +} + +@end diff --git a/iOS/AIBHTMLWebViewManager.h b/iOS/AIBHTMLWebViewManager.h new file mode 100644 index 0000000..f6d5485 --- /dev/null +++ b/iOS/AIBHTMLWebViewManager.h @@ -0,0 +1,17 @@ +// +// AIBHTMLWebViewManager.h +// AIBHTMLWebView +// +// Created by Thomas Parslow on 05/04/2015. +// Copyright (c) 2015 Thomas Parslow. MIT Licensed +// + +#import "RCTViewManager.h" + +@class RCTEventDispatcher; + +@interface AIBHTMLWebViewManager : RCTViewManager + + + +@end diff --git a/iOS/AIBHTMLWebViewManager.m b/iOS/AIBHTMLWebViewManager.m new file mode 100644 index 0000000..847515e --- /dev/null +++ b/iOS/AIBHTMLWebViewManager.m @@ -0,0 +1,39 @@ +// +// AIBHTMLWebViewManager.m +// AIBHTMLWebView +// +// Created by Thomas Parslow on 05/04/2015. +// Copyright (c) 2015 Thomas Parslow. MIT Licensed +// + +#import "AIBHTMLWebViewManager.h" +#import "AIBHTMLWebView.h" +#import "RCTBridge.h" + +#import + +@implementation AIBHTMLWebViewManager + +RCT_REMAP_VIEW_PROPERTY(html, HTML, NSString) +RCT_EXPORT_VIEW_PROPERTY(enableScroll, BOOL) + +- (UIView *)view +{ + AIBHTMLWebView *_view; + _view = [[AIBHTMLWebView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; + return _view; +} + +- (NSDictionary *)customDirectEventTypes +{ + return @{ + @"link": @{ + @"registrationName": @"onLink" + }, + @"contentHeight": @{ + @"registrationName": @"onContentHeight" + } + }; +} + +@end diff --git a/index.ios.js b/index.ios.js new file mode 100644 index 0000000..f5e3104 --- /dev/null +++ b/index.ios.js @@ -0,0 +1,91 @@ +// WebView component that takes HTML strings and sanitises them to +// remove javascript and any other dangerous tags. Link clicks will +// generate events but won't automatically change what is displayed. + +var React = require('react-native'); +var { + View, + PropTypes +} = React; + +var safeHtml = require('safe-html'); +var _ = require('underscore'); + +var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass'); + +var _HTMLWebView = createReactIOSNativeComponentClass({ + validAttributes: {html: true, enableScroll: true}, + uiViewClassName: 'AIBHTMLWebView' +}) + + +var HTMLWebView = React.createClass({ + propTypes: { + html: PropTypes.string.isRequired, + makeSafe: PropTypes.object, + onLink: PropTypes.func, + style: View.propTypes.style, + // Should this view adjust its height automatically to show its + // complete content + autoHeight: PropTypes.bool + }, + + getInitialState: function() { + return { + contentHeight: 1 + }; + }, + + render: function () { + console.log(this.state.contentHeight); + return ( + <_HTMLWebView + style={[{height: this.state.contentHeight}, this.props.style]} + html={this.safeHtml(this.props.html)} + enableScroll={!this.props.autoHeight} + onLink={this.onLink} + onContentHeight={this.onContentHeight} + /> + ); + }, + + safeHtml: function (html: string) { + var config = this.props.makeSafe; + if (config === false) { + // saveHtml disabled + return html; + } else if (!_.isObject(config)) { + config = module.exports.HTML_SAFE_CONFIG; + } + return safeHtml(html, config); + }, + + onLink: function (e) { + if (_.isFunction(this.props.onLink)) { + this.props.onLink(e.nativeEvent.url); + } + }, + + onContentHeight: function (e) { + if (e.nativeEvent.contentHeight > 1 && this.props.autoHeight && e.nativeEvent.contentHeight !== this.state.contentHeight) { + this.setState({contentHeight: e.nativeEvent.contentHeight}); + } + } +}); + +module.exports = HTMLWebView; + +// Allow a few more things than the default config for safe-html since +// we know where it's going to be used. +module.exports.HTML_SAFE_CONFIG = _.defaults( + { + allowedTags: safeHtml.DEFAULT_CONFIG.allowedTags.concat(["img", "style"]), + allowedAttributes: _.defaults( + { + id: {allTags: true}, + style: {allTags: true}, + src: {allowedTags: ["img"]} + }, safeHtml.DEFAULT_CONFIG) + }, + safeHtml.DEFAULT_CONFIG +); diff --git a/package.json b/package.json new file mode 100644 index 0000000..9f81121 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "react-native-html-webview", + "version": "0.0.1", + "description": "Display (possibly untrusted) HTML using a UIWebView in React Native.", + "main": "index.ios.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/almost/react-native-html-webview.git" + }, + "keywords": [ + "react-component", + "reactnative", + "react-native", + "react", + "html", + "ios" + ], + "author": "Thomas Parslow", + "license": "MIT", + "bugs": { + "url": "https://github.com/almost/react-native-html-webview/issues" + }, + "homepage": "https://github.com/almost/react-native-html-webview", + "dependencies": { + "react-native": "^0.3.4", + "safe-html": "0.0.1" + } +}