ZSSRichTextEditor.m 93 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389
  1. //
  2. // ZSSRichTextEditorViewController.m
  3. // ZSSRichTextEditor
  4. //
  5. // Created by Nicholas Hubbard on 11/30/13.
  6. // Copyright (c) 2013 Zed Said Studio. All rights reserved.
  7. //
  8. #import <objc/runtime.h>
  9. #import <UIKit/UIKit.h>
  10. #import <WebKit/WebKit.h>
  11. #import "ZSSRichTextEditor.h"
  12. #import "ZSSBarButtonItem.h"
  13. #import "HRColorUtil.h"
  14. #import "ZSSTextView.h"
  15. @import JavaScriptCore;
  16. /**
  17. WKWebView modifications for hiding the inputAccessoryView
  18. **/
  19. @interface WKWebView (HackishAccessoryHiding)
  20. @property (nonatomic, assign) BOOL hidesInputAccessoryView;
  21. @end
  22. @implementation WKWebView (HackishAccessoryHiding)
  23. static const char * const hackishFixClassName = "WKWebBrowserViewMinusAccessoryView";
  24. static Class hackishFixClass = Nil;
  25. - (UIView *)hackishlyFoundBrowserView {
  26. UIScrollView *scrollView = self.scrollView;
  27. UIView *browserView = nil;
  28. for (UIView *subview in scrollView.subviews) {
  29. if ([NSStringFromClass([subview class]) hasPrefix:@"WKWebBrowserView"]) {
  30. browserView = subview;
  31. break;
  32. }
  33. }
  34. return browserView;
  35. }
  36. - (id)methodReturningNil {
  37. return nil;
  38. }
  39. - (void)ensureHackishSubclassExistsOfBrowserViewClass:(Class)browserViewClass {
  40. if (!hackishFixClass) {
  41. Class newClass = objc_allocateClassPair(browserViewClass, hackishFixClassName, 0);
  42. newClass = objc_allocateClassPair(browserViewClass, hackishFixClassName, 0);
  43. IMP nilImp = [self methodForSelector:@selector(methodReturningNil)];
  44. class_addMethod(newClass, @selector(inputAccessoryView), nilImp, "@@:");
  45. objc_registerClassPair(newClass);
  46. hackishFixClass = newClass;
  47. }
  48. }
  49. - (BOOL) hidesInputAccessoryView {
  50. UIView *browserView = [self hackishlyFoundBrowserView];
  51. return [browserView class] == hackishFixClass;
  52. }
  53. - (void) setHidesInputAccessoryView:(BOOL)value {
  54. UIView *browserView = [self hackishlyFoundBrowserView];
  55. if (browserView == nil) {
  56. return;
  57. }
  58. [self ensureHackishSubclassExistsOfBrowserViewClass:[browserView class]];
  59. if (value) {
  60. object_setClass(browserView, hackishFixClass);
  61. }
  62. else {
  63. Class normalClass = objc_getClass("WKWebBrowserView");
  64. object_setClass(browserView, normalClass);
  65. }
  66. [browserView reloadInputViews];
  67. }
  68. @end
  69. @interface ZSSRichTextEditor ()
  70. /*
  71. * Scroll view containing the toolbar
  72. */
  73. @property (nonatomic, strong) UIScrollView *toolBarScroll;
  74. /*
  75. * Toolbar containing ZSSBarButtonItems
  76. */
  77. @property (nonatomic, strong) UIToolbar *toolbar;
  78. /*
  79. * Holder for all of the toolbar components
  80. */
  81. @property (nonatomic, strong) UIView *toolbarHolder;
  82. /*
  83. * String for the HTML
  84. */
  85. @property (nonatomic, strong) NSString *htmlString;
  86. /*
  87. * WKWebView for writing/editing/displaying the content
  88. */
  89. @property (nonatomic, strong) WKWebView *editorView;
  90. /*
  91. * ZSSTextView for displaying the source code for what is displayed in the editor view
  92. */
  93. @property (nonatomic, strong) ZSSTextView *sourceView;
  94. /*
  95. * CGRect for holding the frame for the editor view
  96. */
  97. @property (nonatomic) CGRect editorViewFrame;
  98. /*
  99. * BOOL for holding if the resources are loaded or not
  100. */
  101. @property (nonatomic) BOOL resourcesLoaded;
  102. /*
  103. * Array holding the enabled editor items
  104. */
  105. @property (nonatomic, strong) NSArray *editorItemsEnabled;
  106. /*
  107. * Alert View used when inserting links/images
  108. */
  109. @property (nonatomic, strong) UIAlertView *alertView;
  110. /*
  111. * NSString holding the selected links URL value
  112. */
  113. @property (nonatomic, strong) NSString *selectedLinkURL;
  114. /*
  115. * NSString holding the selected links title value
  116. */
  117. @property (nonatomic, strong) NSString *selectedLinkTitle;
  118. /*
  119. * NSString holding the selected image URL value
  120. */
  121. @property (nonatomic, strong) NSString *selectedImageURL;
  122. /*
  123. * NSString holding the selected image Alt value
  124. */
  125. @property (nonatomic, strong) NSString *selectedImageAlt;
  126. /*
  127. * CGFloat holdign the selected image scale value
  128. */
  129. @property (nonatomic, assign) CGFloat selectedImageScale;
  130. /*
  131. * NSString holding the base64 value of the current image
  132. */
  133. @property (nonatomic, strong) NSString *imageBase64String;
  134. /*
  135. * Bar button item for the keyboard dismiss button in the toolbar
  136. */
  137. @property (nonatomic, strong) UIBarButtonItem *keyboardItem;
  138. /*
  139. * Array for custom bar button items
  140. */
  141. @property (nonatomic, strong) NSMutableArray *customBarButtonItems;
  142. /*
  143. * Array for custom ZSSBarButtonItems
  144. */
  145. @property (nonatomic, strong) NSMutableArray *customZSSBarButtonItems;
  146. /*
  147. * NSString holding the html
  148. */
  149. @property (nonatomic, strong) NSString *internalHTML;
  150. /*
  151. * NSString holding the css
  152. */
  153. @property (nonatomic, strong) NSString *customCSS;
  154. /*
  155. * BOOL for if the editor is loaded or not
  156. */
  157. @property (nonatomic) BOOL editorLoaded;
  158. /*
  159. * BOOL for if the editor is paste or not
  160. */
  161. @property (nonatomic) BOOL editorPaste;
  162. /*
  163. * Image Picker for selecting photos from users photo library
  164. */
  165. @property (nonatomic, strong) UIImagePickerController *imagePicker;
  166. // local var to hold first responder state after callback
  167. @property (nonatomic) BOOL isFirstResponderUpdated;
  168. /*
  169. * Method for getting a version of the html without quotes
  170. */
  171. - (NSString *)removeQuotesFromHTML:(NSString *)html;
  172. /*
  173. * Method for getting a tidied version of the html
  174. */
  175. - (void)tidyHTML:(NSString *)html completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
  176. /*
  177. * Method for enablign toolbar items
  178. */
  179. - (void)enableToolbarItems:(BOOL)enable;
  180. /*
  181. * Setter for isIpad BOOL
  182. */
  183. - (BOOL)isIpad;
  184. @end
  185. /*
  186. ZSSRichTextEditor
  187. */
  188. @implementation ZSSRichTextEditor
  189. //Scale image from device
  190. static CGFloat kJPEGCompression = 0.8;
  191. static CGFloat kDefaultScale = 0.5;
  192. #pragma mark - View Did Load Section
  193. - (void)viewDidLoad {
  194. [super viewDidLoad];
  195. //Initialise variables
  196. self.editorLoaded = NO;
  197. self.receiveEditorDidChangeEvents = NO;
  198. self.alwaysShowToolbar = NO;
  199. self.shouldShowKeyboard = YES;
  200. self.formatHTML = YES;
  201. //Initalise enabled toolbar items array
  202. self.enabledToolbarItems = [[NSArray alloc] init];
  203. //Frame for the source view and editor view
  204. CGRect frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
  205. //Source View
  206. [self createSourceViewWithFrame:frame];
  207. //Editor View
  208. [self createEditorViewWithFrame:frame];
  209. //Image Picker used to allow the user insert images from the device (base64 encoded)
  210. [self setUpImagePicker];
  211. //Scrolling View
  212. [self createToolBarScroll];
  213. //Toolbar with icons
  214. [self createToolbar];
  215. //Parent holding view
  216. [self createParentHoldingView];
  217. //Hide Keyboard
  218. if (![self isIpad]) {
  219. NSBundle* bundle = [NSBundle bundleForClass:[ZSSRichTextEditor class]];
  220. // Toolbar holder used to crop and position toolbar
  221. UIView *toolbarCropper = [[UIView alloc] initWithFrame:CGRectMake(self.view.frame.size.width-44, 0, 44, 44)];
  222. toolbarCropper.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
  223. toolbarCropper.clipsToBounds = YES;
  224. UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
  225. [btn addTarget:self action:@selector(dismissKeyboard) forControlEvents:UIControlEventTouchUpInside];
  226. UIImage *image = [[UIImage imageNamed:@"ZSSkeyboard.png" inBundle:bundle compatibleWithTraitCollection:nil] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
  227. [btn setImage:image forState:UIControlStateNormal];
  228. [btn setTintColor:[self barButtonItemDefaultColor]];
  229. [toolbarCropper addSubview:btn];
  230. [self.toolbarHolder addSubview:toolbarCropper];
  231. UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0.6f, 44)];
  232. line.backgroundColor = [UIColor lightGrayColor];
  233. line.alpha = 0.7f;
  234. [toolbarCropper addSubview:line];
  235. }
  236. [self.view addSubview:self.toolbarHolder];
  237. //Build the toolbar
  238. [self buildToolbar];
  239. //Load Resources
  240. if (!self.resourcesLoaded) {
  241. [self loadResources];
  242. }
  243. }
  244. #pragma mark - View Will Appear Section
  245. - (void)viewWillAppear:(BOOL)animated {
  246. [super viewWillAppear:animated];
  247. //Add observers for keyboard showing or hiding notifications
  248. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShowOrHide:) name:UIKeyboardWillChangeFrameNotification object:nil];
  249. }
  250. #pragma mark - View Will Disappear Section
  251. - (void)viewWillDisappear:(BOOL)animated {
  252. [super viewWillDisappear:animated];
  253. //Remove observers for keyboard showing or hiding notifications
  254. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillChangeFrameNotification object:nil];
  255. }
  256. #pragma mark - Set Up View Section
  257. - (void)createSourceViewWithFrame:(CGRect)frame {
  258. self.sourceView = [[ZSSTextView alloc] initWithFrame:frame];
  259. self.sourceView.hidden = YES;
  260. self.sourceView.autocapitalizationType = UITextAutocapitalizationTypeNone;
  261. self.sourceView.autocorrectionType = UITextAutocorrectionTypeNo;
  262. self.sourceView.font = [UIFont fontWithName:@"Courier" size:13.0];
  263. self.sourceView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  264. self.sourceView.autoresizesSubviews = YES;
  265. self.sourceView.delegate = self;
  266. [self.view addSubview:self.sourceView];
  267. }
  268. - (void)createEditorViewWithFrame:(CGRect)frame {
  269. //allocate config and contentController and add scriptMessageHandler
  270. WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
  271. WKUserContentController *contentController = [[WKUserContentController alloc] init];
  272. [contentController addScriptMessageHandler:self name:@"jsm"];
  273. config.userContentController = contentController;
  274. //load scripts
  275. NSString *scriptString = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
  276. WKUserScript *script = [[WKUserScript alloc] initWithSource:scriptString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
  277. [contentController addUserScript:script];
  278. //set data detection to none so it doesnt conflict
  279. config.dataDetectorTypes = WKDataDetectorTypeNone;
  280. self.editorView = [[WKWebView alloc] initWithFrame:frame
  281. configuration: config];
  282. self.editorView.UIDelegate = self;
  283. self.editorView.navigationDelegate = self;
  284. self.editorView.hidesInputAccessoryView = YES;
  285. //TODO: Is this behavior correct? Is it the right replacement?
  286. // self.editorView.keyboardDisplayRequiresUserAction = NO;
  287. [ZSSRichTextEditor allowDisplayingKeyboardWithoutUserAction];
  288. self.editorView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
  289. self.editorView.scrollView.bounces = YES;
  290. self.editorView.backgroundColor = [UIColor whiteColor];
  291. [self.view addSubview:self.editorView];
  292. }
  293. - (void)setUpImagePicker {
  294. self.imagePicker = [[UIImagePickerController alloc] init];
  295. self.imagePicker.delegate = self;
  296. self.imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
  297. self.imagePicker.allowsEditing = YES;
  298. self.selectedImageScale = kDefaultScale; //by default scale to half the size
  299. }
  300. - (void)createToolBarScroll {
  301. self.toolBarScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, [self isIpad] ? self.view.frame.size.width : self.view.frame.size.width - 44, 44)];
  302. self.toolBarScroll.backgroundColor = [UIColor clearColor];
  303. self.toolBarScroll.showsHorizontalScrollIndicator = NO;
  304. }
  305. - (void)createToolbar {
  306. self.toolbar = [[UIToolbar alloc] initWithFrame:CGRectZero];
  307. self.toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  308. self.toolbar.backgroundColor = [UIColor clearColor];
  309. [self.toolBarScroll addSubview:self.toolbar];
  310. self.toolBarScroll.autoresizingMask = self.toolbar.autoresizingMask;
  311. }
  312. - (void)createParentHoldingView {
  313. //Background Toolbar
  314. UIToolbar *backgroundToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
  315. backgroundToolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  316. //Parent holding view
  317. self.toolbarHolder = [[UIView alloc] init];
  318. if (_alwaysShowToolbar) {
  319. self.toolbarHolder.frame = CGRectMake(0, self.view.frame.size.height - 44, self.view.frame.size.width, 44);
  320. } else {
  321. self.toolbarHolder.frame = CGRectMake(0, self.view.frame.size.height, self.view.frame.size.width, 44);
  322. }
  323. self.toolbarHolder.autoresizingMask = self.toolbar.autoresizingMask;
  324. [self.toolbarHolder addSubview:self.toolBarScroll];
  325. [self.toolbarHolder insertSubview:backgroundToolbar atIndex:0];
  326. }
  327. #pragma mark - Convenience replacement for keyboardDisplayRequiresUserAction in WKWebview
  328. + (void)allowDisplayingKeyboardWithoutUserAction {
  329. Class class = NSClassFromString(@"WKContentView");
  330. NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0};
  331. NSOperatingSystemVersion iOS_12_2_0 = (NSOperatingSystemVersion){12, 2, 0};
  332. NSOperatingSystemVersion iOS_13_0_0 = (NSOperatingSystemVersion){13, 0, 0};
  333. if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_13_0_0]) {
  334. SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:");
  335. Method method = class_getInstanceMethod(class, selector);
  336. IMP original = method_getImplementation(method);
  337. IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
  338. ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
  339. });
  340. method_setImplementation(method, override);
  341. }
  342. else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_12_2_0]) {
  343. SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
  344. Method method = class_getInstanceMethod(class, selector);
  345. IMP original = method_getImplementation(method);
  346. IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
  347. ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
  348. });
  349. method_setImplementation(method, override);
  350. }
  351. else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) {
  352. SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
  353. Method method = class_getInstanceMethod(class, selector);
  354. IMP original = method_getImplementation(method);
  355. IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
  356. ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
  357. });
  358. method_setImplementation(method, override);
  359. } else {
  360. SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
  361. Method method = class_getInstanceMethod(class, selector);
  362. IMP original = method_getImplementation(method);
  363. IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
  364. ((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3);
  365. });
  366. method_setImplementation(method, override);
  367. }
  368. }
  369. #pragma mark - Resources Section
  370. - (void)loadResources {
  371. //Define correct bundle for loading resources
  372. NSBundle* bundle = [NSBundle bundleForClass:[ZSSRichTextEditor class]];
  373. //Create a string with the contents of editor.html
  374. NSString *filePath = [bundle pathForResource:@"editor" ofType:@"html"];
  375. NSData *htmlData = [NSData dataWithContentsOfFile:filePath];
  376. NSString *htmlString = [[NSString alloc] initWithData:htmlData encoding:NSUTF8StringEncoding];
  377. //Add jQuery.js to the html file
  378. NSString *jquery = [bundle pathForResource:@"jQuery" ofType:@"js"];
  379. NSString *jqueryString = [[NSString alloc] initWithData:[NSData dataWithContentsOfFile:jquery] encoding:NSUTF8StringEncoding];
  380. htmlString = [htmlString stringByReplacingOccurrencesOfString:@"<!-- jQuery -->" withString:jqueryString];
  381. //Add JSBeautifier.js to the html file
  382. NSString *beautifier = [bundle pathForResource:@"JSBeautifier" ofType:@"js"];
  383. NSString *beautifierString = [[NSString alloc] initWithData:[NSData dataWithContentsOfFile:beautifier] encoding:NSUTF8StringEncoding];
  384. htmlString = [htmlString stringByReplacingOccurrencesOfString:@"<!-- jsbeautifier -->" withString:beautifierString];
  385. //Add ZSSRichTextEditor.js to the html file
  386. NSString *source = [bundle pathForResource:@"ZSSRichTextEditor" ofType:@"js"];
  387. NSString *jsString = [[NSString alloc] initWithData:[NSData dataWithContentsOfFile:source] encoding:NSUTF8StringEncoding];
  388. htmlString = [htmlString stringByReplacingOccurrencesOfString:@"<!--editor-->" withString:jsString];
  389. [self.editorView loadHTMLString:htmlString baseURL:self.baseURL];
  390. self.resourcesLoaded = YES;
  391. }
  392. #pragma mark - Toolbar Section
  393. - (void)setEnabledToolbarItems:(NSArray *)enabledToolbarItems {
  394. _enabledToolbarItems = enabledToolbarItems;
  395. [self buildToolbar];
  396. }
  397. - (void)setToolbarItemTintColor:(UIColor *)toolbarItemTintColor {
  398. _toolbarItemTintColor = toolbarItemTintColor;
  399. // Update the color
  400. for (ZSSBarButtonItem *item in self.toolbar.items) {
  401. item.tintColor = [self barButtonItemDefaultColor];
  402. }
  403. self.keyboardItem.tintColor = toolbarItemTintColor;
  404. }
  405. - (void)setToolbarItemSelectedTintColor:(UIColor *)toolbarItemSelectedTintColor {
  406. _toolbarItemSelectedTintColor = toolbarItemSelectedTintColor;
  407. }
  408. - (NSArray *)itemsForToolbar {
  409. //Define correct bundle for loading resources
  410. NSBundle* bundle = [NSBundle bundleForClass:[ZSSRichTextEditor class]];
  411. NSMutableArray *items = [[NSMutableArray alloc] init];
  412. // None
  413. if(_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarNone])
  414. {
  415. return items;
  416. }
  417. BOOL customOrder = NO;
  418. if (_enabledToolbarItems && ![_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll]){
  419. customOrder = YES;
  420. for(int i=0; i < _enabledToolbarItems.count;i++){
  421. [items addObject:@""];
  422. }
  423. }
  424. // Bold
  425. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarBold]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  426. ZSSBarButtonItem *bold = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSbold.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(setBold)];
  427. bold.label = @"bold";
  428. if (customOrder) {
  429. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarBold] withObject:bold];
  430. } else {
  431. [items addObject:bold];
  432. }
  433. }
  434. // Italic
  435. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarItalic]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  436. ZSSBarButtonItem *italic = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSitalic.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(setItalic)];
  437. italic.label = @"italic";
  438. if (customOrder) {
  439. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarItalic] withObject:italic];
  440. } else {
  441. [items addObject:italic];
  442. }
  443. }
  444. // Subscript
  445. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarSubscript]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  446. ZSSBarButtonItem *subscript = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSsubscript.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(setSubscript)];
  447. subscript.label = @"subscript";
  448. if (customOrder) {
  449. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarSubscript] withObject:subscript];
  450. } else {
  451. [items addObject:subscript];
  452. }
  453. }
  454. // Superscript
  455. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarSuperscript]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  456. ZSSBarButtonItem *superscript = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSsuperscript.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(setSuperscript)];
  457. superscript.label = @"superscript";
  458. if (customOrder) {
  459. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarSuperscript] withObject:superscript];
  460. } else {
  461. [items addObject:superscript];
  462. }
  463. }
  464. // Strike Through
  465. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarStrikeThrough]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  466. ZSSBarButtonItem *strikeThrough = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSstrikethrough.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(setStrikethrough)];
  467. strikeThrough.label = @"strikeThrough";
  468. if (customOrder) {
  469. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarStrikeThrough] withObject:strikeThrough];
  470. } else {
  471. [items addObject:strikeThrough];
  472. }
  473. }
  474. // Underline
  475. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarUnderline]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  476. ZSSBarButtonItem *underline = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSunderline.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(setUnderline)];
  477. underline.label = @"underline";
  478. if (customOrder) {
  479. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarUnderline] withObject:underline];
  480. } else {
  481. [items addObject:underline];
  482. }
  483. }
  484. // Remove Format
  485. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarRemoveFormat]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  486. ZSSBarButtonItem *removeFormat = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSclearstyle.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(removeFormat)];
  487. removeFormat.label = @"removeFormat";
  488. if (customOrder) {
  489. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarRemoveFormat] withObject:removeFormat];
  490. } else {
  491. [items addObject:removeFormat];
  492. }
  493. }
  494. // Fonts
  495. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarFonts]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  496. ZSSBarButtonItem *fonts = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSfonts.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(showFontsPicker)];
  497. fonts.label = @"fonts";
  498. if (customOrder) {
  499. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarFonts] withObject:fonts];
  500. } else {
  501. [items addObject:fonts];
  502. }
  503. }
  504. // Undo
  505. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarUndo]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  506. ZSSBarButtonItem *undoButton = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSundo.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(undo:)];
  507. undoButton.label = @"undo";
  508. if (customOrder) {
  509. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarUndo] withObject:undoButton];
  510. } else {
  511. [items addObject:undoButton];
  512. }
  513. }
  514. // Redo
  515. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarRedo]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  516. ZSSBarButtonItem *redoButton = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSredo.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(redo:)];
  517. redoButton.label = @"redo";
  518. if (customOrder) {
  519. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarRedo] withObject:redoButton];
  520. } else {
  521. [items addObject:redoButton];
  522. }
  523. }
  524. // Align Left
  525. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarJustifyLeft]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  526. ZSSBarButtonItem *alignLeft = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSleftjustify.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(alignLeft)];
  527. alignLeft.label = @"justifyLeft";
  528. if (customOrder) {
  529. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarJustifyLeft] withObject:alignLeft];
  530. } else {
  531. [items addObject:alignLeft];
  532. }
  533. }
  534. // Align Center
  535. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarJustifyCenter]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  536. ZSSBarButtonItem *alignCenter = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSScenterjustify.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(alignCenter)];
  537. alignCenter.label = @"justifyCenter";
  538. if (customOrder) {
  539. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarJustifyCenter] withObject:alignCenter];
  540. } else {
  541. [items addObject:alignCenter];
  542. }
  543. }
  544. // Align Right
  545. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarJustifyRight]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  546. ZSSBarButtonItem *alignRight = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSrightjustify.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(alignRight)];
  547. alignRight.label = @"justifyRight";
  548. if (customOrder) {
  549. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarJustifyRight] withObject:alignRight];
  550. } else {
  551. [items addObject:alignRight];
  552. }
  553. }
  554. // Align Justify
  555. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarJustifyFull]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  556. ZSSBarButtonItem *alignFull = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSforcejustify.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(alignFull)];
  557. alignFull.label = @"justifyFull";
  558. if (customOrder) {
  559. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarJustifyFull] withObject:alignFull];
  560. } else {
  561. [items addObject:alignFull];
  562. }
  563. }
  564. // Paragraph
  565. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarParagraph]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  566. ZSSBarButtonItem *paragraph = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSparagraph.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(paragraph)];
  567. paragraph.label = @"p";
  568. if (customOrder) {
  569. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarParagraph] withObject:paragraph];
  570. } else {
  571. [items addObject:paragraph];
  572. }
  573. }
  574. // Header 1
  575. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarH1]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  576. ZSSBarButtonItem *h1 = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSh1.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(heading1)];
  577. h1.label = @"h1";
  578. if (customOrder) {
  579. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarH1] withObject:h1];
  580. } else {
  581. [items addObject:h1];
  582. }
  583. }
  584. // Header 2
  585. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarH2]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  586. ZSSBarButtonItem *h2 = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSh2.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(heading2)];
  587. h2.label = @"h2";
  588. if (customOrder) {
  589. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarH2] withObject:h2];
  590. } else {
  591. [items addObject:h2];
  592. }
  593. }
  594. // Header 3
  595. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarH3]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  596. ZSSBarButtonItem *h3 = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSh3.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(heading3)];
  597. h3.label = @"h3";
  598. if (customOrder) {
  599. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarH3] withObject:h3];
  600. } else {
  601. [items addObject:h3];
  602. }
  603. }
  604. // Heading 4
  605. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarH4]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  606. ZSSBarButtonItem *h4 = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSh4.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(heading4)];
  607. h4.label = @"h4";
  608. if (customOrder) {
  609. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarH4] withObject:h4];
  610. } else {
  611. [items addObject:h4];
  612. }
  613. }
  614. // Header 5
  615. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarH5]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  616. ZSSBarButtonItem *h5 = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSh5.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(heading5)];
  617. h5.label = @"h5";
  618. if (customOrder) {
  619. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarH5] withObject:h5];
  620. } else {
  621. [items addObject:h5];
  622. }
  623. }
  624. // Heading 6
  625. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarH6]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  626. ZSSBarButtonItem *h6 = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSh6.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(heading6)];
  627. h6.label = @"h6";
  628. if (customOrder) {
  629. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarH6] withObject:h6];
  630. } else {
  631. [items addObject:h6];
  632. }
  633. }
  634. // Text Color
  635. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarTextColor]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  636. ZSSBarButtonItem *textColor = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSStextcolor.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(textColor)];
  637. textColor.label = @"textColor";
  638. if (customOrder) {
  639. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarTextColor] withObject:textColor];
  640. } else {
  641. [items addObject:textColor];
  642. }
  643. }
  644. // Background Color
  645. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarBackgroundColor]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  646. ZSSBarButtonItem *bgColor = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSbgcolor.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(bgColor)];
  647. bgColor.label = @"backgroundColor";
  648. if (customOrder) {
  649. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarBackgroundColor] withObject:bgColor];
  650. } else {
  651. [items addObject:bgColor];
  652. }
  653. }
  654. // Unordered List
  655. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarUnorderedList]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  656. ZSSBarButtonItem *ul = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSunorderedlist.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(setUnorderedList)];
  657. ul.label = @"unorderedList";
  658. if (customOrder) {
  659. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarUnorderedList] withObject:ul];
  660. } else {
  661. [items addObject:ul];
  662. }
  663. }
  664. // Ordered List
  665. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarOrderedList]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  666. ZSSBarButtonItem *ol = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSorderedlist.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(setOrderedList)];
  667. ol.label = @"orderedList";
  668. if (customOrder) {
  669. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarOrderedList] withObject:ol];
  670. } else {
  671. [items addObject:ol];
  672. }
  673. }
  674. // Horizontal Rule
  675. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarHorizontalRule]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  676. ZSSBarButtonItem *hr = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSShorizontalrule.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(setHR)];
  677. hr.label = @"horizontalRule";
  678. if (customOrder) {
  679. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarHorizontalRule] withObject:hr];
  680. } else {
  681. [items addObject:hr];
  682. }
  683. }
  684. // Indent
  685. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarIndent]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  686. ZSSBarButtonItem *indent = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSindent.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(setIndent)];
  687. indent.label = @"indent";
  688. if (customOrder) {
  689. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarIndent] withObject:indent];
  690. } else {
  691. [items addObject:indent];
  692. }
  693. }
  694. // Outdent
  695. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarOutdent]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  696. ZSSBarButtonItem *outdent = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSoutdent.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(setOutdent)];
  697. outdent.label = @"outdent";
  698. if (customOrder) {
  699. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarOutdent] withObject:outdent];
  700. } else {
  701. [items addObject:outdent];
  702. }
  703. }
  704. // Image
  705. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarInsertImage]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  706. ZSSBarButtonItem *insertImage = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSimage.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(insertImage)];
  707. insertImage.label = @"image";
  708. if (customOrder) {
  709. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarInsertImage] withObject:insertImage];
  710. } else {
  711. [items addObject:insertImage];
  712. }
  713. }
  714. // Image From Device
  715. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarInsertImageFromDevice]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  716. ZSSBarButtonItem *insertImageFromDevice = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSimageDevice.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(insertImageFromDevice)];
  717. insertImageFromDevice.label = @"imageFromDevice";
  718. if (customOrder) {
  719. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarInsertImageFromDevice] withObject:insertImageFromDevice];
  720. } else {
  721. [items addObject:insertImageFromDevice];
  722. }
  723. }
  724. // Insert Link
  725. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarInsertLink]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  726. ZSSBarButtonItem *insertLink = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSlink.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(insertLink)];
  727. insertLink.label = @"link";
  728. if (customOrder) {
  729. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarInsertLink] withObject:insertLink];
  730. } else {
  731. [items addObject:insertLink];
  732. }
  733. }
  734. // Remove Link
  735. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarRemoveLink]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  736. ZSSBarButtonItem *removeLink = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSunlink.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(removeLink)];
  737. removeLink.label = @"removeLink";
  738. if (customOrder) {
  739. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarRemoveLink] withObject:removeLink];
  740. } else {
  741. [items addObject:removeLink];
  742. }
  743. }
  744. // Quick Link
  745. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarQuickLink]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  746. ZSSBarButtonItem *quickLink = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSquicklink.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(quickLink)];
  747. quickLink.label = @"quickLink";
  748. if (customOrder) {
  749. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarQuickLink] withObject:quickLink];
  750. } else {
  751. [items addObject:quickLink];
  752. }
  753. }
  754. // Show Source
  755. if ((_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarViewSource]) || (_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarAll])) {
  756. ZSSBarButtonItem *showSource = [[ZSSBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"ZSSviewsource.png" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(showHTMLSource:)];
  757. showSource.label = @"source";
  758. if (customOrder) {
  759. [items replaceObjectAtIndex:[_enabledToolbarItems indexOfObject:ZSSRichTextEditorToolbarViewSource] withObject:showSource];
  760. } else {
  761. [items addObject:showSource];
  762. }
  763. }
  764. return [NSArray arrayWithArray:items];
  765. }
  766. - (void)buildToolbar {
  767. // Check to see if we have any toolbar items, if not, add them all
  768. NSArray *items = [self itemsForToolbar];
  769. if (items.count == 0 && !(_enabledToolbarItems && [_enabledToolbarItems containsObject:ZSSRichTextEditorToolbarNone])) {
  770. _enabledToolbarItems = @[ZSSRichTextEditorToolbarAll];
  771. items = [self itemsForToolbar];
  772. }
  773. if (self.customZSSBarButtonItems != nil) {
  774. items = [items arrayByAddingObjectsFromArray:self.customZSSBarButtonItems];
  775. }
  776. // get the width before we add custom buttons
  777. CGFloat toolbarWidth = items.count == 0 ? 0.0f : (CGFloat)(items.count * 44);
  778. if(self.customBarButtonItems != nil)
  779. {
  780. items = [items arrayByAddingObjectsFromArray:self.customBarButtonItems];
  781. for(ZSSBarButtonItem *buttonItem in self.customBarButtonItems)
  782. {
  783. toolbarWidth += buttonItem.customView.frame.size.width + 11.0f;
  784. }
  785. }
  786. self.toolbar.items = items;
  787. for (ZSSBarButtonItem *item in items) {
  788. item.tintColor = [self barButtonItemDefaultColor];
  789. }
  790. self.toolbar.frame = CGRectMake(0, 0, toolbarWidth, 44);
  791. self.toolBarScroll.contentSize = CGSizeMake(self.toolbar.frame.size.width, 44);
  792. }
  793. #pragma mark - Editor Modification Section
  794. - (void)setCSS:(NSString *)css {
  795. self.customCSS = css;
  796. if (self.editorLoaded) {
  797. [self updateCSS];
  798. }
  799. }
  800. - (void)updateCSS {
  801. if (self.customCSS != NULL && [self.customCSS length] != 0) {
  802. NSString *js = [NSString stringWithFormat:@"zss_editor.setCustomCSS(\"%@\");", self.customCSS];
  803. [self.editorView evaluateJavaScript:js completionHandler:^(NSString *result, NSError *error) {
  804. }];
  805. }
  806. }
  807. - (void)setPlaceholderText {
  808. //Call the setPlaceholder javascript method if a placeholder has been set
  809. if (self.placeholder != NULL && [self.placeholder length] != 0) {
  810. NSString *js = [NSString stringWithFormat:@"zss_editor.setPlaceholder(\"%@\");", self.placeholder];
  811. [self.editorView evaluateJavaScript:js completionHandler:^(NSString *result, NSError *error) {
  812. }];
  813. }
  814. }
  815. - (void)setFooterHeight:(float)footerHeight {
  816. //Call the setFooterHeight javascript method
  817. NSString *js = [NSString stringWithFormat:@"zss_editor.setFooterHeight(\"%f\");", footerHeight];
  818. [self.editorView evaluateJavaScript:js completionHandler:^(NSString *result, NSError *error) {
  819. }];
  820. }
  821. - (void)setContentHeight:(float)contentHeight {
  822. //Call the contentHeight javascript method
  823. NSString *js = [NSString stringWithFormat:@"zss_editor.contentHeight = %f;", contentHeight];
  824. [self.editorView evaluateJavaScript:js completionHandler:^(NSString *result, NSError *error) {
  825. }];
  826. }
  827. #pragma mark - Editor Interaction
  828. - (void)focusTextEditor {
  829. //TODO: Is this behavior correct? Is it the right replacement?
  830. // self.editorView.keyboardDisplayRequiresUserAction = NO;
  831. [ZSSRichTextEditor allowDisplayingKeyboardWithoutUserAction];
  832. NSString *js = [NSString stringWithFormat:@"zss_editor.focusEditor();"];
  833. [self.editorView evaluateJavaScript:js completionHandler:^(NSString *result, NSError *error) {
  834. }];
  835. }
  836. - (void)blurTextEditor {
  837. NSString *js = [NSString stringWithFormat:@"zss_editor.blurEditor();"];
  838. [self.editorView evaluateJavaScript:js completionHandler:^(NSString *result, NSError *error) {
  839. }];
  840. }
  841. - (void)setHTML:(NSString *)html {
  842. self.internalHTML = html;
  843. if (self.editorLoaded) {
  844. [self updateHTML];
  845. }
  846. }
  847. - (void)updateHTML {
  848. NSString *html = self.internalHTML;
  849. self.sourceView.text = html;
  850. NSString *cleanedHTML = [self removeQuotesFromHTML:self.sourceView.text];
  851. NSString *trigger = [NSString stringWithFormat:@"zss_editor.setHTML(\"%@\");", cleanedHTML];
  852. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  853. }];
  854. }
  855. - (void)getHTML:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler {
  856. [self.editorView evaluateJavaScript:ZSSEditorHTML completionHandler:^(NSString *result, NSError *error) {
  857. if (error != NULL) {
  858. NSLog(@"HTML Parsing Error: %@", error);
  859. }
  860. NSLog(@"%@", result);
  861. NSString *html = [self removeQuotesFromHTML:result];
  862. NSLog(@"%@", html);
  863. [self tidyHTML:html completionHandler:^(NSString *result, NSError *error) {
  864. completionHandler(result, error);
  865. }];
  866. }];
  867. }
  868. - (void)insertHTML:(NSString *)html {
  869. NSString *cleanedHTML = [self removeQuotesFromHTML:html];
  870. NSString *trigger = [NSString stringWithFormat:@"zss_editor.insertHTML(\"%@\");", cleanedHTML];
  871. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  872. }];
  873. }
  874. - (void)getText:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler {
  875. [self.editorView evaluateJavaScript:ZSSEditorText completionHandler:^(NSString *result, NSError *error) {
  876. if (error != NULL) {
  877. NSLog(@"Text Parsing Error: %@", error);
  878. }
  879. completionHandler(result, error);
  880. }];
  881. }
  882. - (void)updateEditor {
  883. [self getHTML:^(NSString *htmlResult, NSError * _Nullable error) {
  884. [self getText:^(NSString *textResult, NSError * _Nullable error) {
  885. [self editorDidChangeWithText:textResult andHTML:htmlResult];
  886. }];
  887. }];
  888. }
  889. - (void)dismissKeyboard {
  890. [self.view endEditing:YES];
  891. }
  892. - (BOOL)isFirstResponder {
  893. [self.editorView evaluateJavaScript:ZSSEditorContent completionHandler:^(NSNumber *result, NSError *error) {
  894. //save the result as a bool and then update the UI
  895. self.isFirstResponderUpdated = [result boolValue];
  896. if (self.isFirstResponderUpdated == true) {
  897. [self becomeFirstResponder];
  898. } else {
  899. [self resignFirstResponder];
  900. }
  901. }];
  902. //this state is old and will quickly be updated after the callback above completes
  903. //TODO: refactor to find a more elegant approach
  904. return self.isFirstResponderUpdated;
  905. }
  906. - (void)showHTMLSource:(ZSSBarButtonItem *)barButtonItem {
  907. if (self.sourceView.hidden) {
  908. [self getHTML:^(NSString *result, NSError * _Nullable error) {
  909. self.sourceView.text = result;
  910. }];
  911. self.sourceView.hidden = NO;
  912. barButtonItem.tintColor = [UIColor blackColor];
  913. self.editorView.hidden = YES;
  914. [self enableToolbarItems:NO];
  915. } else {
  916. [self setHTML:self.sourceView.text];
  917. barButtonItem.tintColor = [self barButtonItemDefaultColor];
  918. self.sourceView.hidden = YES;
  919. self.editorView.hidden = NO;
  920. [self enableToolbarItems:YES];
  921. }
  922. }
  923. - (void)removeFormat {
  924. NSString *trigger = @"zss_editor.removeFormating();";
  925. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  926. }];
  927. }
  928. - (void)alignLeft {
  929. NSString *trigger = @"zss_editor.setJustifyLeft();";
  930. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  931. }];
  932. }
  933. - (void)alignCenter {
  934. NSString *trigger = @"zss_editor.setJustifyCenter();";
  935. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  936. }];
  937. }
  938. - (void)alignRight {
  939. NSString *trigger = @"zss_editor.setJustifyRight();";
  940. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  941. }];
  942. }
  943. - (void)alignFull {
  944. NSString *trigger = @"zss_editor.setJustifyFull();";
  945. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  946. }];
  947. }
  948. - (void)setBold {
  949. NSString *trigger = @"zss_editor.setBold();";
  950. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  951. }];
  952. }
  953. - (void)setItalic {
  954. NSString *trigger = @"zss_editor.setItalic();";
  955. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  956. }];
  957. }
  958. - (void)setSubscript {
  959. NSString *trigger = @"zss_editor.setSubscript();";
  960. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  961. }];
  962. }
  963. - (void)setUnderline {
  964. NSString *trigger = @"zss_editor.setUnderline();";
  965. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  966. }];
  967. }
  968. - (void)setSuperscript {
  969. NSString *trigger = @"zss_editor.setSuperscript();";
  970. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  971. }];
  972. }
  973. - (void)setStrikethrough {
  974. NSString *trigger = @"zss_editor.setStrikeThrough();";
  975. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  976. }];
  977. }
  978. - (void)setUnorderedList {
  979. NSString *trigger = @"zss_editor.setUnorderedList();";
  980. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  981. }];
  982. }
  983. - (void)setOrderedList {
  984. NSString *trigger = @"zss_editor.setOrderedList();";
  985. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  986. }];
  987. }
  988. - (void)setHR {
  989. NSString *trigger = @"zss_editor.setHorizontalRule();";
  990. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  991. }];
  992. }
  993. - (void)setIndent {
  994. NSString *trigger = @"zss_editor.setIndent();";
  995. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  996. }];
  997. }
  998. - (void)setOutdent {
  999. NSString *trigger = @"zss_editor.setOutdent();";
  1000. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1001. }];
  1002. }
  1003. - (void)heading1 {
  1004. NSString *trigger = @"zss_editor.setHeading('h1');";
  1005. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1006. }];
  1007. }
  1008. - (void)heading2 {
  1009. NSString *trigger = @"zss_editor.setHeading('h2');";
  1010. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1011. }];
  1012. }
  1013. - (void)heading3 {
  1014. NSString *trigger = @"zss_editor.setHeading('h3');";
  1015. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1016. }];
  1017. }
  1018. - (void)heading4 {
  1019. NSString *trigger = @"zss_editor.setHeading('h4');";
  1020. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1021. }];
  1022. }
  1023. - (void)heading5 {
  1024. NSString *trigger = @"zss_editor.setHeading('h5');";
  1025. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1026. }];
  1027. }
  1028. - (void)heading6 {
  1029. NSString *trigger = @"zss_editor.setHeading('h6');";
  1030. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1031. }];
  1032. }
  1033. - (void)paragraph {
  1034. NSString *trigger = @"zss_editor.setParagraph();";
  1035. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1036. }];
  1037. }
  1038. - (void)showFontsPicker {
  1039. // Save the selection location
  1040. [self.editorView evaluateJavaScript:@"zss_editor.prepareInsert();" completionHandler:^(NSString *result, NSError *error) {
  1041. }];
  1042. //Call picker
  1043. ZSSFontsViewController *fontPicker = [ZSSFontsViewController cancelableFontPickerViewControllerWithFontFamily:ZSSFontFamilyDefault];
  1044. fontPicker.delegate = self;
  1045. [self.navigationController pushViewController:fontPicker animated:YES];
  1046. }
  1047. - (void)setSelectedFontFamily:(ZSSFontFamily)fontFamily {
  1048. NSString *fontFamilyString;
  1049. switch (fontFamily) {
  1050. case ZSSFontFamilyDefault:
  1051. fontFamilyString = @"Arial, Helvetica, sans-serif";
  1052. break;
  1053. case ZSSFontFamilyGeorgia:
  1054. fontFamilyString = @"Georgia, serif";
  1055. break;
  1056. case ZSSFontFamilyPalatino:
  1057. fontFamilyString = @"Palatino Linotype, Book Antiqua, Palatino, serif";
  1058. break;
  1059. case ZSSFontFamilyTimesNew:
  1060. fontFamilyString = @"Times New Roman, Times, serif";
  1061. break;
  1062. case ZSSFontFamilyTrebuchet:
  1063. fontFamilyString = @"Trebuchet MS, Helvetica, sans-serif";
  1064. break;
  1065. case ZSSFontFamilyVerdana:
  1066. fontFamilyString = @"Verdana, Geneva, sans-serif";
  1067. break;
  1068. case ZSSFontFamilyCourierNew:
  1069. fontFamilyString = @"Courier New, Courier, monospace";
  1070. break;
  1071. default:
  1072. fontFamilyString = @"Arial, Helvetica, sans-serif";
  1073. break;
  1074. }
  1075. NSString *trigger = [NSString stringWithFormat:@"zss_editor.setFontFamily(\"%@\");", fontFamilyString];
  1076. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1077. }];
  1078. }
  1079. - (void)textColor {
  1080. // Save the selection location
  1081. [self.editorView evaluateJavaScript:@"zss_editor.prepareInsert();" completionHandler:^(NSString *result, NSError *error) {
  1082. }];
  1083. // Call the picker
  1084. HRColorPickerViewController *colorPicker = [HRColorPickerViewController cancelableFullColorPickerViewControllerWithColor:[UIColor whiteColor]];
  1085. colorPicker.delegate = self;
  1086. colorPicker.tag = 1;
  1087. colorPicker.title = NSLocalizedString(@"Text Color", nil);
  1088. [self.navigationController pushViewController:colorPicker animated:YES];
  1089. }
  1090. - (void)bgColor {
  1091. // Save the selection location
  1092. [self.editorView evaluateJavaScript:@"zss_editor.prepareInsert();" completionHandler:^(NSString *result, NSError *error) {
  1093. }];
  1094. // Call the picker
  1095. HRColorPickerViewController *colorPicker = [HRColorPickerViewController cancelableFullColorPickerViewControllerWithColor:[UIColor whiteColor]];
  1096. colorPicker.delegate = self;
  1097. colorPicker.tag = 2;
  1098. colorPicker.title = NSLocalizedString(@"BG Color", nil);
  1099. [self.navigationController pushViewController:colorPicker animated:YES];
  1100. }
  1101. - (void)setSelectedColor:(UIColor*)color tag:(int)tag {
  1102. NSString *hex = [NSString stringWithFormat:@"#%06x",HexColorFromUIColor(color)];
  1103. NSString *trigger;
  1104. if (tag == 1) {
  1105. trigger = [NSString stringWithFormat:@"zss_editor.setTextColor(\"%@\");", hex];
  1106. } else if (tag == 2) {
  1107. trigger = [NSString stringWithFormat:@"zss_editor.setBackgroundColor(\"%@\");", hex];
  1108. }
  1109. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1110. }];
  1111. }
  1112. - (void)undo:(ZSSBarButtonItem *)barButtonItem {
  1113. [self.editorView evaluateJavaScript:@"zss_editor.undo();" completionHandler:^(NSString *result, NSError *error) {
  1114. }];
  1115. }
  1116. - (void)redo:(ZSSBarButtonItem *)barButtonItem {
  1117. [self.editorView evaluateJavaScript:@"zss_editor.redo();" completionHandler:^(NSString *result, NSError *error) {
  1118. }];
  1119. }
  1120. - (void)insertLink {
  1121. // Save the selection location
  1122. [self.editorView evaluateJavaScript:@"zss_editor.prepareInsert();" completionHandler:^(NSString *result, NSError *error) {
  1123. }];
  1124. // Show the dialog for inserting or editing a link
  1125. [self showInsertLinkDialogWithLink:self.selectedLinkURL title:self.selectedLinkTitle];
  1126. }
  1127. - (void)showInsertLinkDialogWithLink:(NSString *)url title:(NSString *)title {
  1128. // Insert Button Title
  1129. NSString *insertButtonTitle = !self.selectedLinkURL ? NSLocalizedString(@"Insert", nil) : NSLocalizedString(@"Update", nil);
  1130. // Picker Button
  1131. UIButton *am = [UIButton buttonWithType:UIButtonTypeCustom];
  1132. am.frame = CGRectMake(0, 0, 25, 25);
  1133. [am setImage:[UIImage imageNamed:@"ZSSpicker.png" inBundle:[NSBundle bundleForClass:[ZSSRichTextEditor class]] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  1134. [am addTarget:self action:@selector(showInsertURLAlternatePicker) forControlEvents:UIControlEventTouchUpInside];
  1135. if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)]) {
  1136. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Insert Link", nil) message:nil preferredStyle:UIAlertControllerStyleAlert];
  1137. [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
  1138. textField.placeholder = NSLocalizedString(@"URL (required)", nil);
  1139. if (url) {
  1140. textField.text = url;
  1141. }
  1142. textField.rightView = am;
  1143. textField.rightViewMode = UITextFieldViewModeAlways;
  1144. textField.clearButtonMode = UITextFieldViewModeAlways;
  1145. }];
  1146. [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
  1147. textField.placeholder = NSLocalizedString(@"Title", nil);
  1148. textField.clearButtonMode = UITextFieldViewModeAlways;
  1149. textField.secureTextEntry = NO;
  1150. if (title) {
  1151. textField.text = title;
  1152. }
  1153. }];
  1154. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
  1155. [self focusTextEditor];
  1156. }]];
  1157. [alertController addAction:[UIAlertAction actionWithTitle:insertButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  1158. UITextField *linkURL = [alertController.textFields objectAtIndex:0];
  1159. UITextField *title = [alertController.textFields objectAtIndex:1];
  1160. if (!self.selectedLinkURL) {
  1161. [self insertLink:linkURL.text title:title.text];
  1162. //NSLog(@"insert link");
  1163. } else {
  1164. [self updateLink:linkURL.text title:title.text];
  1165. }
  1166. [self focusTextEditor];
  1167. }]];
  1168. [self presentViewController:alertController animated:YES completion:NULL];
  1169. } else {
  1170. self.alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Insert Link", nil) message:nil delegate:self cancelButtonTitle:NSLocalizedString(@"Cancel", nil) otherButtonTitles:insertButtonTitle, nil];
  1171. self.alertView.alertViewStyle = UIAlertViewStyleLoginAndPasswordInput;
  1172. self.alertView.tag = 2;
  1173. UITextField *linkURL = [self.alertView textFieldAtIndex:0];
  1174. linkURL.placeholder = NSLocalizedString(@"URL (required)", nil);
  1175. if (url) {
  1176. linkURL.text = url;
  1177. }
  1178. linkURL.rightView = am;
  1179. linkURL.rightViewMode = UITextFieldViewModeAlways;
  1180. UITextField *alt = [self.alertView textFieldAtIndex:1];
  1181. alt.secureTextEntry = NO;
  1182. alt.placeholder = NSLocalizedString(@"Title", nil);
  1183. if (title) {
  1184. alt.text = title;
  1185. }
  1186. [self.alertView show];
  1187. }
  1188. }
  1189. - (void)insertLink:(NSString *)url title:(NSString *)title {
  1190. NSString *trigger = [NSString stringWithFormat:@"zss_editor.insertLink(\"%@\", \"%@\");", url, title];
  1191. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1192. }];
  1193. if (_receiveEditorDidChangeEvents) {
  1194. [self updateEditor];
  1195. }
  1196. }
  1197. - (void)updateLink:(NSString *)url title:(NSString *)title {
  1198. NSString *trigger = [NSString stringWithFormat:@"zss_editor.updateLink(\"%@\", \"%@\");", url, title];
  1199. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1200. }];
  1201. if (_receiveEditorDidChangeEvents) {
  1202. [self updateEditor];
  1203. }
  1204. }
  1205. - (void)dismissAlertView {
  1206. [self.alertView dismissWithClickedButtonIndex:self.alertView.cancelButtonIndex animated:YES];
  1207. }
  1208. - (void)addCustomToolbarItemWithButton:(UIButton *)button {
  1209. if(self.customBarButtonItems == nil)
  1210. {
  1211. self.customBarButtonItems = [NSMutableArray array];
  1212. }
  1213. button.titleLabel.font = [UIFont fontWithName:@"HelveticaNeue-UltraLight" size:28.5f];
  1214. [button setTitleColor:[self barButtonItemDefaultColor] forState:UIControlStateNormal];
  1215. [button setTitleColor:[self barButtonItemSelectedDefaultColor] forState:UIControlStateHighlighted];
  1216. ZSSBarButtonItem *barButtonItem = [[ZSSBarButtonItem alloc] initWithCustomView:button];
  1217. [self.customBarButtonItems addObject:barButtonItem];
  1218. [self buildToolbar];
  1219. }
  1220. - (void)addCustomToolbarItem:(ZSSBarButtonItem *)item {
  1221. if(self.customZSSBarButtonItems == nil)
  1222. {
  1223. self.customZSSBarButtonItems = [NSMutableArray array];
  1224. }
  1225. [self.customZSSBarButtonItems addObject:item];
  1226. [self buildToolbar];
  1227. }
  1228. - (void)removeLink {
  1229. [self.editorView evaluateJavaScript:@"zss_editor.unlink();" completionHandler:^(NSString *result, NSError *error) {
  1230. }];
  1231. if (_receiveEditorDidChangeEvents) {
  1232. [self updateEditor];
  1233. }
  1234. }
  1235. - (void)quickLink {
  1236. [self.editorView evaluateJavaScript:@"zss_editor.quickLink();" completionHandler:^(NSString *result, NSError *error) {
  1237. }];
  1238. if (_receiveEditorDidChangeEvents) {
  1239. [self updateEditor];
  1240. }
  1241. }
  1242. - (void)insertImage {
  1243. // Save the selection location
  1244. [self.editorView evaluateJavaScript:@"zss_editor.prepareInsert();" completionHandler:^(NSString *result, NSError *error) {
  1245. }];
  1246. [self showInsertImageDialogWithLink:self.selectedImageURL alt:self.selectedImageAlt];
  1247. }
  1248. - (void)insertImageFromDevice {
  1249. // Save the selection location
  1250. [self.editorView evaluateJavaScript:@"zss_editor.prepareInsert();" completionHandler:^(NSString *result, NSError *error) {
  1251. }];
  1252. [self showInsertImageDialogFromDeviceWithScale:self.selectedImageScale alt:self.selectedImageAlt];
  1253. }
  1254. - (void)showInsertImageDialogWithLink:(NSString *)url alt:(NSString *)alt {
  1255. // Insert Button Title
  1256. NSString *insertButtonTitle = !self.selectedImageURL ? NSLocalizedString(@"Insert", nil) : NSLocalizedString(@"Update", nil);
  1257. // Picker Button
  1258. UIButton *am = [UIButton buttonWithType:UIButtonTypeCustom];
  1259. am.frame = CGRectMake(0, 0, 25, 25);
  1260. [am setImage:[UIImage imageNamed:@"ZSSpicker.png" inBundle:[NSBundle bundleForClass:[ZSSRichTextEditor class]] compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
  1261. [am addTarget:self action:@selector(showInsertImageAlternatePicker) forControlEvents:UIControlEventTouchUpInside];
  1262. if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)]) {
  1263. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Insert Image", nil) message:nil preferredStyle:UIAlertControllerStyleAlert];
  1264. [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
  1265. textField.placeholder = NSLocalizedString(@"URL (required)", nil);
  1266. if (url) {
  1267. textField.text = url;
  1268. }
  1269. textField.rightView = am;
  1270. textField.rightViewMode = UITextFieldViewModeAlways;
  1271. textField.clearButtonMode = UITextFieldViewModeAlways;
  1272. }];
  1273. [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
  1274. textField.placeholder = NSLocalizedString(@"Alt", nil);
  1275. textField.clearButtonMode = UITextFieldViewModeAlways;
  1276. textField.secureTextEntry = NO;
  1277. if (alt) {
  1278. textField.text = alt;
  1279. }
  1280. }];
  1281. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
  1282. [self focusTextEditor];
  1283. }]];
  1284. [alertController addAction:[UIAlertAction actionWithTitle:insertButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  1285. UITextField *imageURL = [alertController.textFields objectAtIndex:0];
  1286. UITextField *alt = [alertController.textFields objectAtIndex:1];
  1287. if (!self.selectedImageURL) {
  1288. [self insertImage:imageURL.text alt:alt.text];
  1289. } else {
  1290. [self updateImage:imageURL.text alt:alt.text];
  1291. }
  1292. [self focusTextEditor];
  1293. }]];
  1294. [self presentViewController:alertController animated:YES completion:NULL];
  1295. } else {
  1296. self.alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Insert Image", nil) message:nil delegate:self cancelButtonTitle:NSLocalizedString(@"Cancel", nil) otherButtonTitles:insertButtonTitle, nil];
  1297. self.alertView.alertViewStyle = UIAlertViewStyleLoginAndPasswordInput;
  1298. self.alertView.tag = 1;
  1299. UITextField *imageURL = [self.alertView textFieldAtIndex:0];
  1300. imageURL.placeholder = NSLocalizedString(@"URL (required)", nil);
  1301. if (url) {
  1302. imageURL.text = url;
  1303. }
  1304. imageURL.rightView = am;
  1305. imageURL.rightViewMode = UITextFieldViewModeAlways;
  1306. imageURL.clearButtonMode = UITextFieldViewModeAlways;
  1307. UITextField *alt1 = [self.alertView textFieldAtIndex:1];
  1308. alt1.secureTextEntry = NO;
  1309. alt1.placeholder = NSLocalizedString(@"Alt", nil);
  1310. alt1.clearButtonMode = UITextFieldViewModeAlways;
  1311. if (alt) {
  1312. alt1.text = alt;
  1313. }
  1314. [self.alertView show];
  1315. }
  1316. }
  1317. - (void)showInsertImageDialogFromDeviceWithScale:(CGFloat)scale alt:(NSString *)alt {
  1318. // Insert button title
  1319. NSString *insertButtonTitle = !self.selectedImageURL ? NSLocalizedString(@"Pick Image", nil) : NSLocalizedString(@"Pick New Image", nil);
  1320. //If the OS version supports the new UIAlertController go for it. Otherwise use the old UIAlertView
  1321. if ([NSProcessInfo instancesRespondToSelector:@selector(isOperatingSystemAtLeastVersion:)]) {
  1322. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Insert Image From Device", nil) message:nil preferredStyle:UIAlertControllerStyleAlert];
  1323. //Add alt text field
  1324. [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
  1325. textField.placeholder = NSLocalizedString(@"Alt", nil);
  1326. textField.clearButtonMode = UITextFieldViewModeAlways;
  1327. textField.secureTextEntry = NO;
  1328. if (alt) {
  1329. textField.text = alt;
  1330. }
  1331. }];
  1332. //Add scale text field
  1333. [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
  1334. textField.clearButtonMode = UITextFieldViewModeAlways;
  1335. textField.secureTextEntry = NO;
  1336. textField.placeholder = NSLocalizedString(@"Image scale, 0.5 by default", nil);
  1337. textField.keyboardType = UIKeyboardTypeDecimalPad;
  1338. }];
  1339. //Cancel action
  1340. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
  1341. [self focusTextEditor];
  1342. }]];
  1343. //Insert action
  1344. [alertController addAction:[UIAlertAction actionWithTitle:insertButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
  1345. UITextField *textFieldAlt = [alertController.textFields objectAtIndex:0];
  1346. UITextField *textFieldScale = [alertController.textFields objectAtIndex:1];
  1347. self.selectedImageScale = [textFieldScale.text floatValue]?:kDefaultScale;
  1348. self.selectedImageAlt = textFieldAlt.text?:@"";
  1349. [self presentViewController:self.imagePicker animated:YES completion:nil];
  1350. }]];
  1351. [self presentViewController:alertController animated:YES completion:NULL];
  1352. } else {
  1353. self.alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Insert Image", nil) message:nil delegate:self cancelButtonTitle:NSLocalizedString(@"Cancel", nil) otherButtonTitles:insertButtonTitle, nil];
  1354. self.alertView.alertViewStyle = UIAlertViewStyleLoginAndPasswordInput;
  1355. self.alertView.tag = 3;
  1356. UITextField *textFieldAlt = [self.alertView textFieldAtIndex:0];
  1357. textFieldAlt.secureTextEntry = NO;
  1358. textFieldAlt.placeholder = NSLocalizedString(@"Alt", nil);
  1359. textFieldAlt.clearButtonMode = UITextFieldViewModeAlways;
  1360. if (alt) {
  1361. textFieldAlt.text = alt;
  1362. }
  1363. UITextField *textFieldScale = [self.alertView textFieldAtIndex:1];
  1364. textFieldScale.placeholder = NSLocalizedString(@"Image scale, 0.5 by default", nil);
  1365. textFieldScale.keyboardType = UIKeyboardTypeDecimalPad;
  1366. [self.alertView show];
  1367. }
  1368. }
  1369. - (void)insertImage:(NSString *)url alt:(NSString *)alt {
  1370. NSString *trigger = [NSString stringWithFormat:@"zss_editor.insertImage(\"%@\", \"%@\");", url, alt];
  1371. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1372. }];
  1373. }
  1374. - (void)updateImage:(NSString *)url alt:(NSString *)alt {
  1375. NSString *trigger = [NSString stringWithFormat:@"zss_editor.updateImage(\"%@\", \"%@\");", url, alt];
  1376. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1377. }];
  1378. }
  1379. - (void)insertImageBase64String:(NSString *)imageBase64String alt:(NSString *)alt {
  1380. NSString *trigger = [NSString stringWithFormat:@"zss_editor.insertImageBase64String(\"%@\", \"%@\");", imageBase64String, alt];
  1381. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1382. }];
  1383. }
  1384. - (void)updateImageBase64String:(NSString *)imageBase64String alt:(NSString *)alt {
  1385. NSString *trigger = [NSString stringWithFormat:@"zss_editor.updateImageBase64String(\"%@\", \"%@\");", imageBase64String, alt];
  1386. [self.editorView evaluateJavaScript:trigger completionHandler:^(NSString *result, NSError *error) {
  1387. }];
  1388. }
  1389. - (void)updateToolBarWithButtonName:(NSString *)name {
  1390. // Items that are enabled
  1391. NSArray *itemNames = [name componentsSeparatedByString:@","];
  1392. // Special case for link
  1393. NSMutableArray *itemsModified = [[NSMutableArray alloc] init];
  1394. for (NSString *linkItem in itemNames) {
  1395. NSString *updatedItem = linkItem;
  1396. if ([linkItem hasPrefix:@"link:"]) {
  1397. updatedItem = @"link";
  1398. self.selectedLinkURL = [linkItem stringByReplacingOccurrencesOfString:@"link:" withString:@""];
  1399. } else if ([linkItem hasPrefix:@"link-title:"]) {
  1400. self.selectedLinkTitle = [self stringByDecodingURLFormat:[linkItem stringByReplacingOccurrencesOfString:@"link-title:" withString:@""]];
  1401. } else if ([linkItem hasPrefix:@"image:"]) {
  1402. updatedItem = @"image";
  1403. self.selectedImageURL = [linkItem stringByReplacingOccurrencesOfString:@"image:" withString:@""];
  1404. } else if ([linkItem hasPrefix:@"image-alt:"]) {
  1405. self.selectedImageAlt = [self stringByDecodingURLFormat:[linkItem stringByReplacingOccurrencesOfString:@"image-alt:" withString:@""]];
  1406. } else {
  1407. self.selectedImageURL = nil;
  1408. self.selectedImageAlt = nil;
  1409. self.selectedLinkURL = nil;
  1410. self.selectedLinkTitle = nil;
  1411. }
  1412. [itemsModified addObject:updatedItem];
  1413. }
  1414. itemNames = [NSArray arrayWithArray:itemsModified];
  1415. self.editorItemsEnabled = itemNames;
  1416. // Highlight items
  1417. NSArray *items = self.toolbar.items;
  1418. for (ZSSBarButtonItem *item in items) {
  1419. if ([itemNames containsObject:item.label]) {
  1420. item.tintColor = [self barButtonItemSelectedDefaultColor];
  1421. } else {
  1422. item.tintColor = [self barButtonItemDefaultColor];
  1423. }
  1424. }
  1425. }
  1426. #pragma mark - UITextView Delegate
  1427. - (void)textViewDidChange:(UITextView *)textView {
  1428. CGRect line = [textView caretRectForPosition:textView.selectedTextRange.start];
  1429. CGFloat overflow = line.origin.y + line.size.height - ( textView.contentOffset.y + textView.bounds.size.height - textView.contentInset.bottom - textView.contentInset.top );
  1430. if ( overflow > 0 ) {
  1431. // We are at the bottom of the visible text and introduced a line feed, scroll down (iOS 7 does not do it)
  1432. // Scroll caret to visible area
  1433. CGPoint offset = textView.contentOffset;
  1434. offset.y += overflow + 7; // leave 7 pixels margin
  1435. // Cannot animate with setContentOffset:animated: or caret will not appear
  1436. [UIView animateWithDuration:.2 animations:^{
  1437. [textView setContentOffset:offset];
  1438. }];
  1439. }
  1440. }
  1441. #pragma mark - WKScriptMessageHandler Delegate
  1442. - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
  1443. NSString *messageString = (NSString *)message.body;
  1444. NSLog(@"Message received: %@", messageString);
  1445. /*
  1446. Callback for when text is changed, written by @madebydouglas derived from richardortiz84 https://github.com/nnhubbard/ZSSRichTextEditor/issues/5
  1447. */
  1448. if ([messageString isEqualToString:@"paste"]) {
  1449. self.editorPaste = YES;
  1450. }
  1451. if ([messageString isEqualToString:@"input"]) {
  1452. if (_receiveEditorDidChangeEvents) {
  1453. [self updateEditor];
  1454. }
  1455. [self getText:^(NSString * result, NSError * _Nullable error) {
  1456. [self checkForMentionOrHashtagInText:result];
  1457. }];
  1458. if (self.editorPaste) {
  1459. [self blurTextEditor];
  1460. self.editorPaste = NO;
  1461. }
  1462. }
  1463. }
  1464. #pragma mark - WKNavigationDelegate Delegate
  1465. - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
  1466. NSString *query = [navigationAction.request.URL query];
  1467. NSString *urlString = [navigationAction.request.URL absoluteString];
  1468. decisionHandler(WKNavigationActionPolicyAllow);
  1469. NSLog(@"web request");
  1470. NSLog(@"%@", urlString);
  1471. NSLog(@"%@", query);
  1472. if (navigationAction.navigationType == WKNavigationTypeLinkActivated) {
  1473. //On the old UIWebView delegate it returned false Bool here
  1474. //TODO: what should we do now?
  1475. } else if ([urlString rangeOfString:@"callback://0/"].location != NSNotFound) {
  1476. // We recieved the callback
  1477. NSString *className = [urlString stringByReplacingOccurrencesOfString:@"callback://0/" withString:@""];
  1478. [self updateToolBarWithButtonName:className];
  1479. } else if ([urlString rangeOfString:@"debug://"].location != NSNotFound) {
  1480. NSLog(@"Debug Found");
  1481. // We recieved the callback
  1482. NSString *debug = [urlString stringByReplacingOccurrencesOfString:@"debug://" withString:@""];
  1483. debug = [debug stringByReplacingPercentEscapesUsingEncoding:NSStringEncodingConversionAllowLossy];
  1484. NSLog(@"%@", debug);
  1485. } else if ([urlString rangeOfString:@"scroll://"].location != NSNotFound) {
  1486. NSInteger position = [[urlString stringByReplacingOccurrencesOfString:@"scroll://" withString:@""] integerValue];
  1487. [self editorDidScrollWithPosition:position];
  1488. }
  1489. }
  1490. - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
  1491. self.editorLoaded = YES;
  1492. if (!self.internalHTML) {
  1493. self.internalHTML = @"";
  1494. }
  1495. [self updateHTML];
  1496. if(self.placeholder) {
  1497. [self setPlaceholderText];
  1498. }
  1499. if (self.customCSS) {
  1500. [self updateCSS];
  1501. }
  1502. if (self.shouldShowKeyboard) {
  1503. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  1504. [self focusTextEditor];
  1505. });
  1506. }
  1507. /*
  1508. Create listeners for when text is changed, solution by @madebydouglas derived from richardortiz84 https://github.com/nnhubbard/ZSSRichTextEditor/issues/5
  1509. */
  1510. NSString *inputListener = @"document.getElementById('zss_editor_content').addEventListener('input', function() {window.webkit.messageHandlers.jsm.postMessage('input');});";
  1511. NSString *pasteListener = @"document.getElementById('zss_editor_content').addEventListener('paste', function() {window.webkit.messageHandlers.jsm.postMessage('paste');});";
  1512. [self.editorView evaluateJavaScript:inputListener completionHandler:^(NSString *result, NSError *error) {
  1513. if (error != NULL) {
  1514. NSLog(@"%@", error);
  1515. }
  1516. }];
  1517. [self.editorView evaluateJavaScript:pasteListener completionHandler:^(NSString *result, NSError *error) {
  1518. if (error != NULL) {
  1519. NSLog(@"%@", error);
  1520. }
  1521. }];
  1522. }
  1523. #pragma mark - Mention & Hashtag Support Section
  1524. - (void)checkForMentionOrHashtagInText:(NSString *)text {
  1525. if ([text containsString:@" "] && [text length] > 0) {
  1526. NSString *lastWord = nil;
  1527. NSString *matchedWord = nil;
  1528. BOOL ContainsHashtag = NO;
  1529. BOOL ContainsMention = NO;
  1530. NSRange range = [text rangeOfString:@" " options:NSBackwardsSearch];
  1531. lastWord = [text substringFromIndex:range.location];
  1532. if (lastWord != nil) {
  1533. //Check if last word typed starts with a #
  1534. NSRegularExpression *hashtagRegex = [NSRegularExpression regularExpressionWithPattern:@"#(\\w+)" options:0 error:nil];
  1535. NSArray *hashtagMatches = [hashtagRegex matchesInString:lastWord options:0 range:NSMakeRange(0, lastWord.length)];
  1536. for (NSTextCheckingResult *match in hashtagMatches) {
  1537. NSRange wordRange = [match rangeAtIndex:1];
  1538. NSString *word = [lastWord substringWithRange:wordRange];
  1539. matchedWord = word;
  1540. ContainsHashtag = YES;
  1541. }
  1542. if (!ContainsHashtag) {
  1543. //Check if last word typed starts with a @
  1544. NSRegularExpression *mentionRegex = [NSRegularExpression regularExpressionWithPattern:@"@(\\w+)" options:0 error:nil];
  1545. NSArray *mentionMatches = [mentionRegex matchesInString:lastWord options:0 range:NSMakeRange(0, lastWord.length)];
  1546. for (NSTextCheckingResult *match in mentionMatches) {
  1547. NSRange wordRange = [match rangeAtIndex:1];
  1548. NSString *word = [lastWord substringWithRange:wordRange];
  1549. matchedWord = word;
  1550. ContainsMention = YES;
  1551. }
  1552. }
  1553. }
  1554. if (ContainsHashtag) {
  1555. [self hashtagRecognizedWithWord:matchedWord];
  1556. }
  1557. if (ContainsMention) {
  1558. [self mentionRecognizedWithWord:matchedWord];
  1559. }
  1560. }
  1561. }
  1562. #pragma mark - Callbacks
  1563. //Blank implementation
  1564. - (void)editorDidScrollWithPosition:(NSInteger)position {}
  1565. //Blank implementation
  1566. - (void)editorDidChangeWithText:(NSString *)text andHTML:(NSString *)html {}
  1567. //Blank implementation
  1568. - (void)hashtagRecognizedWithWord:(NSString *)word {}
  1569. //Blank implementation
  1570. - (void)mentionRecognizedWithWord:(NSString *)word {}
  1571. #pragma mark - AlertView
  1572. - (BOOL)alertViewShouldEnableFirstOtherButton:(UIAlertView *)alertView {
  1573. if (alertView.tag == 1) {
  1574. UITextField *textField = [alertView textFieldAtIndex:0];
  1575. UITextField *textField2 = [alertView textFieldAtIndex:1];
  1576. if ([textField.text length] == 0 || [textField2.text length] == 0) {
  1577. return NO;
  1578. }
  1579. } else if (alertView.tag == 2) {
  1580. UITextField *textField = [alertView textFieldAtIndex:0];
  1581. if ([textField.text length] == 0) {
  1582. return NO;
  1583. }
  1584. }
  1585. return YES;
  1586. }
  1587. - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
  1588. if (alertView.tag == 1) {
  1589. if (buttonIndex == 1) {
  1590. UITextField *imageURL = [alertView textFieldAtIndex:0];
  1591. UITextField *alt = [alertView textFieldAtIndex:1];
  1592. if (!self.selectedImageURL) {
  1593. [self insertImage:imageURL.text alt:alt.text];
  1594. } else {
  1595. [self updateImage:imageURL.text alt:alt.text];
  1596. }
  1597. }
  1598. } else if (alertView.tag == 2) {
  1599. if (buttonIndex == 1) {
  1600. UITextField *linkURL = [alertView textFieldAtIndex:0];
  1601. UITextField *title = [alertView textFieldAtIndex:1];
  1602. if (!self.selectedLinkURL) {
  1603. [self insertLink:linkURL.text title:title.text];
  1604. } else {
  1605. [self updateLink:linkURL.text title:title.text];
  1606. }
  1607. }
  1608. } else if (alertView.tag == 3) {
  1609. if (buttonIndex == 1) {
  1610. UITextField *textFieldAlt = [alertView textFieldAtIndex:0];
  1611. UITextField *textFieldScale = [alertView textFieldAtIndex:1];
  1612. self.selectedImageScale = [textFieldScale.text floatValue]?:kDefaultScale;
  1613. self.selectedImageAlt = textFieldAlt.text?:@"";
  1614. [self presentViewController:self.imagePicker animated:YES completion:nil];
  1615. }
  1616. }
  1617. }
  1618. #pragma mark - Asset Picker
  1619. - (void)showInsertURLAlternatePicker {
  1620. // Blank method. User should implement this in their subclass
  1621. }
  1622. - (void)showInsertImageAlternatePicker {
  1623. // Blank method. User should implement this in their subclass
  1624. }
  1625. #pragma mark - Image Picker Delegate
  1626. - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
  1627. //Dismiss the Image Picker
  1628. [picker.presentingViewController dismissViewControllerAnimated:YES completion:nil];
  1629. }
  1630. - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info{
  1631. UIImage *selectedImage = info[UIImagePickerControllerEditedImage]?:info[UIImagePickerControllerOriginalImage];
  1632. //Scale the image
  1633. CGSize targetSize = CGSizeMake(selectedImage.size.width * self.selectedImageScale, selectedImage.size.height * self.selectedImageScale);
  1634. UIGraphicsBeginImageContext(targetSize);
  1635. [selectedImage drawInRect:CGRectMake(0,0,targetSize.width,targetSize.height)];
  1636. UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
  1637. UIGraphicsEndImageContext();
  1638. //Compress the image, as it is going to be encoded rather than linked
  1639. NSData *scaledImageData = UIImageJPEGRepresentation(scaledImage, kJPEGCompression);
  1640. //Encode the image data as a base64 string
  1641. NSString *imageBase64String = [scaledImageData base64EncodedStringWithOptions:0];
  1642. //Decide if we have to insert or update
  1643. if (!self.imageBase64String) {
  1644. [self insertImageBase64String:imageBase64String alt:self.selectedImageAlt];
  1645. } else {
  1646. [self updateImageBase64String:imageBase64String alt:self.selectedImageAlt];
  1647. }
  1648. self.imageBase64String = imageBase64String;
  1649. //Dismiss the Image Picker
  1650. [picker.presentingViewController dismissViewControllerAnimated:YES completion:nil];
  1651. }
  1652. #pragma mark - Keyboard status
  1653. - (void)keyboardWillShowOrHide:(NSNotification *)notification {
  1654. // User Info
  1655. NSDictionary *info = notification.userInfo;
  1656. CGFloat duration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
  1657. int curve = [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue];
  1658. CGRect keyboardEnd = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
  1659. // Toolbar Sizes
  1660. CGFloat sizeOfToolbar = self.toolbarHolder.frame.size.height;
  1661. // Keyboard Size
  1662. CGFloat keyboardHeight = keyboardEnd.size.height;
  1663. // Correct Curve
  1664. UIViewAnimationOptions animationOptions = curve << 16;
  1665. const int extraHeight = 10;
  1666. if (keyboardEnd.origin.y < [[UIScreen mainScreen] bounds].size.height) {
  1667. [UIView animateWithDuration:duration delay:0 options:animationOptions animations:^{
  1668. // Toolbar
  1669. CGRect toolbarFrame = self.toolbarHolder.frame;
  1670. CGRect kbRect = [self.toolbarHolder.superview convertRect:keyboardEnd fromView:nil];
  1671. toolbarFrame.origin.y = kbRect.origin.y - sizeOfToolbar;
  1672. self.toolbarHolder.frame = toolbarFrame;
  1673. // Editor View
  1674. CGRect editorFrame = self.editorView.frame;
  1675. editorFrame.size.height = toolbarFrame.origin.y - extraHeight;
  1676. self.editorView.frame = editorFrame;
  1677. self.editorViewFrame = self.editorView.frame;
  1678. self.editorView.scrollView.contentInset = UIEdgeInsetsZero;
  1679. self.editorView.scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;
  1680. // Source View
  1681. CGRect sourceFrame = self.sourceView.frame;
  1682. sourceFrame.size.height = (self.view.frame.size.height - keyboardHeight) - sizeOfToolbar - extraHeight;
  1683. self.sourceView.frame = sourceFrame;
  1684. // Provide editor with keyboard height and editor view height
  1685. [self setFooterHeight:(keyboardHeight - 8)];
  1686. [self setContentHeight: self.editorViewFrame.size.height];
  1687. } completion:nil];
  1688. } else {
  1689. [UIView animateWithDuration:duration delay:0 options:animationOptions animations:^{
  1690. CGRect frame = self.toolbarHolder.frame;
  1691. if (self->_alwaysShowToolbar) {
  1692. CGFloat bottomSafeAreaInset = 0.0;
  1693. if (@available(iOS 11.0, *)) {
  1694. bottomSafeAreaInset = self.view.safeAreaInsets.bottom;
  1695. }
  1696. frame.origin.y = self.view.frame.size.height - sizeOfToolbar - bottomSafeAreaInset;
  1697. } else {
  1698. frame.origin.y = self.view.frame.size.height + keyboardHeight;
  1699. }
  1700. self.toolbarHolder.frame = frame;
  1701. // Editor View
  1702. CGRect editorFrame = self.editorView.frame;
  1703. if (self->_alwaysShowToolbar) {
  1704. editorFrame.size.height = ((self.view.frame.size.height - sizeOfToolbar) - extraHeight);
  1705. } else {
  1706. editorFrame.size.height = self.view.frame.size.height;
  1707. }
  1708. self.editorView.frame = editorFrame;
  1709. self.editorViewFrame = self.editorView.frame;
  1710. self.editorView.scrollView.contentInset = UIEdgeInsetsZero;
  1711. self.editorView.scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;
  1712. // Source View
  1713. CGRect sourceFrame = self.sourceView.frame;
  1714. if (self->_alwaysShowToolbar) {
  1715. sourceFrame.size.height = ((self.view.frame.size.height - sizeOfToolbar) - extraHeight);
  1716. } else {
  1717. sourceFrame.size.height = self.view.frame.size.height;
  1718. }
  1719. self.sourceView.frame = sourceFrame;
  1720. [self setFooterHeight:0];
  1721. [self setContentHeight:self.editorViewFrame.size.height];
  1722. } completion:nil];
  1723. }
  1724. }
  1725. #pragma mark - Utilities
  1726. - (NSString *)removeQuotesFromHTML:(NSString *)html {
  1727. html = [html stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
  1728. html = [html stringByReplacingOccurrencesOfString:@"“" withString:@"&quot;"];
  1729. html = [html stringByReplacingOccurrencesOfString:@"”" withString:@"&quot;"];
  1730. html = [html stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
  1731. html = [html stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
  1732. return html;
  1733. }
  1734. - (void)tidyHTML:(NSString *)html completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler {
  1735. html = [html stringByReplacingOccurrencesOfString:@"<br>" withString:@"<br />"];
  1736. html = [html stringByReplacingOccurrencesOfString:@"<hr>" withString:@"<hr />"];
  1737. if (self.formatHTML) {
  1738. html = [NSString stringWithFormat:@"style_html(\"%@\");", html];
  1739. [self.editorView evaluateJavaScript:html completionHandler:^(NSString *result, NSError *error) {
  1740. if (error != NULL) {
  1741. NSLog(@"HTML Tidying Error: %@", error);
  1742. }
  1743. NSLog(@"%@", result);
  1744. completionHandler(result, error);
  1745. }];
  1746. } else {
  1747. completionHandler(html, NULL);
  1748. }
  1749. }
  1750. - (UIColor *)barButtonItemDefaultColor {
  1751. if (self.toolbarItemTintColor) {
  1752. return self.toolbarItemTintColor;
  1753. }
  1754. return [UIColor colorWithRed:0.0f/255.0f green:122.0f/255.0f blue:255.0f/255.0f alpha:1.0f];
  1755. }
  1756. - (UIColor *)barButtonItemSelectedDefaultColor {
  1757. if (self.toolbarItemSelectedTintColor) {
  1758. return self.toolbarItemSelectedTintColor;
  1759. }
  1760. return [UIColor blackColor];
  1761. }
  1762. - (BOOL)isIpad {
  1763. return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
  1764. }
  1765. - (NSString *)stringByDecodingURLFormat:(NSString *)string {
  1766. NSString *result = [string stringByReplacingOccurrencesOfString:@"+" withString:@" "];
  1767. result = [result stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  1768. return result;
  1769. }
  1770. - (void)enableToolbarItems:(BOOL)enable {
  1771. NSArray *items = self.toolbar.items;
  1772. for (ZSSBarButtonItem *item in items) {
  1773. if (![item.label isEqualToString:@"source"]) {
  1774. item.enabled = enable;
  1775. }
  1776. }
  1777. }
  1778. #pragma mark - Memory Warning Section
  1779. - (void)didReceiveMemoryWarning {
  1780. [super didReceiveMemoryWarning];
  1781. }
  1782. @end