├── .gitignore ├── Image_Placeholder.png ├── VeXplore ├── Assets.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── 1.png │ │ ├── 1 3.png │ │ ├── 1 4.png │ │ ├── 1-2.png │ │ ├── 1 copy.png │ │ ├── 1 copy-2.png │ │ ├── VeXplore.png │ │ ├── VeXplore-1.png │ │ ├── VeXplore-2.png │ │ ├── VeXplore-3.png │ │ ├── VeXplore-4.png │ │ ├── VeXplore-5.png │ │ ├── VeXplore-6.png │ │ ├── VeXplore-7.png │ │ └── Contents.json │ ├── Close.imageset │ │ ├── 1461865222_cross.png │ │ ├── 1461865222_cross 2.png │ │ └── Contents.json │ ├── Safari.imageset │ │ ├── safari-logo (1).png │ │ ├── safari-logo (1) 2.png │ │ └── Contents.json │ ├── Send.imageset │ │ ├── 1463765393_send 2.png │ │ ├── 1463765393_send.png │ │ └── Contents.json │ ├── Setting.imageset │ │ ├── 1462719337_cog.png │ │ ├── 1462719337_cog 2.png │ │ └── Contents.json │ ├── Time.imageset │ │ ├── 1464770510_time 2.png │ │ ├── 1464770510_time.png │ │ └── Contents.json │ ├── Topics.imageset │ │ ├── 1464770499_note.png │ │ ├── 1464770499_note 2.png │ │ └── Contents.json │ ├── Write.imageset │ │ ├── 1462719383_pen 2.png │ │ ├── 1462719383_pen.png │ │ └── Contents.json │ ├── Github.imageset │ │ ├── 1461856003_github.png │ │ ├── 1461855896_github copy.png │ │ └── Contents.json │ ├── Logout.imageset │ │ ├── MainMenu_Logout-1.png │ │ ├── MainMenu_Logout-2.png │ │ └── Contents.json │ ├── Twitch.imageset │ │ ├── 1461859235_twitch.png │ │ ├── 1461859235_twitch copy.png │ │ └── Contents.json │ ├── Login_Background.imageset │ │ ├── background.png │ │ └── Contents.json │ ├── Psn.imageset │ │ ├── 1461864196_playstation.png │ │ ├── 1461864196_playstation copy.png │ │ └── Contents.json │ ├── Replies.imageset │ │ ├── 1462719153_back 2.png │ │ ├── 1462719153_back 3.png │ │ └── Contents.json │ ├── Thank.imageset │ │ ├── 1462719369_heart copy.png │ │ ├── 1462719369_heart copy 2.png │ │ └── Contents.json │ ├── More.imageset │ │ ├── 1464181620_MoreVertical.png │ │ ├── 1464181620_MoreVertical copy.png │ │ └── Contents.json │ ├── Reply.imageset │ │ ├── 1462719371_return copy.png │ │ ├── 1462719371_return copy 2.png │ │ └── Contents.json │ ├── Search_Nav.imageset │ │ ├── 1462719360_search.png │ │ ├── 1462719360_search copy.png │ │ └── Contents.json │ ├── Twitter.imageset │ │ ├── 1461864512_Twitter-1.png │ │ ├── 1461864512_Twitter-1 copy.png │ │ └── Contents.json │ ├── Image.imageset │ │ ├── 1463995674_18.Pictures-Day.png │ │ ├── 1463995674_18.Pictures-Day 2.png │ │ └── Contents.json │ ├── Onepassword.imageset │ │ ├── onepassword-navbar.png │ │ ├── onepassword-navbar@2x.png │ │ ├── onepassword-navbar@3x.png │ │ └── Contents.json │ ├── Sort.imageset │ │ ├── 1462719426_preferences copy.png │ │ ├── 1462719426_preferences copy 2-1.png │ │ └── Contents.json │ ├── Arrow_Right.imageset │ │ ├── 1464770037_Arrow-Right.png │ │ ├── 1464770037_Arrow-Right (1).png │ │ └── Contents.json │ ├── Lock.imageset │ │ ├── 1461862337_icon-114-lock copy.png │ │ ├── 1461862337_icon-114-lock copy 2.png │ │ └── Contents.json │ ├── Avatar_Placeholder.imageset │ │ ├── 1461865203_photo.png │ │ ├── 1461865203_photo copy.png │ │ └── Contents.json │ ├── Delete.imageset │ │ ├── 1463681808_icon-27-trash-can 2.png │ │ ├── 1463681808_icon-27-trash-can.png │ │ └── Contents.json │ ├── Report_Activity.imageset │ │ ├── 1465632015_like copy.png │ │ ├── 1465632015_like copy 2.png │ │ └── Contents.json │ ├── Hide.imageset │ │ ├── 1462747113_icon-21-eye-hidden copy.png │ │ ├── 1462747113_icon-21-eye-hidden copy 2.png │ │ └── Contents.json │ ├── Home.imageset │ │ ├── 1462732243_home_house_real_estate.png │ │ ├── 1462732243_home_house_real_estate 2.png │ │ └── Contents.json │ ├── Invisible.imageset │ │ ├── 1461862272_icon-21-eye-hidden copy.png │ │ ├── 1461862272_icon-21-eye-hidden copy 2.png │ │ └── Contents.json │ ├── Location.imageset │ │ ├── 1461861830_gps_location_map_marker.png │ │ ├── 1461861830_gps_location_map_marker copy.png │ │ └── Contents.json │ ├── Like.imageset │ │ ├── 1461861842_heart_like_love_vote.png copy 2.png │ │ ├── 1461861842_heart_like_love_vote.png copy.png │ │ └── Contents.json │ ├── Nodes.imageset │ │ ├── 1462736683_compass_direction_navigation 2.png │ │ ├── 1462736683_compass_direction_navigation.png │ │ └── Contents.json │ ├── Owner_View.imageset │ │ ├── 1464780510_eye_preview_see_seen_view.png │ │ ├── 1464780512_eye_preview_see_seen_view.png │ │ └── Contents.json │ ├── Cross.imageset │ │ ├── 1461861579_close_delete_remove_icon copy 3.png │ │ ├── 1461861579_close_delete_remove_icon copy 2-1.png │ │ └── Contents.json │ ├── Dribbble.imageset │ │ ├── 1461860642_dribbble_online_social_media.png │ │ ├── 1461860642_dribbble_online_social_media copy.png │ │ └── Contents.json │ ├── Favorite.imageset │ │ ├── 1461861842_heart_like_love_vote.png copy.png │ │ ├── 1461861842_heart_like_love_vote.png copy 2.png │ │ └── Contents.json │ ├── Tabar_Homepage.imageset │ │ ├── 1461861739_home_house_real_estate.png │ │ ├── 1461861739_home_house_real_estate copy.png │ │ └── Contents.json │ ├── Reply_Activity.imageset │ │ ├── 1465279341_SubdirectoryArrowUpLeft-1.png │ │ ├── 1465279341_SubdirectoryArrowUpLeft copy.png │ │ └── Contents.json │ ├── Wang_Xizhi.imageset │ │ ├── 0eb30f2442a7d9330262cc43af4bd11373f00133.jpg │ │ ├── 0eb30f2442a7d9330262cc43af4bd11373f00133 2.jpg │ │ └── Contents.json │ ├── Favorite_Activity.imageset │ │ ├── 1461861842_heart_like_love_vote.png.png │ │ ├── 1461861842_heart_like_love_vote.png 2.png │ │ └── Contents.json │ ├── Tick.imageset │ │ ├── 1462731373_accept_check_ok_outline_tick_yes copy.png │ │ ├── 1462731373_accept_check_ok_outline_tick_yes copy 2.png │ │ └── Contents.json │ ├── Node_Placeholder.imageset │ │ ├── 1463740612_smiley-face-emoticon-avatar-brand.png │ │ ├── 1463740612_smiley-face-emoticon-avatar-brand copy.png │ │ └── Contents.json │ ├── Comment.imageset │ │ ├── 1461861747_bubble_chat_comment_message_outline_talk copy.png │ │ ├── 1461861747_bubble_chat_comment_message_outline_talk copy 2.png │ │ └── Contents.json │ ├── Recent.imageset │ │ ├── 1462732316_alarm_alert_clock_event_history_schedule_time_watch.png │ │ ├── 1462732316_alarm_alert_clock_event_history_schedule_time_watch copy.png │ │ └── Contents.json │ ├── Search.imageset │ │ ├── 1462732255_find_in_magnifier_magnifying_research_search_view_zoom.png │ │ ├── 1462732255_find_in_magnifier_magnifying_research_search_view_zoom copy.png │ │ └── Contents.json │ ├── Homepage.imageset │ │ ├── 1461858344_house-home-export-real_estate-property-outline-stroke.png │ │ ├── 1461858344_house-home-export-real_estate-property-outline-stroke copy.png │ │ └── Contents.json │ ├── Profile.imageset │ │ ├── 1461861750_account_friend_human_man_member_person_profile_user_users.png │ │ ├── 1461861750_account_friend_human_man_member_person_profile_user_users 2.png │ │ └── Contents.json │ ├── Tabar_Search.imageset │ │ ├── 1464775278_find_in_magnifier_magnifying_research_search_view_zoom.png │ │ ├── 1464775281_find_in_magnifier_magnifying_research_search_view_zoom.png │ │ └── Contents.json │ ├── Notification.imageset │ │ ├── 1462732261_bell_sound_notification_remind_reminder_ring_ringing_schedule 2.png │ │ ├── 1462732261_bell_sound_notification_remind_reminder_ring_ringing_schedule.png │ │ └── Contents.json │ └── Ignore.imageset │ │ ├── 1464180871_bin_cancel_close_cross_delete_empty_exit_garbage_minus_out_recycle_remove_trash.png │ │ ├── 1464180871_bin_cancel_close_cross_delete_empty_exit_garbage_minus_out_recycle_remove_trash copy.png │ │ └── Contents.json ├── VeXplore-Bridging-Header.h ├── ImageClickScript.js ├── libxml_header.h ├── Define.swift ├── font.css ├── ImageClick.js ├── ModelResponse.swift ├── ImageReplace.js ├── RichTextRunDelegate.swift ├── NotificationModel.swift ├── Protocols.swift ├── WebImageUtils.swift ├── LoginPageTextField.swift ├── NodeSelectViewController.swift ├── WebImage.swift ├── CommentImageView.swift ├── SectionHeaderView.swift ├── AboutMeCell.swift ├── Base.lproj │ └── LaunchScreen.storyboard ├── RichTextUtils.swift ├── AvatarImageView.swift ├── baseStyle.css ├── Info.plist ├── ProfileSectionHeaderCell.swift ├── NodeModel.swift ├── User.swift ├── PlaceholderTextView.swift ├── ProfileActionView.swift ├── SearchBoxView.swift ├── TopicListViewController.swift ├── Utils.swift ├── BaseCenterLoadingViewController.swift ├── MyFollowingCell.swift ├── CSSStyle.swift ├── TopicDetailModel.swift ├── MemberFollowBlockCell.swift ├── TopicSearchResultCell.swift ├── URLAnalyzer.swift ├── PersonalInfoCell.swift ├── TextLine.swift ├── MyFollowingsViewController.swift ├── FavoriteNodesViewController.swift ├── TopicReplyingViewController.swift ├── RecentListViewController.swift ├── libxml │ └── HTMLtree.h ├── SearchViewController.swift ├── MyFavoriteCell.swift └── MainViewController.swift ├── VeXplore.xcodeproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── SharedKit ├── SharedKit.h ├── Info.plist ├── SharedUtils.swift ├── Response.swift ├── URLAnalysisResult.swift ├── Session.swift ├── JSON.swift ├── NetworkingUtils.swift └── SharedR.swift ├── Scripts └── gitVersion.sh ├── TodayExtension ├── Info.plist └── TodayViewController.swift ├── ActionExtension └── Info.plist └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | xcshareddata 3 | .DS_Store 4 | Version.xcconfig 5 | -------------------------------------------------------------------------------- /Image_Placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/Image_Placeholder.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/1.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/1 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/1 3.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/1 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/1 4.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/1-2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/1 copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/1 copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/1 copy-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/1 copy-2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-1.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-3.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-4.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-5.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-6.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/AppIcon.appiconset/VeXplore-7.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Close.imageset/1461865222_cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Close.imageset/1461865222_cross.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Safari.imageset/safari-logo (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Safari.imageset/safari-logo (1).png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Send.imageset/1463765393_send 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Send.imageset/1463765393_send 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Send.imageset/1463765393_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Send.imageset/1463765393_send.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Setting.imageset/1462719337_cog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Setting.imageset/1462719337_cog.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Time.imageset/1464770510_time 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Time.imageset/1464770510_time 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Time.imageset/1464770510_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Time.imageset/1464770510_time.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Topics.imageset/1464770499_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Topics.imageset/1464770499_note.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Write.imageset/1462719383_pen 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Write.imageset/1462719383_pen 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Write.imageset/1462719383_pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Write.imageset/1462719383_pen.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Close.imageset/1461865222_cross 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Close.imageset/1461865222_cross 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Github.imageset/1461856003_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Github.imageset/1461856003_github.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Logout.imageset/MainMenu_Logout-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Logout.imageset/MainMenu_Logout-1.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Logout.imageset/MainMenu_Logout-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Logout.imageset/MainMenu_Logout-2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Safari.imageset/safari-logo (1) 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Safari.imageset/safari-logo (1) 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Setting.imageset/1462719337_cog 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Setting.imageset/1462719337_cog 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Topics.imageset/1464770499_note 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Topics.imageset/1464770499_note 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Twitch.imageset/1461859235_twitch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Twitch.imageset/1461859235_twitch.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Login_Background.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Login_Background.imageset/background.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Psn.imageset/1461864196_playstation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Psn.imageset/1461864196_playstation.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Replies.imageset/1462719153_back 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Replies.imageset/1462719153_back 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Replies.imageset/1462719153_back 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Replies.imageset/1462719153_back 3.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Thank.imageset/1462719369_heart copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Thank.imageset/1462719369_heart copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Github.imageset/1461855896_github copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Github.imageset/1461855896_github copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/More.imageset/1464181620_MoreVertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/More.imageset/1464181620_MoreVertical.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Reply.imageset/1462719371_return copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Reply.imageset/1462719371_return copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Search_Nav.imageset/1462719360_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Search_Nav.imageset/1462719360_search.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Thank.imageset/1462719369_heart copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Thank.imageset/1462719369_heart copy 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Twitch.imageset/1461859235_twitch copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Twitch.imageset/1461859235_twitch copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Twitter.imageset/1461864512_Twitter-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Twitter.imageset/1461864512_Twitter-1.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Image.imageset/1463995674_18.Pictures-Day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Image.imageset/1463995674_18.Pictures-Day.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Onepassword.imageset/onepassword-navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Onepassword.imageset/onepassword-navbar.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Psn.imageset/1461864196_playstation copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Psn.imageset/1461864196_playstation copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Reply.imageset/1462719371_return copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Reply.imageset/1462719371_return copy 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Sort.imageset/1462719426_preferences copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Sort.imageset/1462719426_preferences copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Arrow_Right.imageset/1464770037_Arrow-Right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Arrow_Right.imageset/1464770037_Arrow-Right.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Image.imageset/1463995674_18.Pictures-Day 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Image.imageset/1463995674_18.Pictures-Day 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Lock.imageset/1461862337_icon-114-lock copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Lock.imageset/1461862337_icon-114-lock copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/More.imageset/1464181620_MoreVertical copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/More.imageset/1464181620_MoreVertical copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Onepassword.imageset/onepassword-navbar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Onepassword.imageset/onepassword-navbar@2x.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Onepassword.imageset/onepassword-navbar@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Onepassword.imageset/onepassword-navbar@3x.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Search_Nav.imageset/1462719360_search copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Search_Nav.imageset/1462719360_search copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Twitter.imageset/1461864512_Twitter-1 copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Twitter.imageset/1461864512_Twitter-1 copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Avatar_Placeholder.imageset/1461865203_photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Avatar_Placeholder.imageset/1461865203_photo.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Delete.imageset/1463681808_icon-27-trash-can 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Delete.imageset/1463681808_icon-27-trash-can 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Delete.imageset/1463681808_icon-27-trash-can.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Delete.imageset/1463681808_icon-27-trash-can.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Lock.imageset/1461862337_icon-114-lock copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Lock.imageset/1461862337_icon-114-lock copy 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Report_Activity.imageset/1465632015_like copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Report_Activity.imageset/1465632015_like copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Sort.imageset/1462719426_preferences copy 2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Sort.imageset/1462719426_preferences copy 2-1.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Arrow_Right.imageset/1464770037_Arrow-Right (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Arrow_Right.imageset/1464770037_Arrow-Right (1).png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Hide.imageset/1462747113_icon-21-eye-hidden copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Hide.imageset/1462747113_icon-21-eye-hidden copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Home.imageset/1462732243_home_house_real_estate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Home.imageset/1462732243_home_house_real_estate.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Report_Activity.imageset/1465632015_like copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Report_Activity.imageset/1465632015_like copy 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Avatar_Placeholder.imageset/1461865203_photo copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Avatar_Placeholder.imageset/1461865203_photo copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Hide.imageset/1462747113_icon-21-eye-hidden copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Hide.imageset/1462747113_icon-21-eye-hidden copy 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Home.imageset/1462732243_home_house_real_estate 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Home.imageset/1462732243_home_house_real_estate 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Invisible.imageset/1461862272_icon-21-eye-hidden copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Invisible.imageset/1461862272_icon-21-eye-hidden copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Location.imageset/1461861830_gps_location_map_marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Location.imageset/1461861830_gps_location_map_marker.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Invisible.imageset/1461862272_icon-21-eye-hidden copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Invisible.imageset/1461862272_icon-21-eye-hidden copy 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Like.imageset/1461861842_heart_like_love_vote.png copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Like.imageset/1461861842_heart_like_love_vote.png copy 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Like.imageset/1461861842_heart_like_love_vote.png copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Like.imageset/1461861842_heart_like_love_vote.png copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Nodes.imageset/1462736683_compass_direction_navigation 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Nodes.imageset/1462736683_compass_direction_navigation 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Nodes.imageset/1462736683_compass_direction_navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Nodes.imageset/1462736683_compass_direction_navigation.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Owner_View.imageset/1464780510_eye_preview_see_seen_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Owner_View.imageset/1464780510_eye_preview_see_seen_view.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Owner_View.imageset/1464780512_eye_preview_see_seen_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Owner_View.imageset/1464780512_eye_preview_see_seen_view.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Cross.imageset/1461861579_close_delete_remove_icon copy 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Cross.imageset/1461861579_close_delete_remove_icon copy 3.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Dribbble.imageset/1461860642_dribbble_online_social_media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Dribbble.imageset/1461860642_dribbble_online_social_media.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Favorite.imageset/1461861842_heart_like_love_vote.png copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Favorite.imageset/1461861842_heart_like_love_vote.png copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Location.imageset/1461861830_gps_location_map_marker copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Location.imageset/1461861830_gps_location_map_marker copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Tabar_Homepage.imageset/1461861739_home_house_real_estate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Tabar_Homepage.imageset/1461861739_home_house_real_estate.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Cross.imageset/1461861579_close_delete_remove_icon copy 2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Cross.imageset/1461861579_close_delete_remove_icon copy 2-1.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Favorite.imageset/1461861842_heart_like_love_vote.png copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Favorite.imageset/1461861842_heart_like_love_vote.png copy 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Reply_Activity.imageset/1465279341_SubdirectoryArrowUpLeft-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Reply_Activity.imageset/1465279341_SubdirectoryArrowUpLeft-1.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Wang_Xizhi.imageset/0eb30f2442a7d9330262cc43af4bd11373f00133.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Wang_Xizhi.imageset/0eb30f2442a7d9330262cc43af4bd11373f00133.jpg -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Dribbble.imageset/1461860642_dribbble_online_social_media copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Dribbble.imageset/1461860642_dribbble_online_social_media copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Favorite_Activity.imageset/1461861842_heart_like_love_vote.png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Favorite_Activity.imageset/1461861842_heart_like_love_vote.png.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Reply_Activity.imageset/1465279341_SubdirectoryArrowUpLeft copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Reply_Activity.imageset/1465279341_SubdirectoryArrowUpLeft copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Tabar_Homepage.imageset/1461861739_home_house_real_estate copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Tabar_Homepage.imageset/1461861739_home_house_real_estate copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Tick.imageset/1462731373_accept_check_ok_outline_tick_yes copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Tick.imageset/1462731373_accept_check_ok_outline_tick_yes copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Wang_Xizhi.imageset/0eb30f2442a7d9330262cc43af4bd11373f00133 2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Wang_Xizhi.imageset/0eb30f2442a7d9330262cc43af4bd11373f00133 2.jpg -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Favorite_Activity.imageset/1461861842_heart_like_love_vote.png 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Favorite_Activity.imageset/1461861842_heart_like_love_vote.png 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Tick.imageset/1462731373_accept_check_ok_outline_tick_yes copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Tick.imageset/1462731373_accept_check_ok_outline_tick_yes copy 2.png -------------------------------------------------------------------------------- /VeXplore.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Node_Placeholder.imageset/1463740612_smiley-face-emoticon-avatar-brand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Node_Placeholder.imageset/1463740612_smiley-face-emoticon-avatar-brand.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Comment.imageset/1461861747_bubble_chat_comment_message_outline_talk copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Comment.imageset/1461861747_bubble_chat_comment_message_outline_talk copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Comment.imageset/1461861747_bubble_chat_comment_message_outline_talk copy 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Comment.imageset/1461861747_bubble_chat_comment_message_outline_talk copy 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Node_Placeholder.imageset/1463740612_smiley-face-emoticon-avatar-brand copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Node_Placeholder.imageset/1463740612_smiley-face-emoticon-avatar-brand copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Recent.imageset/1462732316_alarm_alert_clock_event_history_schedule_time_watch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Recent.imageset/1462732316_alarm_alert_clock_event_history_schedule_time_watch.png -------------------------------------------------------------------------------- /VeXplore/VeXplore-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "OnePasswordExtension.h" 6 | #import "libxml_header.h" 7 | #import 8 | -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Search.imageset/1462732255_find_in_magnifier_magnifying_research_search_view_zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Search.imageset/1462732255_find_in_magnifier_magnifying_research_search_view_zoom.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Homepage.imageset/1461858344_house-home-export-real_estate-property-outline-stroke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Homepage.imageset/1461858344_house-home-export-real_estate-property-outline-stroke.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Recent.imageset/1462732316_alarm_alert_clock_event_history_schedule_time_watch copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Recent.imageset/1462732316_alarm_alert_clock_event_history_schedule_time_watch copy.png -------------------------------------------------------------------------------- /VeXplore/ImageClickScript.js: -------------------------------------------------------------------------------- 1 | 2 | document.addEventListener('click', function (e) 3 | { 4 | if (e.target.nodeName.toUpperCase() == 'IMG') 5 | { 6 | var href = location.href; 7 | href += encodeURI(e.target.src); 8 | location.href = href; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Profile.imageset/1461861750_account_friend_human_man_member_person_profile_user_users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Profile.imageset/1461861750_account_friend_human_man_member_person_profile_user_users.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Search.imageset/1462732255_find_in_magnifier_magnifying_research_search_view_zoom copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Search.imageset/1462732255_find_in_magnifier_magnifying_research_search_view_zoom copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Homepage.imageset/1461858344_house-home-export-real_estate-property-outline-stroke copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Homepage.imageset/1461858344_house-home-export-real_estate-property-outline-stroke copy.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Profile.imageset/1461861750_account_friend_human_man_member_person_profile_user_users 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Profile.imageset/1461861750_account_friend_human_man_member_person_profile_user_users 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Tabar_Search.imageset/1464775278_find_in_magnifier_magnifying_research_search_view_zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Tabar_Search.imageset/1464775278_find_in_magnifier_magnifying_research_search_view_zoom.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Tabar_Search.imageset/1464775281_find_in_magnifier_magnifying_research_search_view_zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Tabar_Search.imageset/1464775281_find_in_magnifier_magnifying_research_search_view_zoom.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Notification.imageset/1462732261_bell_sound_notification_remind_reminder_ring_ringing_schedule 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Notification.imageset/1462732261_bell_sound_notification_remind_reminder_ring_ringing_schedule 2.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Notification.imageset/1462732261_bell_sound_notification_remind_reminder_ring_ringing_schedule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Notification.imageset/1462732261_bell_sound_notification_remind_reminder_ring_ringing_schedule.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Ignore.imageset/1464180871_bin_cancel_close_cross_delete_empty_exit_garbage_minus_out_recycle_remove_trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Ignore.imageset/1464180871_bin_cancel_close_cross_delete_empty_exit_garbage_minus_out_recycle_remove_trash.png -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Ignore.imageset/1464180871_bin_cancel_close_cross_delete_empty_exit_garbage_minus_out_recycle_remove_trash copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xsxsxszs/VeXplore/HEAD/VeXplore/Assets.xcassets/Ignore.imageset/1464180871_bin_cancel_close_cross_delete_empty_exit_garbage_minus_out_recycle_remove_trash copy.png -------------------------------------------------------------------------------- /VeXplore/libxml_header.h: -------------------------------------------------------------------------------- 1 | // 2 | // libxml_header.h 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | #ifndef libxml_header_h 9 | #define libxml_header_h 10 | 11 | #import "tree.h" 12 | #import "parser.h" 13 | #import "HTMLtree.h" 14 | #import "HTMLparser.h" 15 | #import "xpath.h" 16 | 17 | #endif /* libxml_header_h */ 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /VeXplore/Define.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Define.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | // Closure 9 | typealias CompletionTask = (_ success: Bool) -> Void 10 | typealias IgnoreHandler = (_ topicId: String) -> Void 11 | typealias UnfavoriteHandler = (_ topicId: String) -> Void 12 | typealias UnfollowingHandler = (_ username: String) -> Void 13 | 14 | let isProEnabled = true 15 | -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Login_Background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "background.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /VeXplore/font.css: -------------------------------------------------------------------------------- 1 | /* font-size */ 2 | h1 { 3 | font-size: px; 4 | } 5 | 6 | h2 { 7 | font-size: px; 8 | } 9 | 10 | h3 { 11 | font-size: px; 12 | } 13 | 14 | pre { 15 | font-size: px; 16 | } 17 | 18 | body { 19 | font-size: px; 20 | } 21 | 22 | .subtle { 23 | font-size : px; 24 | } 25 | 26 | .subtle .fade { 27 | font-size : px; 28 | } 29 | -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Write.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462719383_pen.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462719383_pen 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461865222_cross.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461865222_cross 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Logout.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "MainMenu_Logout-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "MainMenu_Logout-2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Safari.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "safari-logo (1).png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "safari-logo (1) 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Send.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1463765393_send.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1463765393_send 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Setting.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462719337_cog 2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462719337_cog.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Time.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1464770510_time.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1464770510_time 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Topics.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1464770499_note.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1464770499_note 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Github.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461855896_github copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461856003_github.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Replies.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462719153_back 2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462719153_back 3.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Twitch.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461859235_twitch.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461859235_twitch copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Search_Nav.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462719360_search copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462719360_search.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Thank.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462719369_heart copy 2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462719369_heart copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Avatar_Placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461865203_photo copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461865203_photo.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/More.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1464181620_MoreVertical copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1464181620_MoreVertical.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Psn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461864196_playstation.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461864196_playstation copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Reply.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462719371_return copy 2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462719371_return copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Twitter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461864512_Twitter-1 copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461864512_Twitter-1.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/ImageClick.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('click', function (e) 2 | { 3 | if (e.target.nodeName.toUpperCase() == 'IMG') 4 | { 5 | location.href = 'https://' + '/special_tag_for_image_tap_vexplore/' + e.target.offsetLeft + '/special_tag_for_image_tap_vexplore/' + e.target.offsetTop + '/special_tag_for_image_tap_vexplore/' + e.target.clientWidth + '/special_tag_for_image_tap_vexplore/' + e.target.clientHeight + '/special_tag_for_image_tap_vexplore/' + e.target.attributes['image_cache_key'].value 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Arrow_Right.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1464770037_Arrow-Right.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1464770037_Arrow-Right (1).png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1463995674_18.Pictures-Day.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1463995674_18.Pictures-Day 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Report_Activity.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1465632015_like copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1465632015_like copy 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Delete.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1463681808_icon-27-trash-can 2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1463681808_icon-27-trash-can.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Lock.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461862337_icon-114-lock copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461862337_icon-114-lock copy 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Sort.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462719426_preferences copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462719426_preferences copy 2-1.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Hide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462747113_icon-21-eye-hidden copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462747113_icon-21-eye-hidden copy 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Home.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462732243_home_house_real_estate.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462732243_home_house_real_estate 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Invisible.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461862272_icon-21-eye-hidden copy 2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461862272_icon-21-eye-hidden copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Location.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461861830_gps_location_map_marker copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461861830_gps_location_map_marker.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Nodes.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462736683_compass_direction_navigation.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462736683_compass_direction_navigation 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Owner_View.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1464780512_eye_preview_see_seen_view.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1464780510_eye_preview_see_seen_view.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Tabar_Homepage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461861739_home_house_real_estate.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461861739_home_house_real_estate copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Favorite.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461861842_heart_like_love_vote.png copy 2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461861842_heart_like_love_vote.png copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Favorite_Activity.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461861842_heart_like_love_vote.png.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461861842_heart_like_love_vote.png 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Like.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461861842_heart_like_love_vote.png copy 2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461861842_heart_like_love_vote.png copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Reply_Activity.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1465279341_SubdirectoryArrowUpLeft-1.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1465279341_SubdirectoryArrowUpLeft copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Cross.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461861579_close_delete_remove_icon copy 3.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461861579_close_delete_remove_icon copy 2-1.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Dribbble.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461860642_dribbble_online_social_media copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461860642_dribbble_online_social_media.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Wang_Xizhi.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0eb30f2442a7d9330262cc43af4bd11373f00133.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0eb30f2442a7d9330262cc43af4bd11373f00133 2.jpg", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Onepassword.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "onepassword-navbar.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "onepassword-navbar@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "onepassword-navbar@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Tick.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462731373_accept_check_ok_outline_tick_yes copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462731373_accept_check_ok_outline_tick_yes copy 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Node_Placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1463740612_smiley-face-emoticon-avatar-brand copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1463740612_smiley-face-emoticon-avatar-brand.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SharedKit/SharedKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // SharedKit.h 3 | // SharedKit 4 | // 5 | // Created by Jing Chen on 1/4/17. 6 | // Copyright © 2017 Jimmy. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SharedKit. 12 | FOUNDATION_EXPORT double SharedKitVersionNumber; 13 | 14 | //! Project version string for SharedKit. 15 | FOUNDATION_EXPORT const unsigned char SharedKitVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Comment.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461861747_bubble_chat_comment_message_outline_talk copy 2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461861747_bubble_chat_comment_message_outline_talk copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Recent.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462732316_alarm_alert_clock_event_history_schedule_time_watch.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462732316_alarm_alert_clock_event_history_schedule_time_watch copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Homepage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461858344_house-home-export-real_estate-property-outline-stroke.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461858344_house-home-export-real_estate-property-outline-stroke copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Profile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1461861750_account_friend_human_man_member_person_profile_user_users 2.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1461861750_account_friend_human_man_member_person_profile_user_users.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Search.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462732255_find_in_magnifier_magnifying_research_search_view_zoom.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462732255_find_in_magnifier_magnifying_research_search_view_zoom copy.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Tabar_Search.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1464775281_find_in_magnifier_magnifying_research_search_view_zoom.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1464775278_find_in_magnifier_magnifying_research_search_view_zoom.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Notification.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1462732261_bell_sound_notification_remind_reminder_ring_ringing_schedule.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1462732261_bell_sound_notification_remind_reminder_ring_ringing_schedule 2.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/Assets.xcassets/Ignore.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "1464180871_bin_cancel_close_cross_delete_empty_exit_garbage_minus_out_recycle_remove_trash copy.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "1464180871_bin_cancel_close_cross_delete_empty_exit_garbage_minus_out_recycle_remove_trash.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VeXplore/ModelResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelResponse.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | class CommonResponse 10 | { 11 | var success = false 12 | var message = [String]() 13 | 14 | init(success: Bool, message: [String] = [String]()) 15 | { 16 | self.success = success 17 | self.message = message 18 | } 19 | 20 | } 21 | 22 | class ValueResponse: CommonResponse 23 | { 24 | var value: T? 25 | 26 | init(value: T? = nil, success: Bool, message: [String] = [String]()) 27 | { 28 | super.init(success: success, message: message) 29 | self.value = value 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /VeXplore/ImageReplace.js: -------------------------------------------------------------------------------- 1 | var imageNodes = document.getElementsByTagName("img") 2 | for (var i = 0; i < imageNodes.length; i++) 3 | { 4 | try { 5 | var $el = imageNodes[i].parentNode; 6 | var href = $el.getAttribute('href').split('/').pop(); 7 | var src = imageNodes[i].getAttribute('src').split('/').pop(); 8 | var imageCacheKey = imageNodes[i].getAttribute('image_cache_key').split('/').pop(); 9 | if (href === src || href === imageCacheKey) 10 | { 11 | $el.setAttribute('href', 'javscript:void();'); 12 | } 13 | } catch (err) {} 14 | 15 | if (imageNodes[i].getAttribute("image_cache_key") == "%@") 16 | { 17 | imageNodes[i].setAttribute("src", "%@") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /SharedKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Scripts/gitVersion.sh: -------------------------------------------------------------------------------- 1 | # !/bin/bash 2 | 3 | 4 | ANNOTATED_TAG_NAME="baseVersioningTag" 5 | VERSIONING_XCCONFIG_FILE_PATH="${SRCROOT}/Version.xcconfig" 6 | 7 | if [ ! -e $VERSIONING_XCCONFIG_FILE_PATH ]; then 8 | touch $VERSIONING_XCCONFIG_FILE_PATH 9 | fi 10 | 11 | pushd "$SRCROOT" >/dev/null 12 | # Fetch Git Output 13 | GIT_DESCRIBE_OUTPUT=$(git describe --match $ANNOTATED_TAG_NAME) # e.g., baseVersioningTag-423-g41438bd 14 | 15 | # Use Internal Field Separator to split on "-" 16 | IFS='-' 17 | GIT_DESCRIBE_COMPONENTS=($GIT_DESCRIBE_OUTPUT) # baseVersioningTag 423 g41438bd 18 | unset IFS 19 | VERSION_NUMBER=${GIT_DESCRIBE_COMPONENTS[1]} 20 | 21 | ## Write Build Configuration Values to Xcode Versioning file 22 | if [ -z $VERSION_NUMBER ] 23 | then 24 | echo "BUILD_NUMBER = 1" > $VERSIONING_XCCONFIG_FILE_PATH # set default value if VERSION_NUMBER is empty 25 | else 26 | echo "BUILD_NUMBER = ${VERSION_NUMBER}" > $VERSIONING_XCCONFIG_FILE_PATH 27 | fi 28 | 29 | # Finish file with a Touch 30 | touch $VERSIONING_XCCONFIG_FILE_PATH 31 | popd >/dev/null 32 | -------------------------------------------------------------------------------- /TodayExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | 今日热议 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | ${RELEASE_VERSION} 21 | CFBundleVersion 22 | ${BUILD_NUMBER} 23 | NSExtension 24 | 25 | NSExtensionPointIdentifier 26 | com.apple.widget-extension 27 | NSExtensionPrincipalClass 28 | $(PRODUCT_NAME).TodayViewController 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /SharedKit/SharedUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | ////////////////////////// 10 | ////// Thread Utils ////// 11 | ////////////////////////// 12 | 13 | public func dispatch_async_safely_to_main_queue(block: @escaping ()->()) 14 | { 15 | dispatch_async_safely_to_queue(DispatchQueue.main, block) 16 | } 17 | 18 | public func dispatch_async_to_background_queue(block: @escaping () -> ()) 19 | { 20 | dispatch_async_safely_to_queue(DispatchQueue.global(qos: .default), block) 21 | } 22 | 23 | public func dispatch_async_safely_to_queue(_ queue: DispatchQueue, _ block: @escaping ()->()) 24 | { 25 | if queue === DispatchQueue.main, Thread.isMainThread 26 | { 27 | block() 28 | } 29 | else 30 | { 31 | queue.async { 32 | block() 33 | } 34 | } 35 | } 36 | 37 | public func dispatch_delay_in_main_queue(delay: TimeInterval, block: @escaping ()->()) 38 | { 39 | if Thread.isMainThread 40 | { 41 | DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: block) 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /VeXplore/RichTextRunDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RichTextRunDelegate.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | class RichTextRunDelegate 11 | { 12 | var ascent: CGFloat = 0.0 13 | var descent: CGFloat = 0.0 14 | var width: CGFloat = 0.0 15 | 16 | var ctRunDelegate: CTRunDelegate? { 17 | var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateCurrentVersion, dealloc: { (refCon) -> Void in 18 | }, getAscent: { (refCon) -> CGFloat in 19 | let ref = unsafeBitCast(refCon, to: RichTextRunDelegate.self) 20 | return ref.ascent 21 | }, getDescent: { (refCon) -> CGFloat in 22 | let ref = unsafeBitCast(refCon, to: RichTextRunDelegate.self) 23 | return ref.descent 24 | }, getWidth: { (refCon) -> CGFloat in 25 | let ref = unsafeBitCast(refCon, to: RichTextRunDelegate.self) 26 | return ref.width 27 | }) 28 | 29 | let selfPtr = UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque()) 30 | return CTRunDelegateCreate(&callbacks, selfPtr) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /VeXplore/NotificationModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationModel.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | struct NotificationModel: Codable 11 | { 12 | private(set) var avatar: String? 13 | private(set) var username: String? 14 | private(set) var title: String? 15 | private(set) var date: String? 16 | private(set) var comment: String? 17 | private(set) var notificationId: String? 18 | private(set) var topicId: String? 19 | 20 | init(rootNode: HTMLNode) 21 | { 22 | avatar = rootNode.xPath(".//img[@class='avatar']").first?["src"] 23 | username = rootNode.xPath("./table/tr/td[2]/span[1]/a[1]/strong").first?.content 24 | title = rootNode.xPath("./table/tr/td[2]/span[1]").first?.content 25 | date = rootNode.xPath("./table/tr/td[2]/span[2]").first?.content 26 | comment = rootNode.xPath("./table/tr/td[2]/div[@class='payload']").first?.content 27 | notificationId = rootNode["id"]?.replacingOccurrences(of: "n_", with: SharedR.String.Empty) 28 | let topicIdUrl = rootNode.xPath("./table/tr/td[2]/span[1]/a[2]").first?["href"] 29 | topicId = topicIdUrl?.extractId() 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /VeXplore/Protocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Protocols.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | protocol AvatarTappedDelegate: class 9 | { 10 | func avatarTapped(withUsername username: String) 11 | } 12 | 13 | 14 | protocol TopicCellDelegate: AvatarTappedDelegate 15 | { 16 | func nodeTapped(withNodeId nodeId: String, nodeName: String?) 17 | } 18 | 19 | 20 | protocol TopicDetailDelegate: TopicCellDelegate 21 | { 22 | func favoriteBtnTapped() 23 | } 24 | 25 | 26 | protocol SwipeCellDelegate: class 27 | { 28 | func cellWillBeginSwipe(at indexPath: IndexPath) 29 | func cellShouldBeginSwpipe() -> Bool 30 | } 31 | // optional func in SwipeCellDelegate 32 | extension SwipeCellDelegate 33 | { 34 | func cellShouldBeginSwpipe() -> Bool 35 | { 36 | return true 37 | } 38 | } 39 | 40 | 41 | protocol NotificationCellDelegate: AvatarTappedDelegate, SwipeCellDelegate 42 | { 43 | func deleteNotification(withId notificationId: String) 44 | } 45 | 46 | 47 | protocol CommentCellDelegate: AvatarTappedDelegate, SwipeCellDelegate 48 | { 49 | func thankBtnTapped(withReplyId replyId: String, indexPath: IndexPath) 50 | func ignoreBtnTapped(withReplyId replyId: String) 51 | func replyBtnTapped(withUsername username: String, index: String) 52 | } 53 | -------------------------------------------------------------------------------- /VeXplore/WebImageUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebImageUtils.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String 11 | { 12 | var md5: String { 13 | let context = UnsafeMutablePointer.allocate(capacity: 1) 14 | var digest = Array(repeating:0, count:Int(CC_MD5_DIGEST_LENGTH)) 15 | CC_MD5_Init(context) 16 | CC_MD5_Update(context, self, CC_LONG(lengthOfBytes(using: String.Encoding.utf8))) 17 | CC_MD5_Final(&digest, context) 18 | context.deallocate(capacity: 1) 19 | var hexString = "" 20 | for byte in digest 21 | { 22 | hexString += String(format:"%02x", byte) 23 | } 24 | return hexString 25 | } 26 | 27 | } 28 | 29 | 30 | extension Data 31 | { 32 | static var gifHeader: [UInt8] = [0x47, 0x49, 0x46] 33 | 34 | var isGifFormat: Bool { 35 | var buffer = [UInt8](repeating: 0, count: 3) 36 | (self as NSData).getBytes(&buffer, length: 3) 37 | if buffer == Data.gifHeader 38 | { 39 | return true 40 | } 41 | return false 42 | } 43 | 44 | } 45 | 46 | extension Int 47 | { 48 | func isValidStatusCode() -> Bool 49 | { 50 | return (200..<400).contains(self) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ActionExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | VeXplore 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | ${RELEASE_VERSION} 21 | CFBundleVersion 22 | ${BUILD_NUMBER} 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | NSExtensionActivationRule 28 | 29 | NSExtensionActivationSupportsWebURLWithMaxCount 30 | 1 31 | 32 | 33 | NSExtensionPointIdentifier 34 | com.apple.ui-services 35 | NSExtensionPrincipalClass 36 | $(PRODUCT_NAME).ActionNavigationController 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /VeXplore/LoginPageTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginPageTextField.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | //import SharedKit 9 | // 10 | //class LoginPageTextField: UITextField 11 | //{ 12 | // lazy var bendingLine: BendingLine = { 13 | // let view = BendingLine() 14 | // view.translatesAutoresizingMaskIntoConstraints = false 15 | // view.backgroundColor = .clear 16 | // view.lineColor = .desc 17 | // 18 | // return view 19 | // }() 20 | // 21 | // override init(frame: CGRect) 22 | // { 23 | // super.init(frame: frame) 24 | // 25 | // addSubview(bendingLine) 26 | // let bindings = ["bendingLine": bendingLine] 27 | // addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[bendingLine]|", metrics: nil, views: bindings)) 28 | // bendingLine.topAnchor.constraint(equalTo: bottomAnchor).isActive = true 29 | // bendingLine.heightAnchor.constraint(equalToConstant: 8.0).isActive = true 30 | // 31 | // font = SharedR.Font.Medium 32 | // clipsToBounds = false 33 | // autocorrectionType = .no 34 | // autocapitalizationType = .none 35 | // } 36 | // 37 | // required init?(coder aDecoder: NSCoder) 38 | // { 39 | // fatalError("init(coder:) has not been implemented") 40 | // } 41 | // 42 | //} 43 | 44 | -------------------------------------------------------------------------------- /VeXplore/NodeSelectViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NodeSelectViewController.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | protocol NodeSelectDelegate: class 10 | { 11 | func didSelectNode(_ node: NodeModel) 12 | } 13 | 14 | class NodeSelectViewController: NodeSearchViewController 15 | { 16 | weak var delegate: NodeSelectDelegate? 17 | 18 | override func viewDidLoad() 19 | { 20 | super.viewDidLoad() 21 | title = R.String.NodeChoose 22 | 23 | let closeBtn = UIBarButtonItem(image: R.Image.Close, style: .plain, target: self, action: #selector(closeBtnTapped)) 24 | navigationItem.leftBarButtonItem = closeBtn 25 | tableView.estimatedRowHeight = R.Constant.EstimatedRowHeight 26 | } 27 | 28 | @objc 29 | private func closeBtnTapped() 30 | { 31 | dismiss(animated: true, completion: nil) 32 | } 33 | 34 | // MARK: - UITableViewDelegate 35 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 36 | { 37 | let nodeModel = isSearching ? searchResultNodes[indexPath.section].nodes[indexPath.row] : groupedNodes[indexPath.section].nodes[indexPath.row] 38 | DispatchQueue.main.async { 39 | self.delegate?.didSelectNode(nodeModel) 40 | self.dismiss(animated: true, completion: nil) 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /SharedKit/Response.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Response.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct DataResponse 11 | { 12 | public let request: URLRequest? 13 | public let response: HTTPURLResponse? 14 | public let data: Data? 15 | public let result: Result 16 | 17 | } 18 | 19 | public enum Result 20 | { 21 | case success(Value) 22 | case failure(Error?) 23 | 24 | public var isSuccess: Bool { 25 | switch self { 26 | case .success: 27 | return true 28 | case .failure: 29 | return false 30 | } 31 | } 32 | 33 | public var value: Value? { 34 | switch self 35 | { 36 | case .success(let value): 37 | return value 38 | case .failure: 39 | return nil 40 | } 41 | } 42 | 43 | public var error: Error? { 44 | switch self 45 | { 46 | case .success: 47 | return nil 48 | case .failure(let error): 49 | return error 50 | } 51 | } 52 | } 53 | 54 | public struct DataResponseSerializer 55 | { 56 | public typealias SerializeResponse = (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result 57 | var serializeResponse: SerializeResponse 58 | public init(serializeResponse: @escaping SerializeResponse) 59 | { 60 | self.serializeResponse = serializeResponse 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /VeXplore/WebImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebImage.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | typealias ImageRetrieveCompletionHandler = ((_ image: UIImage?, _ originalData: Data?, _ error: NSError?) -> Void) 11 | 12 | struct WebImage 13 | { 14 | static func retrieveImage(with url: URL, completionHandler: ImageRetrieveCompletionHandler?) 15 | { 16 | ImageCache.default.retrieveImageData(forKey: url.cacheKey, completionHandler: { originalData in 17 | if originalData != nil 18 | { 19 | completionHandler?(nil, originalData, nil) 20 | } 21 | else 22 | { 23 | WebImage.downloadAndCacheImage(with: url, forKey: url.cacheKey, completionHandler: completionHandler) 24 | } 25 | }) 26 | } 27 | 28 | private static func downloadAndCacheImage(with url: URL, 29 | forKey key: String, 30 | completionHandler: ImageRetrieveCompletionHandler?) 31 | { 32 | ImageDownloader.default.downloadImage(with: url, completionHandler: { image, originalData, error in 33 | if let image = image, let originalData = originalData 34 | { 35 | ImageCache.default.cache(image: image, originalData: originalData, forKey: key) 36 | } 37 | completionHandler?(image, originalData, error) 38 | }) 39 | } 40 | 41 | } 42 | 43 | extension URL 44 | { 45 | var cacheKey: String { 46 | return absoluteString 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /VeXplore/CommentImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommentImageView.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | protocol CommentImageTapDelegate: class 10 | { 11 | func commentImageSingleTap(_ imageView: CommentImageView) 12 | } 13 | 14 | class CommentImageView: AnimatableImageView 15 | { 16 | var imageURL: String? 17 | weak var delegate: CommentImageTapDelegate? 18 | 19 | init() 20 | { 21 | super.init(frame: CGRect(x: 0, y: 0, width: R.Constant.CommentImageSize, height: R.Constant.CommentImageSize)) 22 | contentMode = .scaleAspectFill 23 | clipsToBounds = true 24 | isUserInteractionEnabled = true 25 | } 26 | 27 | required init?(coder aDecoder: NSCoder) 28 | { 29 | fatalError("init(coder:) has not been implemented") 30 | } 31 | 32 | override func willMove(toSuperview newSuperview: UIView?) 33 | { 34 | super.willMove(toSuperview: newSuperview) 35 | guard image == nil else { 36 | return 37 | } 38 | 39 | if let imageURL = imageURL, let URL = URL(string: imageURL) 40 | { 41 | setImageOriginalData(with: URL, placeholder: R.Image.ImagePlaceholder) 42 | } 43 | } 44 | 45 | override func touchesEnded(_ touches: Set, with event: UIEvent?) 46 | { 47 | if let touch = touches.first, touch.tapCount == 1 48 | { 49 | handleSingleTap() 50 | } 51 | next?.touchesCancelled(touches, with: event) 52 | } 53 | 54 | func handleSingleTap() 55 | { 56 | delegate?.commentImageSingleTap(self) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /VeXplore/SectionHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SectionHeaderView.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | class SectionHeaderView: BaseTableViewHeaderFooterView 11 | { 12 | lazy var contentLabel: UILabel = { 13 | let label = UILabel() 14 | label.translatesAutoresizingMaskIntoConstraints = false 15 | label.font = SharedR.Font.VeryLarge 16 | label.textColor = .body 17 | 18 | return label 19 | }() 20 | 21 | override init(reuseIdentifier: String?) 22 | { 23 | super.init(reuseIdentifier: reuseIdentifier) 24 | 25 | contentView.addSubview(contentLabel) 26 | let bindings = ["contentLabel": contentLabel] 27 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-14-[contentLabel]-12-|", metrics: nil, views: bindings)) 28 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-2-[contentLabel]-2-|", metrics: nil, views: bindings)) 29 | 30 | backgroundView = { 31 | let view = UIView(frame: bounds) 32 | view.backgroundColor = UIColor.border.withAlphaComponent(0.8) 33 | 34 | return view 35 | }() 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) 39 | { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | 43 | @objc 44 | override func refreshColorScheme() 45 | { 46 | super.refreshColorScheme() 47 | contentLabel.textColor = .body 48 | backgroundView?.backgroundColor = UIColor.border.withAlphaComponent(0.8) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /VeXplore/AboutMeCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutMeCell.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | class AboutMeCell: BaseTableViewCell 11 | { 12 | lazy var contentLabel: UILabel = { 13 | let label = UILabel() 14 | label.translatesAutoresizingMaskIntoConstraints = false 15 | label.numberOfLines = 0 16 | label.font = SharedR.Font.Medium 17 | label.textColor = UIColor.desc 18 | 19 | return label 20 | }() 21 | 22 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) 23 | { 24 | super.init(style: style, reuseIdentifier: reuseIdentifier) 25 | 26 | contentView.addSubview(contentLabel) 27 | let bindings = ["contentLabel": contentLabel] 28 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-12-[contentLabel]-12-|", metrics: nil, views: bindings)) 29 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[contentLabel]-8-|", metrics: nil, views: bindings)) 30 | 31 | preservesSuperviewLayoutMargins = false 32 | layoutMargins = .zero 33 | selectionStyle = .none 34 | } 35 | 36 | required init?(coder aDecoder: NSCoder) 37 | { 38 | fatalError("init(coder:) has not been implemented") 39 | } 40 | 41 | override func prepareForReuse() 42 | { 43 | super.prepareForReuse() 44 | contentLabel.font = SharedR.Font.Medium 45 | } 46 | 47 | @objc 48 | override func refreshColorScheme() 49 | { 50 | super.refreshColorScheme() 51 | contentLabel.textColor = .desc 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /VeXplore/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /VeXplore/RichTextUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RichTextUtils.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | let AttachmentAttributeName = "vexplore.textAttributeName.attachment" 12 | let HighlightAttributeName = "vexplore.textAttributeName.highlight" 13 | 14 | extension NSMutableAttributedString 15 | { 16 | class func attachmentString(with imageView: UIImageView, size: CGSize, alignTo font: UIFont) -> NSMutableAttributedString 17 | { 18 | let attrs = NSMutableAttributedString(string: " ") 19 | attrs.addAttribute(AttachmentAttributeName, value: imageView) 20 | let delegate = RichTextRunDelegate() 21 | delegate.width = size.width 22 | delegate.ascent = max(size.height + font.descender, 0) 23 | delegate.descent = size.height - delegate.ascent 24 | if let ctRunDelegate = delegate.ctRunDelegate 25 | { 26 | attrs.addAttribute(kCTRunDelegateAttributeName as String, value: ctRunDelegate) 27 | } 28 | return attrs 29 | } 30 | 31 | func setHighlightText(withColor color: UIColor, url: String) 32 | { 33 | addAttribute(NSAttributedStringKey.foregroundColor.rawValue, value: color) 34 | addAttribute(HighlightAttributeName, value: url) 35 | } 36 | 37 | func set(lineSpacing: CGFloat) 38 | { 39 | let style = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle 40 | style.lineSpacing = lineSpacing 41 | addAttribute(NSAttributedStringKey.paragraphStyle.rawValue, value: style) 42 | } 43 | 44 | func addAttribute(_ name: String, value: Any) 45 | { 46 | addAttribute(NSAttributedStringKey(rawValue: name), value: value, range: NSMakeRange(0, length)) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VeXplore 2 | Third-party app for V2EX. Developed using Swift. 3 | 4 | ## Download 5 | [VeXplore (Charged)](https://itunes.apple.com/us/app/vexplore/id1119508407?ls=1&mt=8) 6 | [VeXplore Lite (Free)](https://itunes.apple.com/us/app/vexplore-free/id1191058321?ls=1&mt=8) 7 | 8 | ## Screenshots 9 | ![](http://p1.bpimg.com/567571/46253251e99f2b6d.png) | ![](http://p1.bpimg.com/567571/b70de1d3f2660b2b.png) 10 | --- 11 | ![](http://p1.bpimg.com/567571/8b4c5644a4e30f21.png) | ![](http://p1.bpimg.com/567571/1db224ebfd16a68b.png) 12 | --- 13 | ![](http://p1.bpimg.com/567571/a247578921820368.png) | ![](http://p1.bpimg.com/567571/2ec477b8e205c3e3.png) 14 | --- 15 | ![](http://p1.bpimg.com/567571/16f9dc7f6ec050a5.png) | ![](http://p1.bpimg.com/567571/0876ec2ec1db7c87.png) 16 | --- 17 | ![](http://p1.bpimg.com/567571/39ec77817015a3b0.png) | ![](http://p1.bpimg.com/567571/a22f05e34804064b.png) 18 | --- 19 | ![](http://p1.bpimg.com/567571/93ee1b4376923398.png) | ![](http://p1.bpimg.com/567571/6e95e878e76da972.png) 20 | --- 21 | ![](http://p1.bpimg.com/567571/6f068840d790b56f.png) | ![](http://p1.bpimg.com/567571/ee8ecb05ca2a50d2.png) 22 | --- 23 | ![](http://p1.bpimg.com/567571/6ddf6a3cf8de690a.png) | ![](http://p1.bpimg.com/567571/03b598bdd3659432.png) 24 | --- 25 | ![](http://p1.bpimg.com/567571/689df527a2da7f2a.png) | ![](http://p1.bpimg.com/567571/bf6a69b84dc100dc.png) 26 | --- 27 | ![](http://p1.bpimg.com/567571/411db1d7e2e6fb2e.png) | ![](http://p1.bpimg.com/567571/e50eee5633c2cb2f.png) 28 | --- 29 | ![](http://i1.piimg.com/567571/b7f825e636e5851b.png) | ![](http://p1.bpimg.com/567571/a3cc8681fdf13454.png) 30 | --- 31 | ![](http://p1.bpimg.com/567571/4826ca414cd3dd12.png) | ![](http://p1.bpimg.com/567571/4fea7b367df01ba0.png) 32 | 33 | ## Build Requirements 34 | * Xcode 8.3+ 35 | * iOS 9.0+ 36 | * Open "VeXplore.xcodeproj" 37 | * Run 38 | 39 | ## Contact 40 | Any questions about this project, feel free to contact me via wmywbyt.cj@gmail.com. 41 | 42 | 43 | -------------------------------------------------------------------------------- /VeXplore/AvatarImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AvatarImageView.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | class AvatarImageView: UIImageView 11 | { 12 | private var imageDownloadId: ImageDownloadId? 13 | 14 | func avatarImage(withURL url: URL) 15 | { 16 | setImage(withURL: url, placeholderImage: R.Image.AvatarPlaceholder, imageProcessing: { (image) -> UIImage in 17 | return image.roundCornerImage() 18 | }) 19 | } 20 | 21 | func cancelImageDownloadTaskIfNeed() 22 | { 23 | guard let imageDownloadId = imageDownloadId else { 24 | return 25 | } 26 | ImageDownloader.default.cancelImageDownloadTask(for: imageDownloadId) 27 | self.imageDownloadId = nil 28 | } 29 | 30 | private func setImage(withURL url: URL, placeholderImage: UIImage?, imageProcessing:((_ image: UIImage) -> UIImage)?) 31 | { 32 | image = placeholderImage 33 | ImageCache.default.retrieveImage(forKey: url.cacheKey) { image in 34 | if image != nil 35 | { 36 | dispatch_async_safely_to_main_queue { 37 | self.image = image 38 | } 39 | } 40 | else 41 | { 42 | self.cancelImageDownloadTaskIfNeed() 43 | self.imageDownloadId = ImageDownloader.default.downloadImage(with: url, completionHandler: { (image, originalData, error) in 44 | if let image = image, let roundedImage = imageProcessing?(image) 45 | { 46 | // cache image 47 | ImageCache.default.cache(image: roundedImage, forKey: url.cacheKey) 48 | dispatch_async_safely_to_main_queue { 49 | self.image = roundedImage 50 | } 51 | } 52 | }) 53 | } 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /VeXplore/baseStyle.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-weight: 500; 3 | line-height: 1.4; 4 | margin: 5px 0px 15px 0px; 5 | padding: 0px; 6 | } 7 | 8 | h2 { 9 | font-weight: 500; 10 | line-height: 1.2; 11 | margin: 20px 0px 20px 0px; 12 | padding: 0px 0px 8px 0px; 13 | border-bottom: 1px solid H2_BORDER_BOTTOM_COLOR_PLACEHOLDER; 14 | } 15 | 16 | h3 { 17 | font-weight: 500; 18 | line-height: 100%; 19 | margin: 5px 0px 20px 0px; 20 | padding: 0px 0px 8px 0px; 21 | } 22 | 23 | hr { 24 | border: none; 25 | height: 1px; 26 | margin-bottom: 1em; 27 | } 28 | 29 | pre { 30 | letter-spacing: 0.015em; 31 | line-height: 120%; 32 | padding: 0.5em; 33 | margin: 0px; 34 | white-space: pre; 35 | overflow-x: auto; 36 | overflow-y: auto; 37 | } 38 | 39 | pre a { 40 | color: inherit; 41 | text-decoration: underline; 42 | } 43 | 44 | code { 45 | padding: 1px 2px 1px 2px; 46 | border-radius: 2px; 47 | } 48 | 49 | 50 | ul { 51 | list-style: square; 52 | margin: 1em 0px 1em 1em; 53 | padding: 0px; 54 | } 55 | 56 | ul li, ol li { 57 | padding: 0px; 58 | margin: 0px; 59 | } 60 | 61 | ol { 62 | margin: 1em 0px 0em 2em; 63 | padding: 0px; 64 | } 65 | 66 | img { 67 | max-width: 100%; 68 | } 69 | 70 | .imgly { 71 | max-width: 100%; 72 | } 73 | 74 | p { 75 | margin-top: 3px; 76 | margin-bottom: 3px; 77 | } 78 | 79 | 80 | a:link, a:visited, a:active { 81 | text-decoration: none; 82 | word-break: break-all; 83 | color: HREF_COLOR_PLACEHOLDER; 84 | text-decoration: underline; 85 | } 86 | 87 | body { 88 | font-family: 'Helvetica', monospace; 89 | -webkit-text-size-adjust: none; 90 | line-height: 1.7; 91 | word-wrap: break-word; 92 | max-height: 20em; 93 | padding: 5px; 94 | color: BODY_COLOR_PLACEHOLDER; 95 | background-color: BODY_BACKGROUND_COLOR_PLACEHOLDER; 96 | } 97 | 98 | .subtle { 99 | padding: 5px; 100 | color: BODY_COLOR_PLACEHOLDER; 101 | background-color: SUBTITLE_BACKGROUND_COLOR_PLACEHOLDER; 102 | } 103 | 104 | .subtle .fade { 105 | color: SUBTITLE_FADE_COLOR_PLACEHOLDER; 106 | background-color: SUBTITLE_FADE_BACKGROUND_COLOR_PLACEHOLDER; 107 | } 108 | -------------------------------------------------------------------------------- /VeXplore/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | ${RELEASE_VERSION} 19 | CFBundleSignature 20 | ???? 21 | CFBundleURLTypes 22 | 23 | 24 | CFBundleTypeRole 25 | Editor 26 | CFBundleURLName 27 | in.jimmyis.vexplore 28 | CFBundleURLSchemes 29 | 30 | vexplore 31 | 32 | 33 | 34 | CFBundleVersion 35 | ${BUILD_NUMBER} 36 | LSApplicationQueriesSchemes 37 | 38 | org-appextension-feature-password-management 39 | 40 | LSRequiresIPhoneOS 41 | 42 | NSAppTransportSecurity 43 | 44 | NSAllowsArbitraryLoads 45 | 46 | 47 | NSPhotoLibraryUsageDescription 48 | 49 | UIApplicationShortcutWidget 50 | in.jimmyis.vexplore.TodayExtension 51 | UILaunchStoryboardName 52 | LaunchScreen 53 | UIRequiredDeviceCapabilities 54 | 55 | armv7 56 | 57 | UIRequiresFullScreen 58 | 59 | UISupportedInterfaceOrientations 60 | 61 | UIInterfaceOrientationPortrait 62 | 63 | UISupportedInterfaceOrientations~ipad 64 | 65 | UIInterfaceOrientationPortrait 66 | UIInterfaceOrientationPortraitUpsideDown 67 | UIInterfaceOrientationLandscapeLeft 68 | UIInterfaceOrientationLandscapeRight 69 | 70 | UIViewControllerBasedStatusBarAppearance 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /VeXplore/ProfileSectionHeaderCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileSectionHeaderCell.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | class ProfileSectionHeaderCell: BaseTableViewCell 11 | { 12 | lazy var titleLabel: UILabel = { 13 | let label = UILabel() 14 | label.translatesAutoresizingMaskIntoConstraints = false 15 | label.font = SharedR.Font.Small 16 | label.textColor = .desc 17 | label.textAlignment = .center 18 | 19 | return label 20 | }() 21 | 22 | private lazy var bottomLine: UIView = { 23 | let view = UIView() 24 | view.translatesAutoresizingMaskIntoConstraints = false 25 | view.backgroundColor = .border 26 | 27 | return view 28 | }() 29 | 30 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) 31 | { 32 | super.init(style: style, reuseIdentifier: reuseIdentifier) 33 | 34 | contentView.addSubview(titleLabel) 35 | contentView.addSubview(bottomLine) 36 | let bindings = [ 37 | "titleLabel": titleLabel, 38 | "bottomLine": bottomLine 39 | ] 40 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[titleLabel]|", metrics: nil, views: bindings)) 41 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[titleLabel]-8-|", metrics: nil, views: bindings)) 42 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[bottomLine]|", metrics: nil, views: bindings)) 43 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[bottomLine(0.5)]|", metrics: nil, views: bindings)) 44 | 45 | preservesSuperviewLayoutMargins = false 46 | layoutMargins = .zero 47 | selectionStyle = .none 48 | } 49 | 50 | required init?(coder aDecoder: NSCoder) 51 | { 52 | fatalError("init(coder:) has not been implemented") 53 | } 54 | 55 | override func prepareForReuse() 56 | { 57 | super.prepareForReuse() 58 | titleLabel.font = SharedR.Font.Small 59 | } 60 | 61 | @objc 62 | override func refreshColorScheme() 63 | { 64 | super.refreshColorScheme() 65 | titleLabel.textColor = .desc 66 | bottomLine.backgroundColor = .border 67 | contentView.backgroundColor = .subBackground 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /VeXplore/NodeModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NodeModel.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | 11 | class NodeModel: NSObject, Codable 12 | { 13 | private(set) var nodeId: String? 14 | private(set) var nodeName: String? 15 | private(set) var avatar: String? = nil 16 | 17 | //sort 18 | @objc var initialLetter: String? = nil 19 | @objc var allLetter: String? 20 | var allLetterWithoutSpace: String? 21 | 22 | private enum CodingKeys: String, CodingKey { 23 | case nodeId 24 | case nodeName 25 | case allLetter 26 | case allLetterWithoutSpace 27 | } 28 | 29 | init(json: JSON) 30 | { 31 | nodeId = json["name"].string 32 | nodeName = json["title"].string 33 | } 34 | 35 | init(rootNode: HTMLNode) 36 | { 37 | nodeName = rootNode.content 38 | if var href = rootNode["href"], let range = href.range(of: "/go/") 39 | { 40 | href.replaceSubrange(range, with: SharedR.String.Empty) 41 | nodeId = href 42 | } 43 | avatar = rootNode.xPath(".//img").first?["src"] 44 | } 45 | 46 | init(favoriteNode: HTMLNode) 47 | { 48 | nodeName = favoriteNode.xPath("./div/text()").first?.content 49 | if var href = favoriteNode["href"], let range = href.range(of: "/go/") 50 | { 51 | href.replaceSubrange(range, with: SharedR.String.Empty) 52 | nodeId = href 53 | } 54 | avatar = favoriteNode.xPath(".//img").first?["src"] 55 | } 56 | 57 | } 58 | 59 | 60 | struct NodeGroupModel: Codable 61 | { 62 | private(set) var childNodes = [NodeModel]() 63 | private(set) var groupName: String? 64 | 65 | init(rootNode: HTMLNode) 66 | { 67 | groupName = rootNode.xPath("./td[1]/span").first?.content 68 | for node in rootNode.xPath("./td[2]/a") 69 | { 70 | childNodes.append(NodeModel(rootNode: node)) 71 | } 72 | } 73 | 74 | } 75 | 76 | extension NodeGroupModel: Equatable {} 77 | 78 | func ==(lhs: NodeGroupModel, rhs: NodeGroupModel) -> Bool 79 | { 80 | guard lhs.groupName == rhs.groupName, lhs.childNodes.count == rhs.childNodes.count else { 81 | return false 82 | } 83 | 84 | for i in 0.. 0 else { 65 | return 66 | } 67 | if let startIndex = notificationString.index(notificationString.startIndex, offsetBy: range.location + 1, limitedBy: notificationString.endIndex), 68 | let endIndex = notificationString.index(notificationString.startIndex, offsetBy: range.location + range.length - 1, limitedBy: notificationString.endIndex) 69 | { 70 | let countString = notificationString[startIndex.. 0 29 | { 30 | type = URLType(rawValue: index)! 31 | switch type 32 | { 33 | case .topic: 34 | if let range = url.range(of: "/t/") 35 | { 36 | var topicId = url[range.upperBound...] 37 | if let range = topicId.range(of: "?") 38 | { 39 | topicId = topicId[.. Request? { 18 | get 19 | { 20 | lock.lock() 21 | defer { lock.unlock() } 22 | return requests[task.taskIdentifier] 23 | } 24 | set 25 | { 26 | lock.lock() 27 | defer { lock.unlock() } 28 | requests[task.taskIdentifier] = newValue 29 | } 30 | } 31 | 32 | private override init() 33 | { 34 | super.init() 35 | session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) 36 | } 37 | 38 | deinit 39 | { 40 | session.invalidateAndCancel() 41 | } 42 | 43 | func request(_ url: String, 44 | method: HTTPMethod = .get, 45 | parameters: [String: String]? = nil, 46 | headers: [String: String]? = nil) -> Request 47 | { 48 | do { 49 | let urlRequest = try URLRequest(url: url, method: method, headers: headers) 50 | let encodedUrlRequest = try urlRequest.encode(with: parameters) 51 | return request(with: encodedUrlRequest) 52 | } catch { 53 | return request(with: error) 54 | } 55 | } 56 | 57 | private func request(with urlRequest: URLRequest) -> Request 58 | { 59 | let task = session.dataTask(with: urlRequest) 60 | let request = Request(session: session, task: task) 61 | self[task] = request 62 | request.resume() 63 | return request 64 | } 65 | 66 | private func request(with error: Error) -> Request 67 | { 68 | let request = Request(session: session, task: nil, error: error) 69 | request.resume() 70 | return request 71 | } 72 | 73 | // MARK: - URLSessionDataDelegate 74 | public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) 75 | { 76 | if let request = self[task] 77 | { 78 | request.didComplete(withError: error) 79 | } 80 | self[task] = nil 81 | } 82 | 83 | // MARK: - URLSessionDataDelegate 84 | public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) 85 | { 86 | if let request = self[dataTask] 87 | { 88 | request.didReceive(data: data) 89 | } 90 | } 91 | 92 | } 93 | 94 | -------------------------------------------------------------------------------- /VeXplore/TopicListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopicListViewController.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | class TopicListViewController: SwipeTableViewController 10 | { 11 | override func viewDidLoad() 12 | { 13 | super.viewDidLoad() 14 | tableView.register(TopicCell.self, forCellReuseIdentifier: String(describing: TopicCell.self)) 15 | NotificationCenter.default.addObserver(self, selector: #selector(handleFontsizeDidChanged), name: Notification.Name.Setting.FontsizeDidChange, object: nil) 16 | } 17 | 18 | @objc 19 | func handleFontsizeDidChanged() 20 | { 21 | tableView.reloadData() 22 | } 23 | 24 | // MARK: - UITableViewDataSource 25 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 26 | { 27 | let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TopicCell.self), for: indexPath) as! TopicCell 28 | let topicItem = topicList[indexPath.row] 29 | cell.topicItemModel = topicItem 30 | cell.topicTitleLabel.text = topicItem.topicTitle 31 | cell.userNameLabel.text = topicItem.username 32 | cell.nodeNameBtn.setTitle(topicItem.nodeName, for: .normal) 33 | if let repliesNumberString = topicItem.repliesNumber, repliesNumberString.isEmpty == false 34 | { 35 | cell.repliesNumberLabel.text = repliesNumberString 36 | } 37 | if let avatar = topicItem.avatar, let url = URL(string: R.String.Https + avatar) 38 | { 39 | cell.avatarImageView.avatarImage(withURL: url) 40 | } 41 | cell.lastReplayDateAndUserLabel.text = R.String.NoRepliesNow 42 | if let lastReplyDate = topicItem.lastReplyDate 43 | { 44 | if topicItem.lastReplyUserName != nil 45 | { 46 | cell.lastReplayDateAndUserLabel.text = lastReplyDate 47 | } 48 | else 49 | { 50 | cell.lastReplayDateAndUserLabel.text = String(format: R.String.PublicDate, lastReplyDate) 51 | } 52 | } 53 | cell.delegate = self 54 | return cell 55 | } 56 | 57 | // MARK: - UITableViewDelegate 58 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 59 | { 60 | if let topicId = topicList[indexPath.row].topicId 61 | { 62 | let topicVC = TopicViewController(topicId: topicId) 63 | topicVC.ignoreHandler = { topicId -> Void in 64 | self.removeTopic(withId: topicId) 65 | } 66 | DispatchQueue.main.async(execute: { 67 | self.bouncePresent(navigationVCWith: topicVC, completion: nil) 68 | }) 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /VeXplore/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | ////////////////////////////// 9 | ////// Data Persistence ////// 10 | ////////////////////////////// 11 | 12 | func pageCacheDirPath() -> String? 13 | { 14 | if let sysCacheDirPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first 15 | { 16 | let pageCacheDirPath = (sysCacheDirPath as NSString).appendingPathComponent("pagesData") 17 | var isDir: ObjCBool = false 18 | let fileManager = FileManager.default 19 | var dirExists = fileManager.fileExists(atPath: pageCacheDirPath, isDirectory: &isDir) 20 | do { 21 | if dirExists == true && isDir.boolValue == false 22 | { 23 | try fileManager.removeItem(atPath: pageCacheDirPath) 24 | dirExists = false 25 | } 26 | if dirExists == false 27 | { 28 | try fileManager.createDirectory(atPath: pageCacheDirPath, withIntermediateDirectories: false, attributes: nil) 29 | } 30 | return pageCacheDirPath 31 | } catch let error as NSError { 32 | print(error.localizedDescription) 33 | return nil 34 | } 35 | } 36 | return nil 37 | } 38 | 39 | func cachePathString(withFilename filename: String) -> String? 40 | { 41 | var filePath: String? = nil 42 | if let dirPath = pageCacheDirPath() 43 | { 44 | filePath = (dirPath as NSString).appendingPathComponent(filename) 45 | } 46 | return filePath 47 | } 48 | 49 | func clearPageCache() 50 | { 51 | if let dirPath = pageCacheDirPath() 52 | { 53 | do { 54 | try FileManager.default.removeItem(atPath: dirPath) 55 | } catch let error as NSError { 56 | print(error.localizedDescription) 57 | } 58 | } 59 | } 60 | 61 | 62 | //////////////////// 63 | ////// Others ////// 64 | //////////////////// 65 | 66 | func statusBarHeight() -> CGFloat 67 | { 68 | let statusBarFrame = UIApplication.shared.statusBarFrame 69 | return min(statusBarFrame.width, statusBarFrame.height) 70 | } 71 | 72 | func versionBuild() -> String 73 | { 74 | let version = currentVersion() 75 | let build = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String 76 | return "Version \(version)(\(build))" 77 | } 78 | 79 | func currentVersion() -> String 80 | { 81 | return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String 82 | } 83 | 84 | // remove user defaults, for test 85 | func removeUserDefaults() 86 | { 87 | let appDomain = Bundle.main.bundleIdentifier 88 | UserDefaults.standard.removePersistentDomain(forName: appDomain!) 89 | } 90 | 91 | let isPad = UIDevice.current.userInterfaceIdiom == .pad 92 | 93 | -------------------------------------------------------------------------------- /VeXplore/BaseCenterLoadingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCenterLoadingViewController.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | class BaseCenterLoadingViewController: SwipeTransitionViewController, SquareLoadingViewDelegate, UITableViewDelegate, UITableViewDataSource 10 | { 11 | lazy var tableView: UITableView = { 12 | let tableView = UITableView(frame: self.view.bounds, style: .plain) 13 | tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 14 | tableView.dataSource = self 15 | tableView.delegate = self 16 | tableView.separatorStyle = .none 17 | tableView.isHidden = true 18 | tableView.backgroundColor = .background 19 | 20 | return tableView 21 | }() 22 | 23 | lazy var centerLoadingView: SquaresLoadingView = { 24 | let view = SquaresLoadingView(loadingStyle: LoadingStyle.bottom) 25 | view.translatesAutoresizingMaskIntoConstraints = false 26 | view.delegate = self 27 | 28 | return view 29 | }() 30 | 31 | override func viewDidLoad() 32 | { 33 | super.viewDidLoad() 34 | 35 | view.addSubview(tableView) 36 | view.addSubview(centerLoadingView) 37 | centerLoadingView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 38 | centerLoadingView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 39 | centerLoadingView.widthAnchor.constraint(equalToConstant: R.Constant.LoadingViewHeight).isActive = true 40 | centerLoadingView.initSquaresPosition() 41 | beginLoading() 42 | } 43 | 44 | @objc 45 | override func refreshColorScheme() 46 | { 47 | super.refreshColorScheme() 48 | tableView.backgroundColor = .background 49 | } 50 | 51 | private func beginLoading() 52 | { 53 | centerLoadingView.beginLoading() 54 | loadingRequest() 55 | } 56 | 57 | // MARK: - Public 58 | func loadingRequest() 59 | { 60 | // override this method in subclass 61 | } 62 | 63 | func stopLoading(withSuccesse success: Bool, completion: CompletionTask?) 64 | { 65 | centerLoadingView.stopLoading(withSuccess: success, completion: completion) 66 | } 67 | 68 | // MARK: - SquareLoadingViewDelegate 69 | func didTriggeredReloading() 70 | { 71 | beginLoading() 72 | } 73 | 74 | // MARK: - UITableViewDataSource 75 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 76 | { 77 | // override this method in subclass 78 | return 0 79 | } 80 | 81 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 82 | { 83 | // override this method in subclass 84 | return UITableViewCell() 85 | } 86 | 87 | // MARK: - UITableViewDelegate 88 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 89 | { 90 | // override this method in subclass 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /VeXplore/MyFollowingCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyFollowingCell.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | class MyFollowingCell: BaseTableViewCell 11 | { 12 | lazy var avatarImageView: AvatarImageView = { 13 | let view = AvatarImageView() 14 | view.translatesAutoresizingMaskIntoConstraints = false 15 | view.contentMode = .scaleAspectFit 16 | view.tintColor = .body 17 | 18 | return view 19 | }() 20 | 21 | lazy var contentLabel: UILabel = { 22 | let label = UILabel() 23 | label.translatesAutoresizingMaskIntoConstraints = false 24 | label.font = SharedR.Font.Medium 25 | label.textColor = .desc 26 | 27 | return label 28 | }() 29 | 30 | private lazy var bottomLine: UIView = { 31 | let view = UIView() 32 | view.translatesAutoresizingMaskIntoConstraints = false 33 | view.backgroundColor = .border 34 | 35 | return view 36 | }() 37 | 38 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) 39 | { 40 | super.init(style: style, reuseIdentifier: reuseIdentifier) 41 | 42 | contentView.addSubview(avatarImageView) 43 | contentView.addSubview(contentLabel) 44 | contentView.addSubview(bottomLine) 45 | let bindings = [ 46 | "avatarImageView": avatarImageView, 47 | "contentLabel": contentLabel, 48 | "bottomLine": bottomLine 49 | ] 50 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-8-[avatarImageView(40@999)]-8-[contentLabel]-12-|", metrics: nil, views: bindings)) 51 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[avatarImageView]-8-[bottomLine(0.5)]|", metrics: nil, views: bindings)) 52 | contentLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true 53 | avatarImageView.heightAnchor.constraint(equalTo: avatarImageView.widthAnchor).isActive = true 54 | bottomLine.leadingAnchor.constraint(equalTo: contentLabel.leadingAnchor).isActive = true 55 | bottomLine.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true 56 | 57 | preservesSuperviewLayoutMargins = false 58 | layoutMargins = .zero 59 | selectionStyle = .none 60 | } 61 | 62 | required init?(coder aDecoder: NSCoder) 63 | { 64 | fatalError("init(coder:) has not been implemented") 65 | } 66 | 67 | override func prepareForReuse() 68 | { 69 | avatarImageView.cancelImageDownloadTaskIfNeed() 70 | super.prepareForReuse() 71 | avatarImageView.image = nil 72 | contentLabel.font = SharedR.Font.Medium 73 | } 74 | 75 | @objc 76 | override func refreshColorScheme() 77 | { 78 | super.refreshColorScheme() 79 | avatarImageView.tintColor = .body 80 | contentLabel.textColor = .desc 81 | bottomLine.backgroundColor = .border 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /VeXplore/CSSStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CSSStyle.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | class CSSStyle 11 | { 12 | class var `default`: String { 13 | let BASE_CSS = try! String(contentsOfFile: Bundle.main.path(forResource: "baseStyle", ofType: "css")!, encoding: .utf8) 14 | let FONT_CSS = try! String(contentsOfFile: Bundle.main.path(forResource: "font", ofType: "css")!, encoding: .utf8) 15 | 16 | let COLOR_STYLE_ARRAY = [ 17 | ColorStyle(colorName: "H2_COLOR_PLACEHOLDER", colorString: UIColor.border.toHexString()), 18 | ColorStyle(colorName: "HREF_COLOR_PLACEHOLDER", colorString: UIColor.href.toHexString()), 19 | ColorStyle(colorName: "BODY_COLOR_PLACEHOLDER", colorString: UIColor.body.toHexString()), 20 | ColorStyle(colorName: "BODY_BACKGROUND_COLOR_PLACEHOLDER", colorString: UIColor.background.toHexString()), 21 | ColorStyle(colorName: "SUBTITLE_BACKGROUND_COLOR_PLACEHOLDER", colorString: UIColor.refBackground.toHexString()), 22 | ColorStyle(colorName: "SUBTITLE_FADE_COLOR_PLACEHOLDER", colorString: UIColor.note.toHexString()), 23 | ColorStyle(colorName: "SUBTITLE_FADE_BACKGROUND_COLOR_PLACEHOLDER", colorString: UIColor.subBackground.toHexString()) 24 | ] 25 | var baseCss = BASE_CSS 26 | COLOR_STYLE_ARRAY.forEach { colorStyle in 27 | baseCss = baseCss.replacingOccurrences(of: colorStyle.colorName, with: colorStyle.colorString) 28 | } 29 | 30 | let FONT_SIZE_STYLE_ARRAY = [ 31 | FontSizeStyle(labelName:"", defaultFontSize: Int(UIFont.preferredFont(forTextStyle: UIFontTextStyle.title1).pointSize)), 32 | FontSizeStyle(labelName:"", defaultFontSize: Int(UIFont.preferredFont(forTextStyle: UIFontTextStyle.title2).pointSize)), 33 | FontSizeStyle(labelName:"", defaultFontSize: Int(UIFont.preferredFont(forTextStyle: UIFontTextStyle.title3).pointSize)), 34 | FontSizeStyle(labelName:"", defaultFontSize: Int(UIFont.preferredFont(forTextStyle: UIFontTextStyle.subheadline).pointSize)), 35 | FontSizeStyle(labelName:"", defaultFontSize: Int(SharedR.Font.Medium.pointSize)), // 正文 36 | FontSizeStyle(labelName:"", defaultFontSize: Int(SharedR.Font.Small.pointSize)), // 附言正文 37 | FontSizeStyle(labelName:"", defaultFontSize: Int(SharedR.Font.ExtraSmall.pointSize)) // 附言标题 38 | ] 39 | var fontCss = FONT_CSS 40 | FONT_SIZE_STYLE_ARRAY.forEach { fontSizeStyle in 41 | fontCss = fontCss.replacingOccurrences(of: fontSizeStyle.labelName, with: String(fontSizeStyle.defaultFontSize)) 42 | } 43 | 44 | return baseCss + fontCss 45 | } 46 | 47 | private struct FontSizeStyle 48 | { 49 | let labelName: String 50 | let defaultFontSize: Int 51 | } 52 | 53 | private struct ColorStyle 54 | { 55 | let colorName: String 56 | let colorString: String 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /VeXplore/TopicDetailModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopicDetailModel.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | class TopicDetailModel: NSObject 11 | { 12 | private(set) var avatar: String? 13 | private(set) var nodeName: String? 14 | private(set) var nodeId: String? 15 | private(set) var username: String? 16 | private(set) var topicTitle: String? 17 | private(set) var topicContent: String! 18 | private(set) var date: String? 19 | private(set) var favoriteNum: String? 20 | private(set) var token: String? 21 | private(set) var commentTotalPages = 1 22 | private(set) var isFavorite = false 23 | private(set) var topicCommentTotalCount: String? 24 | private(set) var reportUrl: String? 25 | private(set) var topicId: String? 26 | 27 | init(id: String, rootNode: HTMLNode) 28 | { 29 | self.topicId = id 30 | if let node = rootNode.xPath("./div[1]/a[2]").first 31 | { 32 | nodeName = node.content 33 | if var href = node["href"], 34 | let range = href.range(of: "/go/") 35 | { 36 | href.replaceSubrange(range, with: SharedR.String.Empty) 37 | nodeId = href 38 | } 39 | } 40 | avatar = rootNode.xPath(".//img[@class='avatar']").first?["src"] 41 | username = rootNode.xPath(".//small[contains(text(),'By')]/a").first?.content 42 | topicTitle = rootNode.xPath(".//h1").first?.content 43 | topicContent = rootNode.xPath("./div[@class='cell']/div").first?.rawContent ?? SharedR.String.Empty 44 | 45 | // Append 46 | let appendNodes = rootNode.xPath("./div[@class='subtle']") 47 | for node in appendNodes 48 | { 49 | if let content = node.rawContent 50 | { 51 | topicContent = topicContent + content 52 | } 53 | } 54 | date = rootNode.xPath("./div[1]/small/text()").last?.content 55 | favoriteNum = rootNode.xPath(".//div[@class='inner']/div/span").first?.content?.stringByRemovingNewLinesAndWhitespace() 56 | if rootNode.xPath(".//a[text()='取消收藏']").count > 0 57 | { 58 | isFavorite = true 59 | } 60 | if let token = rootNode.xPath(".//a[@class='op'][1][text()='加入收藏' or text()='取消收藏']").first?["href"] 61 | { 62 | let array = token.components(separatedBy: "?t=") 63 | if array.count == 2 64 | { 65 | self.token = array[1] 66 | } 67 | } 68 | if let topicCommentTotalCountText = rootNode.xPath("//div[@class='box']/div[@class='cell']/span").first?.content, 69 | let range = topicCommentTotalCountText.range(of: " 回复") 70 | { 71 | topicCommentTotalCount = String(topicCommentTotalCountText[.. JSON { 81 | var json = JSON.null 82 | if type == .dictionary, let value = rawDictionary[key] 83 | { 84 | json = JSON(object: value) 85 | } 86 | return json 87 | } 88 | 89 | } 90 | 91 | // using 'for...in' to access 92 | extension JSON: Swift.Sequence 93 | { 94 | public func makeIterator() -> JSONIterator 95 | { 96 | return JSON.Iterator(json: self) 97 | } 98 | 99 | } 100 | 101 | public struct JSONIterator: IteratorProtocol 102 | { 103 | private var type: Type 104 | private var arrayIterator: IndexingIterator<[Any]>? 105 | private var arrayIndex = 0 106 | 107 | init(json: JSON) 108 | { 109 | type = json.type 110 | if type == .array 111 | { 112 | arrayIterator = json.rawArray.makeIterator() 113 | } 114 | } 115 | 116 | mutating public func next() -> (String, JSON)? 117 | { 118 | if type == .array, let next = arrayIterator?.next() 119 | { 120 | let i = arrayIndex 121 | arrayIndex += 1 122 | return (String(i), JSON(object: next)) 123 | } 124 | return nil 125 | } 126 | 127 | } 128 | 129 | -------------------------------------------------------------------------------- /SharedKit/NetworkingUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkingUtils.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum HTTPMethod: String 11 | { 12 | case get = "GET" 13 | case post = "POST" 14 | } 15 | 16 | 17 | public extension String 18 | { 19 | func toURL() throws -> URL 20 | { 21 | guard let url = URL(string: self) else { 22 | throw NSError() 23 | } 24 | return url 25 | } 26 | } 27 | 28 | 29 | extension URLRequest 30 | { 31 | init(url: String, method: HTTPMethod, headers: [String: String]? = nil) throws 32 | { 33 | let url = try url.toURL() 34 | self.init(url: url) 35 | httpMethod = method.rawValue 36 | if let headers = headers 37 | { 38 | for (field, value) in headers 39 | { 40 | setValue(value, forHTTPHeaderField: field) 41 | } 42 | } 43 | } 44 | 45 | func encode(with parameters: [String: String]?) throws -> URLRequest 46 | { 47 | guard let parameters = parameters, parameters.isEmpty == false else { 48 | return self 49 | } 50 | 51 | var urlRequest = self 52 | if urlRequest.httpMethod == "GET" 53 | { 54 | guard let url = urlRequest.url else { 55 | throw NSError() 56 | } 57 | 58 | if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) 59 | { 60 | let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters) 61 | urlComponents.percentEncodedQuery = percentEncodedQuery 62 | urlRequest.url = urlComponents.url 63 | } 64 | } 65 | else 66 | { 67 | if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil 68 | { 69 | urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type") 70 | } 71 | urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false) 72 | } 73 | return urlRequest 74 | } 75 | 76 | private func query(_ parameters: [String: String]) -> String 77 | { 78 | let escapedParameters = parameters.map { (escape($0.key), escape($0.value)) } 79 | let result = escapedParameters.map { "\($0)=\($1)" }.joined(separator: "&") 80 | return result 81 | } 82 | 83 | private func escape(_ string: String) -> String 84 | { 85 | var allowedCharacterSet = CharacterSet.urlQueryAllowed 86 | allowedCharacterSet.remove(charactersIn: "!*'();:@&=+$,#[]") // remove reserved characters 87 | return string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string 88 | } 89 | 90 | } 91 | 92 | 93 | public struct Networking 94 | { 95 | @discardableResult 96 | public static func request(_ url: String, 97 | method: HTTPMethod = .get, 98 | parameters: [String: String]? = nil, 99 | headers: [String: String]? = nil) -> Request 100 | { 101 | return SessionManager.shared.request(url, method: method, parameters: parameters, headers: headers) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /VeXplore/TopicSearchResultCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopicSearchResultCell.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | class TopicSearchResultCell: BaseTableViewCell 11 | { 12 | lazy var cellTitleLabel: UILabel = { 13 | let label = UILabel() 14 | label.translatesAutoresizingMaskIntoConstraints = false 15 | label.font = SharedR.Font.Small 16 | label.textColor = .desc 17 | label.textAlignment = .center 18 | 19 | return label 20 | }() 21 | 22 | lazy var topicTitleLabel: UILabel = { 23 | let label = UILabel() 24 | label.translatesAutoresizingMaskIntoConstraints = false 25 | label.font = SharedR.Font.Medium 26 | label.numberOfLines = 0 27 | label.textColor = .body 28 | 29 | return label 30 | }() 31 | 32 | lazy var separatorLine: UIView = { 33 | let view = UIView() 34 | view.translatesAutoresizingMaskIntoConstraints = false 35 | view.backgroundColor = .border 36 | 37 | return view 38 | }() 39 | 40 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) 41 | { 42 | super.init(style: style, reuseIdentifier: reuseIdentifier) 43 | self.commonInit() 44 | } 45 | 46 | private func commonInit() 47 | { 48 | contentView.addSubview(topicTitleLabel) 49 | contentView.addSubview(cellTitleLabel) 50 | contentView.addSubview(separatorLine) 51 | let bindings = [ 52 | "topicTitleLabel": topicTitleLabel, 53 | "separatorLine": separatorLine, 54 | "cellTitleLabel": cellTitleLabel 55 | ] 56 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-8-[cellTitleLabel(40)]-8-[topicTitleLabel]-8-|", metrics: nil, views: bindings)) 57 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-12-[topicTitleLabel]-12-|", metrics: nil, views: bindings)) 58 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[separatorLine(0.5)]|", metrics: nil, views: bindings)) 59 | cellTitleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true 60 | separatorLine.leadingAnchor.constraint(equalTo: topicTitleLabel.leadingAnchor).isActive = true 61 | separatorLine.trailingAnchor.constraint(equalTo: topicTitleLabel.trailingAnchor).isActive = true 62 | 63 | preservesSuperviewLayoutMargins = false 64 | layoutMargins = .zero 65 | selectionStyle = .none 66 | } 67 | 68 | required init?(coder aDecoder: NSCoder) 69 | { 70 | fatalError("init(coder:) has not been implemented") 71 | } 72 | 73 | override func prepareForReuse() 74 | { 75 | super.prepareForReuse() 76 | cellTitleLabel.font = SharedR.Font.Small 77 | topicTitleLabel.font = SharedR.Font.Medium 78 | } 79 | 80 | override func layoutSubviews() 81 | { 82 | super.layoutSubviews() 83 | contentView.layoutSubviews() 84 | topicTitleLabel.preferredMaxLayoutWidth = topicTitleLabel.bounds.width 85 | } 86 | 87 | @objc 88 | override func refreshColorScheme() 89 | { 90 | super.refreshColorScheme() 91 | cellTitleLabel.textColor = .desc 92 | topicTitleLabel.textColor = .body 93 | separatorLine.backgroundColor = .border 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /VeXplore/URLAnalyzer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLAnalyzer.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SafariServices 9 | import MessageUI 10 | import SharedKit 11 | 12 | struct URLAnalyzer 13 | { 14 | @discardableResult 15 | static func Analyze(url:String, handleViewController: SwipeTableViewController) -> Bool 16 | { 17 | let result = URLAnalysisResult(url: url) 18 | dispatch_async_safely_to_main_queue { 19 | switch result.type 20 | { 21 | case .url: 22 | if let urlString = result.value, let url = URL(string: urlString) 23 | { 24 | // open in safari 25 | let safariVC = SFSafariViewController(url: url, entersReaderIfAvailable: true) 26 | handleViewController.present(safariVC, animated: true, completion: nil) 27 | } 28 | case .member: 29 | if let username = result.value 30 | { 31 | let profileVC = OtherProfileViewController() 32 | profileVC.username = username 33 | handleViewController.bouncePresent(viewController: profileVC, completion: nil) 34 | } 35 | case .topic: 36 | if let topicId = result.value 37 | { 38 | let topicVC = TopicViewController(topicId: topicId) 39 | handleViewController.bouncePresent(navigationVCWith: topicVC, completion: nil) 40 | } 41 | case .node: 42 | if let nodeId = result.value 43 | { 44 | let nodeTopicListVC = NodeTopicListViewController() 45 | nodeTopicListVC.nodeId = nodeId 46 | handleViewController.bouncePresent(navigationVCWith: nodeTopicListVC, completion: { 47 | nodeTopicListVC.startLoading() 48 | }) 49 | } 50 | case .email: 51 | if let recipient = result.value 52 | { 53 | if MFMailComposeViewController.canSendMail() 54 | { 55 | let mailCompose = MFMailComposeViewController() 56 | mailCompose.mailComposeDelegate = handleViewController 57 | mailCompose.setToRecipients([recipient]) 58 | dispatch_async_safely_to_main_queue { 59 | handleViewController.present(mailCompose, animated: true, completion: nil) 60 | } 61 | } 62 | else 63 | { 64 | let alertController = UIAlertController(title: nil, message: R.String.EmailNotSetAlert, preferredStyle: .alert) 65 | let copyAction = UIAlertAction(title: R.String.Confirm, style: .default) { (action) in 66 | UIPasteboard.general.string = recipient 67 | } 68 | alertController.addAction(copyAction) 69 | dispatch_async_safely_to_main_queue { 70 | handleViewController.present(alertController, animated: true, completion: nil) 71 | } 72 | } 73 | } 74 | case .undefined: 75 | break 76 | } 77 | } 78 | if result.type == .undefined 79 | { 80 | return false 81 | } 82 | return true 83 | } 84 | 85 | } 86 | 87 | -------------------------------------------------------------------------------- /VeXplore/PersonalInfoCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersonalInfoCell.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | class PersonalInfoCell: BaseTableViewCell 11 | { 12 | lazy var iconImageView: UIImageView = { 13 | let view = UIImageView() 14 | view.translatesAutoresizingMaskIntoConstraints = false 15 | view.contentMode = .scaleAspectFit 16 | 17 | return view 18 | }() 19 | 20 | lazy var contentLabel: UILabel = { 21 | let label = UILabel() 22 | label.translatesAutoresizingMaskIntoConstraints = false 23 | label.font = SharedR.Font.Medium 24 | label.textColor = .desc 25 | 26 | return label 27 | }() 28 | 29 | private lazy var line: UIView = { 30 | let view = UIView() 31 | view.translatesAutoresizingMaskIntoConstraints = false 32 | view.backgroundColor = .border 33 | 34 | return view 35 | }() 36 | 37 | lazy var longLine: UIView = { 38 | let view = UIView() 39 | view.translatesAutoresizingMaskIntoConstraints = false 40 | view.backgroundColor = .border 41 | view.isHidden = true 42 | 43 | return view 44 | }() 45 | 46 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) 47 | { 48 | super.init(style: style, reuseIdentifier: reuseIdentifier) 49 | 50 | contentView.addSubview(iconImageView) 51 | contentView.addSubview(contentLabel) 52 | contentView.addSubview(line) 53 | contentView.addSubview(longLine) 54 | let bindings = [ 55 | "iconImageView": iconImageView, 56 | "contentLabel": contentLabel, 57 | "line": line, 58 | "longLine": longLine 59 | ] 60 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-12-[iconImageView(20)]-12-[contentLabel]-12-|", metrics: nil, views: bindings)) 61 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[longLine]|", metrics: nil, views: bindings)) 62 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[contentLabel]-8-|", metrics: nil, views: bindings)) 63 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[line(0.5)]|", metrics: nil, views: bindings)) 64 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[longLine(0.5)]|", metrics: nil, views: bindings)) 65 | iconImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true 66 | iconImageView.heightAnchor.constraint(equalTo: iconImageView.widthAnchor).isActive = true 67 | line.leadingAnchor.constraint(equalTo: contentLabel.leadingAnchor).isActive = true 68 | line.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true 69 | 70 | preservesSuperviewLayoutMargins = false 71 | layoutMargins = .zero 72 | selectionStyle = .none 73 | } 74 | 75 | required init?(coder aDecoder: NSCoder) 76 | { 77 | fatalError("init(coder:) has not been implemented") 78 | } 79 | 80 | override func prepareForReuse() 81 | { 82 | super.prepareForReuse() 83 | line.isHidden = false 84 | longLine.isHidden = true 85 | contentLabel.font = SharedR.Font.Medium 86 | } 87 | 88 | @objc 89 | override func refreshColorScheme() 90 | { 91 | super.refreshColorScheme() 92 | contentLabel.textColor = .desc 93 | line.backgroundColor = .border 94 | longLine.backgroundColor = .border 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /VeXplore/TextLine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextLine.swift 3 | // YYText 4 | // 5 | // Copyright © 2016 ibireme. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | 10 | class TextLine 11 | { 12 | private(set) var ctLine: CTLine! 13 | private(set) var bounds: CGRect! 14 | private(set) var ascent: CGFloat = 0.0 15 | private(set) var descent: CGFloat = 0.0 16 | private(set) var leading: CGFloat = 0.0 17 | private(set) var lineWidth: CGFloat = 0.0 18 | private(set) var attachments = [UIImageView]() 19 | private(set) var attachmentRects = [CGRect]() 20 | private var firstGlyphPosX: CGFloat = 0 21 | 22 | // baseline position 23 | var position: CGPoint! { 24 | didSet 25 | { 26 | reloadBounds() 27 | } 28 | } 29 | 30 | var size: CGSize { 31 | return bounds.size 32 | } 33 | 34 | var width: CGFloat { 35 | return bounds.width 36 | } 37 | 38 | var height: CGFloat { 39 | return bounds.height 40 | } 41 | 42 | var top: CGFloat { 43 | return bounds.minY 44 | } 45 | 46 | var bottom: CGFloat { 47 | return bounds.maxY 48 | } 49 | 50 | var left: CGFloat { 51 | return bounds.minX 52 | } 53 | 54 | var right: CGFloat { 55 | return bounds.maxX 56 | } 57 | 58 | required init(ctLine: CTLine, position: CGPoint) 59 | { 60 | self.ctLine = ctLine 61 | self.position = position 62 | commonInit() 63 | } 64 | 65 | private func commonInit() 66 | { 67 | lineWidth = CGFloat(CTLineGetTypographicBounds(ctLine, &ascent, &descent, &leading)); 68 | if CTLineGetGlyphCount(ctLine) > 0 69 | { 70 | let runs = CTLineGetGlyphRuns(ctLine) as! [CTRun] 71 | let run = runs[0] 72 | var pos: CGPoint = .zero 73 | CTRunGetPositions(run, CFRangeMake(0, 1), &pos) 74 | firstGlyphPosX = pos.x 75 | reloadBounds() 76 | } 77 | } 78 | 79 | private func reloadBounds() 80 | { 81 | bounds = CGRect(x: position.x + firstGlyphPosX, y: position.y - ascent, width: lineWidth, height: ascent + descent) 82 | let runs = CTLineGetGlyphRuns(ctLine) 83 | let runsCount = CFArrayGetCount(runs) 84 | guard ctLine != nil && runsCount > 0 else { 85 | return 86 | } 87 | 88 | attachments.removeAll() 89 | attachmentRects.removeAll() 90 | for i in 0.. 0 else { 94 | continue 95 | } 96 | 97 | let attrs = CTRunGetAttributes(run) as NSDictionary 98 | if let attachment = attrs[AttachmentAttributeName] as? UIImageView 99 | { 100 | var runPos: CGPoint = .zero 101 | CTRunGetPositions(run, CFRangeMake(0, 1), &runPos) 102 | var ascent: CGFloat = 0.0 103 | var descent: CGFloat = 0.0 104 | let runWidth: CGFloat = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, nil)) 105 | runPos.x = position.x + runPos.x 106 | runPos.y = position.y - runPos.y 107 | let runTypoBounds = CGRect(x: runPos.x, y: runPos.y - ascent, width: runWidth, height: ascent + descent) 108 | attachments.append(attachment) 109 | attachmentRects.append(runTypoBounds) 110 | } 111 | } 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /VeXplore/MyFollowingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyFollowingsViewController.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | class MyFollowingsViewController: BaseCenterLoadingViewController 10 | { 11 | private var followingList = [(String, String)]() // (url, username) 12 | override func viewDidLoad() 13 | { 14 | super.viewDidLoad() 15 | title = R.String.MyFollowings 16 | 17 | tableView.register(MyFollowingCell.self, forCellReuseIdentifier: String(describing: MyFollowingCell.self)) 18 | tableView.isHidden = false 19 | tableView.estimatedRowHeight = R.Constant.EstimatedRowHeight 20 | tableView.rowHeight = UITableViewAutomaticDimension 21 | let closeBtn = UIBarButtonItem(image: R.Image.Close, style: .plain, target: self, action: #selector(closeBtnTapped)) 22 | navigationItem.leftBarButtonItem = closeBtn 23 | } 24 | 25 | override func loadingRequest() 26 | { 27 | V2Request.Profile.getFollowings(completion: { [weak self] (response) in 28 | guard let weakSelf = self else { 29 | return 30 | } 31 | 32 | weakSelf.stopLoading(withSuccesse: response.success, completion: { (success) in 33 | if response.message.count > 0 && response.message[0] == R.String.NotAuthorizedError 34 | { 35 | User.shared.logout() 36 | } 37 | else if success, let value = response.value 38 | { 39 | weakSelf.followingList = value 40 | weakSelf.tableView.reloadData() 41 | weakSelf.centerLoadingView.removeFromSuperview() 42 | } 43 | }) 44 | }) 45 | } 46 | 47 | @objc 48 | private func closeBtnTapped() 49 | { 50 | dismiss(animated: true, completion: nil) 51 | } 52 | 53 | // MARK: - UITableViewDataSource 54 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 55 | { 56 | return followingList.count 57 | } 58 | 59 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 60 | { 61 | let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MyFollowingCell.self), for: indexPath) as! MyFollowingCell 62 | let followingMember = followingList[indexPath.row] 63 | cell.contentLabel.text = followingMember.1 64 | if let url = URL(string: R.String.Https + followingMember.0) 65 | { 66 | cell.avatarImageView.avatarImage(withURL: url) 67 | } 68 | return cell 69 | } 70 | 71 | // MARK: - UITableViewDelegate 72 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 73 | { 74 | let username = followingList[indexPath.row].1 75 | let profileVC = OtherProfileViewController() 76 | profileVC.username = username 77 | profileVC.unfollowingHandler = { [weak self] username -> Void in 78 | guard let weakSelf = self else{ 79 | return 80 | } 81 | weakSelf.removeUser(withUsername: username) 82 | } 83 | DispatchQueue.main.async(execute: { 84 | self.bouncePresent(viewController: profileVC, completion: nil) 85 | }) 86 | } 87 | 88 | func removeUser(withUsername username: String) 89 | { 90 | if let index = followingList.index(where: {$0.1 == username}) 91 | { 92 | followingList.remove(at: index) 93 | tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic) 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /VeXplore/FavoriteNodesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavoriteNodesViewController.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | class FavoriteNodesViewController: BaseCenterLoadingViewController 10 | { 11 | private var nodesList = [NodeModel]() 12 | var nodeToDelete: String? 13 | 14 | override func viewDidLoad() 15 | { 16 | super.viewDidLoad() 17 | title = R.String.MyFavoriteNodes 18 | 19 | tableView.register(MyFollowingCell.self, forCellReuseIdentifier: String(describing: MyFollowingCell.self)) 20 | tableView.isHidden = false 21 | tableView.estimatedRowHeight = R.Constant.EstimatedRowHeight 22 | tableView.rowHeight = UITableViewAutomaticDimension 23 | let closeBtn = UIBarButtonItem(image: R.Image.Close, style: .plain, target: self, action: #selector(closeBtnTapped)) 24 | navigationItem.leftBarButtonItem = closeBtn 25 | } 26 | 27 | override func viewDidAppear(_ animated: Bool) 28 | { 29 | super.viewDidAppear(animated) 30 | if let nodeToDelete = nodeToDelete, let index = nodesList.index(where: {$0.nodeId == nodeToDelete}) 31 | { 32 | nodesList.remove(at: index) 33 | tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic) 34 | } 35 | } 36 | 37 | @objc 38 | private func closeBtnTapped() 39 | { 40 | dismiss(animated: true, completion: nil) 41 | } 42 | 43 | override func loadingRequest() 44 | { 45 | V2Request.Profile.getFavoriteNodes(completion: { [weak self] (response) in 46 | guard let weakSelf = self else { 47 | return 48 | } 49 | 50 | weakSelf.stopLoading(withSuccesse: response.success, completion: { (success) in 51 | if response.message.count > 0 && response.message[0] == R.String.NotAuthorizedError 52 | { 53 | User.shared.logout() 54 | } 55 | else if success, let value = response.value 56 | { 57 | weakSelf.nodesList = value 58 | weakSelf.tableView.reloadData() 59 | weakSelf.centerLoadingView.removeFromSuperview() 60 | } 61 | }) 62 | }) 63 | } 64 | 65 | // MARK: - UITableViewDataSource 66 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 67 | { 68 | return nodesList.count 69 | } 70 | 71 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 72 | { 73 | let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: MyFollowingCell.self), for: indexPath) as! MyFollowingCell 74 | let followingMember = nodesList[indexPath.row] 75 | cell.contentLabel.text = followingMember.nodeName 76 | if let avatar = followingMember.avatar, let url = URL(string: R.String.Https + avatar) 77 | { 78 | cell.avatarImageView.avatarImage(withURL: url) 79 | } 80 | return cell 81 | } 82 | 83 | // MARK: - UITableViewDelegate 84 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 85 | { 86 | let nodeModel = nodesList[indexPath.row] 87 | let nodeTopicListVC = NodeTopicListViewController() 88 | nodeTopicListVC.node = nodeModel 89 | nodeTopicListVC.title = nodeModel.nodeName 90 | nodeTopicListVC.favoriteNodesVC = self 91 | DispatchQueue.main.async { 92 | self.bouncePresent(navigationVCWith: nodeTopicListVC, completion: { 93 | nodeTopicListVC.startLoading() 94 | }) 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /VeXplore/TopicReplyingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopicReplyingViewController.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SafariServices 9 | import StoreKit 10 | 11 | class TopicReplyingViewController: InputViewController 12 | { 13 | var atSomeone: String? { 14 | set 15 | { 16 | if let atSomeone = newValue 17 | { 18 | inputContainerView.contentTextView.text.append(atSomeone) 19 | inputContainerView.isPostEnabled = true 20 | } 21 | } 22 | get 23 | { 24 | fatalError("You cannot read from this object.") 25 | } 26 | } 27 | 28 | var topicId: String? 29 | 30 | override func viewDidLoad() 31 | { 32 | super.viewDidLoad() 33 | inputContainerView.titleLabel.text = R.String.Reply 34 | inputContainerView.imageBtn.isEnabled = true 35 | } 36 | 37 | override func viewWillAppear(_ animated: Bool) 38 | { 39 | super.viewWillAppear(animated) 40 | inputContainerView.contentTextView.becomeFirstResponder() 41 | } 42 | 43 | override func resetTextViews() 44 | { 45 | super.resetTextViews() 46 | inputContainerView.contentTextView.placeholderText = R.String.ReplyPlaceholder 47 | inputContainerView.contentTextView.placeholderTextColor = .border 48 | } 49 | 50 | func textViewDidChange(_ textView: UITextView) 51 | { 52 | if textView == inputContainerView.contentTextView 53 | { 54 | inputContainerView.isPostEnabled = (textView.text.isEmpty == false) 55 | } 56 | } 57 | 58 | override func closeBtnTapped() 59 | { 60 | inputContainerView.contentTextView.resignFirstResponder() 61 | super.closeBtnTapped() 62 | } 63 | 64 | override func postBtnTapped() 65 | { 66 | if inputContainerView.contentTextView.text.isEmpty == false, let topicId = topicId 67 | { 68 | backgroudView.isHidden = false 69 | loadingCancelBtn.isHidden = false 70 | centerLoadingView.isHidden = false 71 | centerLoadingView.initSquaresNormalPostion() 72 | centerLoadingView.beginLoading() 73 | V2Request.Topic.reply(withTopicId: topicId, content: inputContainerView.contentTextView.text) { [weak self] (response) in 74 | guard let weakSelf = self else { 75 | return 76 | } 77 | 78 | weakSelf.centerLoadingView.stopLoading(withSuccess: response.success, completion: { (success) in 79 | if success 80 | { 81 | NotificationCenter.default.post(name: NSNotification.Name.Topic.CommentAdded, object: nil) 82 | NotificationCenter.default.post(name: NSNotification.Name.Profile.NeedRefresh, object: nil) 83 | ModalTransitioningDelegate.shared.reverseDirection = true 84 | weakSelf.closeBtnTapped() 85 | if #available(iOS 10.3, *) 86 | { 87 | SKStoreReviewController.requestReview() 88 | } 89 | } 90 | }) 91 | } 92 | } 93 | } 94 | 95 | func safariViewControllerDidFinish(_ controller: SFSafariViewController) 96 | { 97 | if let newCopyString = UIPasteboard.general.string, newCopyString != copyedString, newCopyString.isValidImgUrl() 98 | { 99 | inputContainerView.contentTextView.text.append(newCopyString) 100 | inputContainerView.isPostEnabled = true 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /VeXplore/RecentListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecentListViewController.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | class RecentListViewController: SwipeTableViewController 10 | { 11 | var page = 0 12 | 13 | override func viewDidLoad() 14 | { 15 | super.viewDidLoad() 16 | tableView.register(TopicCell.self, forCellReuseIdentifier: String(describing: TopicCell.self)) 17 | tableView.estimatedRowHeight = R.Constant.EstimatedRowHeight 18 | } 19 | 20 | // MARK: - Loading request 21 | override func topLoadingRequest() 22 | { 23 | request = V2Request.Topic.getRecentList(withPage: page) { [weak self] (response) in 24 | guard let weakSelf = self else { 25 | return 26 | } 27 | 28 | weakSelf.stopLoading(withLoadingStyle: .top, success: response.success, completion: { (success) -> Void in 29 | if success, let value = response.value 30 | { 31 | weakSelf.topicList = value 32 | weakSelf.tableView.reloadData() 33 | UIView.animate(withDuration: R.Constant.InsetAnimationDuration, delay: 0, options: .beginFromCurrentState, animations: { 34 | weakSelf.tableView.contentInset = .zero 35 | }, completion: nil) 36 | weakSelf.isTopLoadingFail = false 37 | weakSelf.enableTopLoading = false 38 | weakSelf.tableView.tableHeaderView = nil 39 | } 40 | else 41 | { 42 | weakSelf.isTopLoadingFail = true 43 | } 44 | weakSelf.isTopLoading = false 45 | }) 46 | } 47 | } 48 | 49 | // MARK: - UITableViewDataSource 50 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 51 | { 52 | let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TopicCell.self), for: indexPath) as! TopicCell 53 | let topicItem = topicList[indexPath.row] 54 | cell.topicItemModel = topicItem 55 | cell.topicTitleLabel.text = topicItem.topicTitle 56 | cell.userNameLabel.text = topicItem.username 57 | cell.nodeNameBtn.setTitle(topicItem.nodeName, for: .normal) 58 | if topicItem.repliesNumber != nil 59 | { 60 | cell.repliesNumberLabel.text = topicItem.repliesNumber 61 | } 62 | if let avatar = topicItem.avatar, let url = URL(string: R.String.Https + avatar) 63 | { 64 | cell.avatarImageView.avatarImage(withURL: url) 65 | } 66 | if let lastReplyDate = topicItem.lastReplyDate 67 | { 68 | if topicItem.lastReplyUserName != nil 69 | { 70 | cell.lastReplayDateAndUserLabel.text = lastReplyDate 71 | } 72 | else 73 | { 74 | cell.lastReplayDateAndUserLabel.text = String(format: R.String.PublicDate, lastReplyDate) 75 | } 76 | } 77 | cell.delegate = self 78 | return cell 79 | } 80 | 81 | // MARK: - UITableViewDelegate 82 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 83 | { 84 | if let topicId = topicList[indexPath.row].topicId 85 | { 86 | let topicVC = TopicViewController(topicId: topicId) 87 | topicVC.ignoreHandler = { [weak self] topicId -> Void in 88 | guard let weakSelf = self else { 89 | return 90 | } 91 | weakSelf.removeTopic(withId: topicId) 92 | } 93 | DispatchQueue.main.async(execute: { 94 | self.bouncePresent(navigationVCWith: topicVC, completion: nil) 95 | }) 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /VeXplore/libxml/HTMLtree.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Summary: specific APIs to process HTML tree, especially serialization 3 | * Description: this module implements a few function needed to process 4 | * tree in an HTML specific way. 5 | * 6 | * Copy: See Copyright for the status of this software. 7 | * 8 | * Author: Daniel Veillard 9 | */ 10 | 11 | #ifndef __HTML_TREE_H__ 12 | #define __HTML_TREE_H__ 13 | 14 | #include 15 | #include "tree.h" 16 | #include "HTMLparser.h" 17 | 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | 24 | /** 25 | * HTML_TEXT_NODE: 26 | * 27 | * Macro. A text node in a HTML document is really implemented 28 | * the same way as a text node in an XML document. 29 | */ 30 | #define HTML_TEXT_NODE XML_TEXT_NODE 31 | /** 32 | * HTML_ENTITY_REF_NODE: 33 | * 34 | * Macro. An entity reference in a HTML document is really implemented 35 | * the same way as an entity reference in an XML document. 36 | */ 37 | #define HTML_ENTITY_REF_NODE XML_ENTITY_REF_NODE 38 | /** 39 | * HTML_COMMENT_NODE: 40 | * 41 | * Macro. A comment in a HTML document is really implemented 42 | * the same way as a comment in an XML document. 43 | */ 44 | #define HTML_COMMENT_NODE XML_COMMENT_NODE 45 | /** 46 | * HTML_PRESERVE_NODE: 47 | * 48 | * Macro. A preserved node in a HTML document is really implemented 49 | * the same way as a CDATA section in an XML document. 50 | */ 51 | #define HTML_PRESERVE_NODE XML_CDATA_SECTION_NODE 52 | /** 53 | * HTML_PI_NODE: 54 | * 55 | * Macro. A processing instruction in a HTML document is really implemented 56 | * the same way as a processing instruction in an XML document. 57 | */ 58 | #define HTML_PI_NODE XML_PI_NODE 59 | 60 | XMLPUBFUN htmlDocPtr XMLCALL 61 | htmlNewDoc (const xmlChar *URI, 62 | const xmlChar *ExternalID); 63 | XMLPUBFUN htmlDocPtr XMLCALL 64 | htmlNewDocNoDtD (const xmlChar *URI, 65 | const xmlChar *ExternalID); 66 | XMLPUBFUN const xmlChar * XMLCALL 67 | htmlGetMetaEncoding (htmlDocPtr doc); 68 | XMLPUBFUN int XMLCALL 69 | htmlSetMetaEncoding (htmlDocPtr doc, 70 | const xmlChar *encoding); 71 | XMLPUBFUN void XMLCALL 72 | htmlDocDumpMemory (xmlDocPtr cur, 73 | xmlChar **mem, 74 | int *size); 75 | XMLPUBFUN void XMLCALL 76 | htmlDocDumpMemoryFormat (xmlDocPtr cur, 77 | xmlChar **mem, 78 | int *size, 79 | int format); 80 | XMLPUBFUN int XMLCALL 81 | htmlDocDump (FILE *f, 82 | xmlDocPtr cur); 83 | XMLPUBFUN int XMLCALL 84 | htmlSaveFile (const char *filename, 85 | xmlDocPtr cur); 86 | XMLPUBFUN int XMLCALL 87 | htmlNodeDump (xmlBufferPtr buf, 88 | xmlDocPtr doc, 89 | xmlNodePtr cur); 90 | XMLPUBFUN void XMLCALL 91 | htmlNodeDumpFile (FILE *out, 92 | xmlDocPtr doc, 93 | xmlNodePtr cur); 94 | XMLPUBFUN int XMLCALL 95 | htmlNodeDumpFileFormat (FILE *out, 96 | xmlDocPtr doc, 97 | xmlNodePtr cur, 98 | const char *encoding, 99 | int format); 100 | XMLPUBFUN int XMLCALL 101 | htmlSaveFileEnc (const char *filename, 102 | xmlDocPtr cur, 103 | const char *encoding); 104 | XMLPUBFUN int XMLCALL 105 | htmlSaveFileFormat (const char *filename, 106 | xmlDocPtr cur, 107 | const char *encoding, 108 | int format); 109 | 110 | XMLPUBFUN void XMLCALL 111 | htmlNodeDumpFormatOutput(xmlOutputBufferPtr buf, 112 | xmlDocPtr doc, 113 | xmlNodePtr cur, 114 | const char *encoding, 115 | int format); 116 | XMLPUBFUN void XMLCALL 117 | htmlDocContentDumpOutput(xmlOutputBufferPtr buf, 118 | xmlDocPtr cur, 119 | const char *encoding); 120 | XMLPUBFUN void XMLCALL 121 | htmlDocContentDumpFormatOutput(xmlOutputBufferPtr buf, 122 | xmlDocPtr cur, 123 | const char *encoding, 124 | int format); 125 | XMLPUBFUN void XMLCALL 126 | htmlNodeDumpOutput (xmlOutputBufferPtr buf, 127 | xmlDocPtr doc, 128 | xmlNodePtr cur, 129 | const char *encoding); 130 | 131 | 132 | XMLPUBFUN int XMLCALL 133 | htmlIsBooleanAttr (const xmlChar *name); 134 | 135 | 136 | #ifdef __cplusplus 137 | } 138 | #endif 139 | 140 | 141 | #endif /* __HTML_TREE_H__ */ 142 | 143 | -------------------------------------------------------------------------------- /VeXplore/SearchViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchViewController.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | import SharedKit 9 | 10 | class SearchViewController: BaseViewController, UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate 11 | { 12 | lazy var searchBox: SearchBoxView = { 13 | let view = SearchBoxView() 14 | view.translatesAutoresizingMaskIntoConstraints = false 15 | view.searchField.delegate = self 16 | view.searchField.addTarget(self, action: #selector(searchFieldDidChange(_:)), for: .editingChanged) 17 | 18 | return view 19 | }() 20 | 21 | private lazy var line: UIView = { 22 | let view = UIView() 23 | view.translatesAutoresizingMaskIntoConstraints = false 24 | view.backgroundColor = .border 25 | 26 | return view 27 | }() 28 | 29 | lazy var tableView: UITableView = { 30 | let tableView = UITableView(frame: CGRect.zero, style: .plain) 31 | tableView.translatesAutoresizingMaskIntoConstraints = false 32 | tableView.dataSource = self 33 | tableView.delegate = self 34 | tableView.separatorStyle = .none 35 | tableView.backgroundColor = .background 36 | tableView.sectionIndexBackgroundColor = .background 37 | tableView.sectionIndexColor = .href 38 | tableView.register(SectionHeaderView.self, forHeaderFooterViewReuseIdentifier: String(describing: SectionHeaderView.self)) 39 | 40 | return tableView 41 | }() 42 | 43 | override func viewDidLoad() 44 | { 45 | super.viewDidLoad() 46 | 47 | view.addSubview(searchBox) 48 | view.addSubview(line) 49 | view.addSubview(tableView) 50 | let bindings = [ 51 | "searchBox": searchBox, 52 | "line": line, 53 | "tableView": tableView 54 | ] 55 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-12-[searchBox]-12-|", metrics: nil, views: bindings)) 56 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[line]|", metrics: nil, views: bindings)) 57 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tableView]|", metrics: nil, views: bindings)) 58 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-4-[searchBox]-4-[line(0.5)][tableView]|", metrics: nil, views: bindings)) 59 | } 60 | 61 | @objc 62 | override func refreshColorScheme() 63 | { 64 | super.refreshColorScheme() 65 | line.backgroundColor = .border 66 | tableView.backgroundColor = .background 67 | tableView.sectionIndexBackgroundColor = .background 68 | tableView.sectionIndexColor = .href 69 | view.backgroundColor = .subBackground 70 | } 71 | 72 | @objc 73 | override func handleContentSizeCategoryDidChanged() 74 | { 75 | super.handleContentSizeCategoryDidChanged() 76 | searchBox.searchField.font = SharedR.Font.Small 77 | } 78 | 79 | // override this method in subclass 80 | @objc 81 | func searchFieldDidChange(_ textField: UITextField) {} 82 | 83 | // MARK: - UITableViewDataSource 84 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 85 | { 86 | return 0 87 | } 88 | 89 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 90 | { 91 | let cell = UITableViewCell() 92 | return cell 93 | } 94 | 95 | // MARK: - UITableViewDelegate 96 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 97 | { 98 | return UITableViewAutomaticDimension 99 | } 100 | 101 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 102 | { 103 | // override this method in subclass 104 | } 105 | 106 | func scrollViewDidScroll(_ scrollView: UIScrollView) 107 | { 108 | // override this method in subclass 109 | } 110 | 111 | 112 | func scrollViewWillBeginDragging(_ scrollView: UIScrollView) 113 | { 114 | searchBox.searchField.resignFirstResponder() 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /VeXplore/MyFavoriteCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyFavoriteCell.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | protocol MyFavoriteCellDelegate: class 10 | { 11 | func favoriteTopicsTapped() 12 | func favoriteNodesTapped() 13 | func myFollowingsTapped() 14 | } 15 | 16 | class MyFavoriteCell: BaseTableViewCell 17 | { 18 | lazy var nodesView: ProfileActionView = { 19 | let view = ProfileActionView() 20 | view.translatesAutoresizingMaskIntoConstraints = false 21 | view.textLabel.text = R.String.FavoriteNodes 22 | view.numLabel.text = R.String.Zero 23 | view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(nodesViewTapped))) 24 | 25 | return view 26 | }() 27 | 28 | lazy var topicsView: ProfileActionView = { 29 | let view = ProfileActionView() 30 | view.translatesAutoresizingMaskIntoConstraints = false 31 | view.textLabel.text = R.String.FavoriteTopics 32 | view.numLabel.text = R.String.Zero 33 | view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(topicsViewTapped))) 34 | 35 | return view 36 | }() 37 | 38 | lazy var followingView: ProfileActionView = { 39 | let view = ProfileActionView() 40 | view.translatesAutoresizingMaskIntoConstraints = false 41 | view.verticalLine.isHidden = true 42 | view.textLabel.text = R.String.Followings 43 | view.numLabel.text = R.String.Zero 44 | view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(followingsViewTapped))) 45 | 46 | return view 47 | }() 48 | 49 | private lazy var bottomLine: UIView = { 50 | let view = UIView() 51 | view.translatesAutoresizingMaskIntoConstraints = false 52 | view.backgroundColor = .border 53 | 54 | return view 55 | }() 56 | 57 | weak var delegate: MyFavoriteCellDelegate? 58 | 59 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) 60 | { 61 | super.init(style: style, reuseIdentifier: reuseIdentifier) 62 | 63 | contentView.addSubview(nodesView) 64 | contentView.addSubview(topicsView) 65 | contentView.addSubview(followingView) 66 | contentView.addSubview(bottomLine) 67 | let bindings = [ 68 | "nodesView": nodesView, 69 | "topicsView": topicsView, 70 | "followingView": followingView, 71 | "bottomLine": bottomLine 72 | ] 73 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[nodesView][topicsView][followingView]|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: bindings)) 74 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[bottomLine]|", metrics: nil, views: bindings)) 75 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-8-[nodesView]-8-|", metrics: nil, views: bindings)) 76 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[bottomLine(0.5)]|", metrics: nil, views: bindings)) 77 | nodesView.widthAnchor.constraint(equalTo: followingView.widthAnchor).isActive = true 78 | topicsView.widthAnchor.constraint(equalTo: followingView.widthAnchor).isActive = true 79 | 80 | selectionStyle = .none 81 | } 82 | 83 | required init?(coder aDecoder: NSCoder) 84 | { 85 | super.init(coder: aDecoder) 86 | } 87 | 88 | override func prepareForReuse() 89 | { 90 | super.prepareForReuse() 91 | nodesView.prepareForReuse() 92 | topicsView.prepareForReuse() 93 | followingView.prepareForReuse() 94 | delegate = nil 95 | } 96 | 97 | @objc 98 | override func refreshColorScheme() 99 | { 100 | super.refreshColorScheme() 101 | bottomLine.backgroundColor = .border 102 | } 103 | 104 | // MARK: - Actions 105 | @objc 106 | private func nodesViewTapped() 107 | { 108 | delegate?.favoriteNodesTapped() 109 | } 110 | 111 | @objc 112 | private func topicsViewTapped() 113 | { 114 | delegate?.favoriteTopicsTapped() 115 | } 116 | 117 | @objc 118 | private func followingsViewTapped() 119 | { 120 | delegate?.myFollowingsTapped() 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /TodayExtension/TodayViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodayViewController.swift 3 | // TodayExtension 4 | // 5 | // Copyright © 2017 Jimmy. All rights reserved. 6 | // 7 | 8 | import NotificationCenter 9 | import SharedKit 10 | 11 | // Due to Swift module, NSExtensionPrincipalClass should be $(PRODUCT_NAME).TodayViewController 12 | // Or you can use TodayViewController as NSExtensionPrincipalClass, and rename the class expose to Objc by using: 13 | // @objc (TodayViewController) 14 | class TodayViewController: UIViewController, NCWidgetProviding, UITableViewDataSource, UITableViewDelegate 15 | { 16 | private let rowHeight: CGFloat = 37.0 17 | 18 | lazy private var tableView: UITableView = { 19 | let tableView = UITableView(frame: .zero, style: .plain) 20 | tableView.translatesAutoresizingMaskIntoConstraints = false 21 | tableView.dataSource = self 22 | tableView.delegate = self 23 | tableView.rowHeight = self.rowHeight 24 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: String(describing: UITableViewCell.self)) 25 | 26 | return tableView 27 | }() 28 | 29 | private var data = [TopicItem]() 30 | 31 | override func viewDidLoad() 32 | { 33 | super.viewDidLoad() 34 | extensionContext?.widgetLargestAvailableDisplayMode = .expanded 35 | view.addSubview(tableView) 36 | let bindings: [String : Any] = ["tableView": tableView] 37 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tableView]|", metrics: nil, views: bindings)) 38 | view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[tableView]|", metrics: nil, views: bindings)) 39 | } 40 | 41 | override func viewDidAppear(_ animated: Bool) 42 | { 43 | super.viewDidAppear(animated) 44 | loadData() 45 | } 46 | 47 | func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) 48 | { 49 | loadData() 50 | completionHandler(NCUpdateResult.newData) 51 | } 52 | 53 | func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) 54 | { 55 | if activeDisplayMode == .expanded 56 | { 57 | preferredContentSize = CGSize(width: 0, height: rowHeight * CGFloat(data.count)) 58 | } 59 | else 60 | { 61 | preferredContentSize = maxSize 62 | } 63 | } 64 | 65 | // MARK: - UITableViewDataSource 66 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 67 | { 68 | return data.count 69 | } 70 | 71 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 72 | { 73 | let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UITableViewCell.self), for: indexPath) 74 | cell.textLabel?.font = UIFont.systemFont(ofSize: 14.0) 75 | cell.textLabel?.text = data[indexPath.row].title 76 | return cell 77 | } 78 | 79 | // MARK: - UITableViewDelegate 80 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 81 | { 82 | let topicURL = data[indexPath.row].url 83 | if let url = URL(string: "vexplore://?\(topicURL)") 84 | { 85 | extensionContext?.open(url, completionHandler: nil) 86 | } 87 | } 88 | 89 | 90 | private func loadData() 91 | { 92 | let url = "https://www.v2ex.com/api/topics/hot.json" 93 | var topics = [TopicItem]() 94 | Networking.request(url, headers: SharedR.Dict.MobileClientHeaders).responseJSON { (response) in 95 | if response.result.isSuccess, let value = response.result.value 96 | { 97 | let json = JSON(object: value) 98 | for (_, subJson) in json 99 | { 100 | if let topicURL = subJson["url"].string, let topicTitle = subJson["title"].string 101 | { 102 | let topicItem = TopicItem(url: topicURL, title: topicTitle) 103 | topics.append(topicItem) 104 | } 105 | } 106 | self.data = topics 107 | self.tableView.reloadData() 108 | } 109 | } 110 | } 111 | 112 | private struct TopicItem 113 | { 114 | let url: String 115 | let title: String 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /SharedKit/SharedR.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SharedR.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2017 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | public struct SharedR 10 | { 11 | struct Array 12 | { 13 | static let URLPatterns = [ 14 | "^(http:\\/\\/|https:\\/\\/)?(www\\.)?(v2ex.com)?(/)?t/[0-9]+", 15 | "^(http:\\/\\/|https:\\/\\/)?(www\\.)?(v2ex.com)?(/)?member/[a-zA-Z0-9_]+$", 16 | "^(http:\\/\\/|https:\\/\\/)?(www\\.)?(v2ex.com)?(/)?go/[a-zA-Z0-9_]+$", 17 | "^mailto:.*@.*\\..*$", 18 | "^(http|ftp|https):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&:/~\\+#]*[\\w\\-\\@?^=%&/~\\+#])?" 19 | ] 20 | 21 | static let ValidImgUrls = [ 22 | "imgur.com", 23 | "sinaimg.cn", 24 | "ooo.0o0.ooo" 25 | ] 26 | } 27 | 28 | public struct Dict 29 | { 30 | public static let MobileClientHeaders = ["user-agent": String.MobileUserAgent] 31 | public static let DesktopClientHeaders = ["user-agent": String.DesktopUserAgent] 32 | } 33 | 34 | struct Key 35 | { 36 | static let Username = "vexplore.userDefaults.key.username" 37 | static let ShowedTabs = "vexplore.userDefaults.key.showedTabs" 38 | static let HiddenTabs = "vexplore.userDefaults.key.hiddenTabs" 39 | static let CurrentTab = "vexplore.userDefaults.key.currentTab" 40 | static let EnableShake = "vexplore.userDefaults.key.enableShake" 41 | static let EnablePullReply = "vexplore.userDefaults.key.enablePullReply" 42 | static let EnableTabBarHidden = "vexplore.userDefaults.key.enableTabBarHidden" 43 | static let EnableShowReplyIndex = "vexplore.userDefaults.key.enableShowReplyIndex" 44 | static let EnableHighlightOwnerReplies = "vexplore.userDefaults.key.EnableHighlightOwnerReplies" 45 | static let DynamicTitleFontScale = "vexplore.userDefaults.key.dynamicTitleFontScale" 46 | static let AllNodesEtag = "vexplore.userDefaults.key.allNodesEtag" 47 | static let HomePageTopicList = "vexplore.topicList.homePage.key.%@" 48 | static let LastCacheVersion = "vexplore.userDefaults.key.lastCacheVersion" 49 | static let EnableNightMode = "vexplore.userDefaults.key.enableNightMode" 50 | static let AlwaysEnableNightMode = "vexplore.userDefaults.key.alwaysEnableNightMode" 51 | static let EnableSchedule = "vexplore.userDefaults.key.enableSchedule" 52 | static let ScheduleStartDate = "vexplore.userDefaults.key.scheduleStartDate" 53 | static let ScheduleEndDate = "vexplore.userDefaults.key.scheduleEndDate" 54 | static let BaiduAccessToken = "vexplore.userDefaults.key.baiduAccessToken" 55 | static let BaiduTokenExpiresDate = "vexplore.userDefaults.key.baiduTokenExpiresDate" 56 | } 57 | 58 | public struct String 59 | { 60 | public static let Empty = "" 61 | public static let V2EX = "V2EX" 62 | public static let ViewInApp = "打开" 63 | public static let InvalidTopic = "该页面不是V2EX帖子\n无法使用VeXplore查看🤷‍♀️" 64 | public static let ValidTopic = "该页面为V2EX帖子\n可以使用VeXplore查看👏" 65 | public static let MobileUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4" 66 | public static let DesktopUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36" 67 | } 68 | 69 | public static var Font: RFont { return RFont() } 70 | public struct RFont 71 | { 72 | fileprivate init(){} 73 | public var VeryLarge: UIFont { return UIFont.preferredFont(forTextStyle: .title3) } 74 | public var Large: UIFont { return UIFont.preferredFont(forTextStyle: .body) } 75 | public var Medium: UIFont { return UIFont.preferredFont(forTextStyle: .callout) } // topic title, comment 76 | public var Small: UIFont { return UIFont.preferredFont(forTextStyle: .footnote) } 77 | public var VerySmall: UIFont { return UIFont.preferredFont(forTextStyle: .caption1) } 78 | public var ExtraSmall: UIFont { return UIFont.preferredFont(forTextStyle: .caption2) } // date 79 | 80 | public var StaticMedium: UIFont { return UIFont.systemFont(ofSize: 14.0) } 81 | public var DynamicMedium: UIFont { 82 | let fontScaleString = UserDefaults.fontScaleString 83 | let fontScale = CGFloat(fontScaleString.doubleValue) 84 | let scaledFontSize = round(SharedR.Font.Medium.pointSize * fontScale) 85 | let font = SharedR.Font.Medium.withSize(scaledFontSize) 86 | return font 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /VeXplore/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // VeXplore 4 | // 5 | // Copyright © 2016 Jimmy. All rights reserved. 6 | // 7 | 8 | 9 | class MainViewController: UITabBarController, UITabBarControllerDelegate 10 | { 11 | static let shared = MainViewController() 12 | private let homeVC = HomePageViewController() 13 | private let nodesVC = NodesViewController() 14 | private let searchVC = SiteSearchViewController() 15 | private var notificationVC: NotificationViewController! 16 | private var profileVC: MyProfileViewController! 17 | private var notificationTabItem: UITabBarItem! 18 | 19 | private init() 20 | { 21 | super.init(nibName: nil, bundle: nil) 22 | } 23 | 24 | required init?(coder aDecoder: NSCoder) 25 | { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | override func viewDidLoad() 30 | { 31 | super.viewDidLoad() 32 | 33 | buildUI() 34 | delegate = self 35 | } 36 | 37 | private func buildUI() 38 | { 39 | if User.shared.isLogin == true, 40 | let diskCachePath = cachePathString(withFilename: NotificationViewController.description()), 41 | let jsonData = NSKeyedUnarchiver.unarchiveObject(withFile: diskCachePath) as? Data, 42 | let unarchiveVC = try? JSONDecoder().decode(NotificationViewController.self, from: jsonData), 43 | unarchiveVC.username == User.shared.username 44 | { 45 | notificationVC = unarchiveVC 46 | } 47 | else 48 | { 49 | notificationVC = NotificationViewController() 50 | } 51 | 52 | if User.shared.isLogin == true, 53 | let diskCachePath = cachePathString(withFilename: MyProfileViewController.description()), 54 | let jsonData = NSKeyedUnarchiver.unarchiveObject(withFile: diskCachePath) as? Data, 55 | let unarchiveVC = try? JSONDecoder().decode(MyProfileViewController.self, from: jsonData) 56 | { 57 | profileVC = unarchiveVC 58 | } 59 | else 60 | { 61 | profileVC = MyProfileViewController() 62 | } 63 | 64 | nodesVC.searchVC.getAllNodesIfNeed() 65 | 66 | let homeNav = UINavigationController(rootViewController: homeVC) 67 | let nodesNav = UINavigationController(rootViewController: nodesVC) 68 | let searchNav = UINavigationController(rootViewController: searchVC) 69 | let notificationNav = UINavigationController(rootViewController: notificationVC) 70 | let profileNav = UINavigationController(rootViewController:profileVC) 71 | 72 | homeNav.tabBarItem = UITabBarItem(title: nil, image: R.Image.Home, selectedImage: R.Image.Home) 73 | nodesNav.tabBarItem = UITabBarItem(title: nil, image: R.Image.Nodes, selectedImage: R.Image.Nodes) 74 | searchNav.tabBarItem = UITabBarItem(title: nil, image: R.Image.TabarSearch, selectedImage: R.Image.TabarSearch) 75 | notificationTabItem = UITabBarItem(title: nil, image: R.Image.Notification, selectedImage: R.Image.Notification) 76 | notificationNav.tabBarItem = notificationTabItem 77 | profileNav.tabBarItem = UITabBarItem(title: nil, image: R.Image.Profile, selectedImage: R.Image.Profile) 78 | 79 | viewControllers = [homeNav, nodesNav, searchNav, notificationNav, profileNav] 80 | 81 | refreshColorScheme() 82 | 83 | NotificationCenter.default.addObserver(self, selector: #selector(refreshColorScheme), name: NSNotification.Name.Setting.NightModeDidChange, object: nil) 84 | } 85 | 86 | @objc 87 | private func refreshColorScheme() 88 | { 89 | tabBar.setupTabBar() 90 | } 91 | 92 | func setNotificationNum(_ number: Int) 93 | { 94 | notificationTabItem.badgeValue = number > 0 ? String(number) : nil 95 | } 96 | 97 | func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool 98 | { 99 | if viewControllers![selectedIndex] == viewController 100 | { 101 | if selectedIndex == 0 102 | { 103 | homeVC.doubleTapTabarItem() 104 | } 105 | else if selectedIndex == 1 106 | { 107 | nodesVC.doubleTapTabarItem() 108 | } 109 | else if selectedIndex == 3 110 | { 111 | notificationVC.doubleTapTabarItem() 112 | } 113 | return false 114 | } 115 | return true 116 | } 117 | 118 | } 119 | --------------------------------------------------------------------------------