├── .gitignore ├── .travis.yml ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── Example.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── SwiftUI │ │ └── SampleUI.swift │ ├── ViewController.swift │ └── ViewController │ │ ├── CodeOnly.swift │ │ ├── CustomCss.swift │ │ ├── NonStyled.swift │ │ ├── Plugins.swift │ │ ├── RemoteStylesheets.swift │ │ ├── ScrollView.swift │ │ └── Storyboard.swift ├── Gemfile ├── Gemfile.lock ├── Podfile ├── Podfile.lock └── package-lock.json ├── LICENSE ├── MarkdownView.podspec ├── MarkdownView.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── MarkdownView.xcscheme ├── Package.swift ├── README.md ├── Sources ├── Info.plist ├── MarkdownView.h └── MarkdownView │ ├── MarkdownUI.swift │ ├── MarkdownView.swift │ └── Resources │ ├── main.css │ ├── main.js │ ├── main.js.LICENSE.txt │ ├── non_styled.html │ └── styled.html ├── sample.gif ├── sample.md ├── sample_css.png ├── sample_plugin.png └── webassets ├── .babelrc ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src ├── css │ ├── bootstrap.css │ ├── gist.css │ ├── github.css │ └── index.css └── js │ └── index.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | # Note: if you ignore the Pods directory, make sure to uncomment 31 | # `pod install` in .travis.yml 32 | # 33 | Pods/ 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode12.5 2 | language: swift 3 | before_install: 4 | - brew update 5 | - brew outdated carthage || brew upgrade carthage 6 | install: 7 | - gem install xcpretty 8 | script: 9 | - set -o pipefail && cd Example && bundle install && bundle exec pod install && cd ../ && xcodebuild build -workspace Example/Example.xcworkspace -scheme Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty 10 | - pod lib lint --quick 11 | - carthage build --platform iOS --no-skip-current 12 | - swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios14.0-simulator" 13 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3E3DF024278C5387009DD55A /* SampleUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DF023278C5387009DD55A /* SampleUI.swift */; }; 11 | 3E5E1E1E26E8F1620098F0E3 /* CustomCss.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5E1E1D26E8F1610098F0E3 /* CustomCss.swift */; }; 12 | 3E71C76026EA0D7400C79EA9 /* Plugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E71C75F26EA0D7400C79EA9 /* Plugins.swift */; }; 13 | 3E910F3026F06F6E009C311B /* RemoteStylesheets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E910F2F26F06F6E009C311B /* RemoteStylesheets.swift */; }; 14 | 3E9561AE26EB7E9900FFCC19 /* NonStyled.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9561AD26EB7E9900FFCC19 /* NonStyled.swift */; }; 15 | DE524D651EC47CB100E8C2F9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE524D641EC47CB100E8C2F9 /* AppDelegate.swift */; }; 16 | DE524D671EC47CB100E8C2F9 /* CodeOnly.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE524D661EC47CB100E8C2F9 /* CodeOnly.swift */; }; 17 | DE524D6A1EC47CB100E8C2F9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE524D681EC47CB100E8C2F9 /* Main.storyboard */; }; 18 | DE524D6C1EC47CB100E8C2F9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DE524D6B1EC47CB100E8C2F9 /* Assets.xcassets */; }; 19 | DE524D6F1EC47CB100E8C2F9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DE524D6D1EC47CB100E8C2F9 /* LaunchScreen.storyboard */; }; 20 | DF85605DE14EAE2D14E58EB3 /* libPods-Example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EAEB7F4358B9EF21E9D96277 /* libPods-Example.a */; }; 21 | FCB2E9471ECA8B7600285B87 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB2E9461ECA8B7600285B87 /* ViewController.swift */; }; 22 | FCB2E9491ECA929500285B87 /* Storyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB2E9481ECA929500285B87 /* Storyboard.swift */; }; 23 | FCB2E94B1ECA93CB00285B87 /* ScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCB2E94A1ECA93CB00285B87 /* ScrollView.swift */; }; 24 | FCB2E94F1ECADF3700285B87 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = FCB2E94E1ECADF3700285B87 /* README.md */; }; 25 | FCB2E9511ECAF36500285B87 /* sample.md in Resources */ = {isa = PBXBuildFile; fileRef = FCB2E9501ECAF36500285B87 /* sample.md */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 3E3DF023278C5387009DD55A /* SampleUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleUI.swift; sourceTree = ""; }; 30 | 3E5E1E1D26E8F1610098F0E3 /* CustomCss.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCss.swift; sourceTree = ""; }; 31 | 3E71C75F26EA0D7400C79EA9 /* Plugins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plugins.swift; sourceTree = ""; }; 32 | 3E910F2F26F06F6E009C311B /* RemoteStylesheets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteStylesheets.swift; sourceTree = ""; }; 33 | 3E9561AD26EB7E9900FFCC19 /* NonStyled.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStyled.swift; sourceTree = ""; }; 34 | 7DB4CCAB27BE7F6CF35BA1E5 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; 35 | 88E6DEC525FE3CD3BAB79EA8 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; 36 | DE524D611EC47CB100E8C2F9 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | DE524D641EC47CB100E8C2F9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 38 | DE524D661EC47CB100E8C2F9 /* CodeOnly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeOnly.swift; sourceTree = ""; }; 39 | DE524D691EC47CB100E8C2F9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 40 | DE524D6B1EC47CB100E8C2F9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 41 | DE524D6E1EC47CB100E8C2F9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 42 | DE524D701EC47CB100E8C2F9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | EAEB7F4358B9EF21E9D96277 /* libPods-Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | FCB2E9461ECA8B7600285B87 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 45 | FCB2E9481ECA929500285B87 /* Storyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storyboard.swift; sourceTree = ""; }; 46 | FCB2E94A1ECA93CB00285B87 /* ScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollView.swift; sourceTree = ""; }; 47 | FCB2E94E1ECADF3700285B87 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../README.md; sourceTree = ""; }; 48 | FCB2E9501ECAF36500285B87 /* sample.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = sample.md; path = ../../sample.md; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | DE524D5E1EC47CB100E8C2F9 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | DF85605DE14EAE2D14E58EB3 /* libPods-Example.a in Frameworks */, 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | /* End PBXFrameworksBuildPhase section */ 61 | 62 | /* Begin PBXGroup section */ 63 | 3E3DF022278C5375009DD55A /* SwiftUI */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 3E3DF023278C5387009DD55A /* SampleUI.swift */, 67 | ); 68 | path = SwiftUI; 69 | sourceTree = ""; 70 | }; 71 | 3E5E1E1F26E8F1670098F0E3 /* ViewController */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | DE524D661EC47CB100E8C2F9 /* CodeOnly.swift */, 75 | FCB2E9481ECA929500285B87 /* Storyboard.swift */, 76 | FCB2E94A1ECA93CB00285B87 /* ScrollView.swift */, 77 | 3E5E1E1D26E8F1610098F0E3 /* CustomCss.swift */, 78 | 3E71C75F26EA0D7400C79EA9 /* Plugins.swift */, 79 | 3E9561AD26EB7E9900FFCC19 /* NonStyled.swift */, 80 | 3E910F2F26F06F6E009C311B /* RemoteStylesheets.swift */, 81 | ); 82 | path = ViewController; 83 | sourceTree = ""; 84 | }; 85 | 5120DE397F30974224FD684A /* Frameworks */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | EAEB7F4358B9EF21E9D96277 /* libPods-Example.a */, 89 | ); 90 | name = Frameworks; 91 | sourceTree = ""; 92 | }; 93 | 671C4DD5B4C96E4760239E4D /* Pods */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 88E6DEC525FE3CD3BAB79EA8 /* Pods-Example.debug.xcconfig */, 97 | 7DB4CCAB27BE7F6CF35BA1E5 /* Pods-Example.release.xcconfig */, 98 | ); 99 | name = Pods; 100 | sourceTree = ""; 101 | }; 102 | DE524D581EC47CB100E8C2F9 = { 103 | isa = PBXGroup; 104 | children = ( 105 | DE524D631EC47CB100E8C2F9 /* Example */, 106 | DE524D621EC47CB100E8C2F9 /* Products */, 107 | 671C4DD5B4C96E4760239E4D /* Pods */, 108 | 5120DE397F30974224FD684A /* Frameworks */, 109 | ); 110 | sourceTree = ""; 111 | }; 112 | DE524D621EC47CB100E8C2F9 /* Products */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | DE524D611EC47CB100E8C2F9 /* Example.app */, 116 | ); 117 | name = Products; 118 | sourceTree = ""; 119 | }; 120 | DE524D631EC47CB100E8C2F9 /* Example */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 3E3DF022278C5375009DD55A /* SwiftUI */, 124 | 3E5E1E1F26E8F1670098F0E3 /* ViewController */, 125 | FCB2E9501ECAF36500285B87 /* sample.md */, 126 | FCB2E94E1ECADF3700285B87 /* README.md */, 127 | DE524D641EC47CB100E8C2F9 /* AppDelegate.swift */, 128 | FCB2E9461ECA8B7600285B87 /* ViewController.swift */, 129 | DE524D681EC47CB100E8C2F9 /* Main.storyboard */, 130 | DE524D6D1EC47CB100E8C2F9 /* LaunchScreen.storyboard */, 131 | DE524D701EC47CB100E8C2F9 /* Info.plist */, 132 | DE524D6B1EC47CB100E8C2F9 /* Assets.xcassets */, 133 | ); 134 | path = Example; 135 | sourceTree = ""; 136 | }; 137 | /* End PBXGroup section */ 138 | 139 | /* Begin PBXNativeTarget section */ 140 | DE524D601EC47CB100E8C2F9 /* Example */ = { 141 | isa = PBXNativeTarget; 142 | buildConfigurationList = DE524D731EC47CB100E8C2F9 /* Build configuration list for PBXNativeTarget "Example" */; 143 | buildPhases = ( 144 | AC7DF812B9F91A932D25CB6D /* [CP] Check Pods Manifest.lock */, 145 | DE524D5D1EC47CB100E8C2F9 /* Sources */, 146 | DE524D5E1EC47CB100E8C2F9 /* Frameworks */, 147 | DE524D5F1EC47CB100E8C2F9 /* Resources */, 148 | A9591DA79983D9E13DB22B4B /* [CP] Copy Pods Resources */, 149 | ); 150 | buildRules = ( 151 | ); 152 | dependencies = ( 153 | ); 154 | name = Example; 155 | productName = Example; 156 | productReference = DE524D611EC47CB100E8C2F9 /* Example.app */; 157 | productType = "com.apple.product-type.application"; 158 | }; 159 | /* End PBXNativeTarget section */ 160 | 161 | /* Begin PBXProject section */ 162 | DE524D591EC47CB100E8C2F9 /* Project object */ = { 163 | isa = PBXProject; 164 | attributes = { 165 | LastSwiftUpdateCheck = 0830; 166 | LastUpgradeCheck = 1020; 167 | ORGANIZATIONNAME = com.keita.oouchi; 168 | TargetAttributes = { 169 | DE524D601EC47CB100E8C2F9 = { 170 | CreatedOnToolsVersion = 8.3.2; 171 | DevelopmentTeam = XF6BW7ZJN2; 172 | ProvisioningStyle = Automatic; 173 | }; 174 | }; 175 | }; 176 | buildConfigurationList = DE524D5C1EC47CB100E8C2F9 /* Build configuration list for PBXProject "Example" */; 177 | compatibilityVersion = "Xcode 3.2"; 178 | developmentRegion = en; 179 | hasScannedForEncodings = 0; 180 | knownRegions = ( 181 | en, 182 | Base, 183 | ); 184 | mainGroup = DE524D581EC47CB100E8C2F9; 185 | productRefGroup = DE524D621EC47CB100E8C2F9 /* Products */; 186 | projectDirPath = ""; 187 | projectRoot = ""; 188 | targets = ( 189 | DE524D601EC47CB100E8C2F9 /* Example */, 190 | ); 191 | }; 192 | /* End PBXProject section */ 193 | 194 | /* Begin PBXResourcesBuildPhase section */ 195 | DE524D5F1EC47CB100E8C2F9 /* Resources */ = { 196 | isa = PBXResourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | FCB2E94F1ECADF3700285B87 /* README.md in Resources */, 200 | FCB2E9511ECAF36500285B87 /* sample.md in Resources */, 201 | DE524D6F1EC47CB100E8C2F9 /* LaunchScreen.storyboard in Resources */, 202 | DE524D6C1EC47CB100E8C2F9 /* Assets.xcassets in Resources */, 203 | DE524D6A1EC47CB100E8C2F9 /* Main.storyboard in Resources */, 204 | ); 205 | runOnlyForDeploymentPostprocessing = 0; 206 | }; 207 | /* End PBXResourcesBuildPhase section */ 208 | 209 | /* Begin PBXShellScriptBuildPhase section */ 210 | A9591DA79983D9E13DB22B4B /* [CP] Copy Pods Resources */ = { 211 | isa = PBXShellScriptBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | ); 215 | inputPaths = ( 216 | "${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-resources.sh", 217 | "${PODS_CONFIGURATION_BUILD_DIR}/MarkdownView/MarkdownView.bundle", 218 | ); 219 | name = "[CP] Copy Pods Resources"; 220 | outputPaths = ( 221 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MarkdownView.bundle", 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | shellPath = /bin/sh; 225 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-resources.sh\"\n"; 226 | showEnvVarsInLog = 0; 227 | }; 228 | AC7DF812B9F91A932D25CB6D /* [CP] Check Pods Manifest.lock */ = { 229 | isa = PBXShellScriptBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | ); 233 | inputPaths = ( 234 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 235 | "${PODS_ROOT}/Manifest.lock", 236 | ); 237 | name = "[CP] Check Pods Manifest.lock"; 238 | outputPaths = ( 239 | "$(DERIVED_FILE_DIR)/Pods-Example-checkManifestLockResult.txt", 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | shellPath = /bin/sh; 243 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 244 | showEnvVarsInLog = 0; 245 | }; 246 | /* End PBXShellScriptBuildPhase section */ 247 | 248 | /* Begin PBXSourcesBuildPhase section */ 249 | DE524D5D1EC47CB100E8C2F9 /* Sources */ = { 250 | isa = PBXSourcesBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | FCB2E9491ECA929500285B87 /* Storyboard.swift in Sources */, 254 | 3E910F3026F06F6E009C311B /* RemoteStylesheets.swift in Sources */, 255 | FCB2E9471ECA8B7600285B87 /* ViewController.swift in Sources */, 256 | 3E9561AE26EB7E9900FFCC19 /* NonStyled.swift in Sources */, 257 | 3E5E1E1E26E8F1620098F0E3 /* CustomCss.swift in Sources */, 258 | 3E71C76026EA0D7400C79EA9 /* Plugins.swift in Sources */, 259 | FCB2E94B1ECA93CB00285B87 /* ScrollView.swift in Sources */, 260 | 3E3DF024278C5387009DD55A /* SampleUI.swift in Sources */, 261 | DE524D671EC47CB100E8C2F9 /* CodeOnly.swift in Sources */, 262 | DE524D651EC47CB100E8C2F9 /* AppDelegate.swift in Sources */, 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXSourcesBuildPhase section */ 267 | 268 | /* Begin PBXVariantGroup section */ 269 | DE524D681EC47CB100E8C2F9 /* Main.storyboard */ = { 270 | isa = PBXVariantGroup; 271 | children = ( 272 | DE524D691EC47CB100E8C2F9 /* Base */, 273 | ); 274 | name = Main.storyboard; 275 | sourceTree = ""; 276 | }; 277 | DE524D6D1EC47CB100E8C2F9 /* LaunchScreen.storyboard */ = { 278 | isa = PBXVariantGroup; 279 | children = ( 280 | DE524D6E1EC47CB100E8C2F9 /* Base */, 281 | ); 282 | name = LaunchScreen.storyboard; 283 | sourceTree = ""; 284 | }; 285 | /* End PBXVariantGroup section */ 286 | 287 | /* Begin XCBuildConfiguration section */ 288 | DE524D711EC47CB100E8C2F9 /* Debug */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ALWAYS_SEARCH_USER_PATHS = NO; 292 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 293 | CLANG_ANALYZER_NONNULL = YES; 294 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 295 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 296 | CLANG_CXX_LIBRARY = "libc++"; 297 | CLANG_ENABLE_MODULES = YES; 298 | CLANG_ENABLE_OBJC_ARC = YES; 299 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 300 | CLANG_WARN_BOOL_CONVERSION = YES; 301 | CLANG_WARN_COMMA = YES; 302 | CLANG_WARN_CONSTANT_CONVERSION = YES; 303 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 304 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 305 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 306 | CLANG_WARN_EMPTY_BODY = YES; 307 | CLANG_WARN_ENUM_CONVERSION = YES; 308 | CLANG_WARN_INFINITE_RECURSION = YES; 309 | CLANG_WARN_INT_CONVERSION = YES; 310 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 311 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 312 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 313 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 314 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 315 | CLANG_WARN_STRICT_PROTOTYPES = YES; 316 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 317 | CLANG_WARN_UNREACHABLE_CODE = YES; 318 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 319 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 320 | COPY_PHASE_STRIP = NO; 321 | DEBUG_INFORMATION_FORMAT = dwarf; 322 | ENABLE_STRICT_OBJC_MSGSEND = YES; 323 | ENABLE_TESTABILITY = YES; 324 | GCC_C_LANGUAGE_STANDARD = gnu99; 325 | GCC_DYNAMIC_NO_PIC = NO; 326 | GCC_NO_COMMON_BLOCKS = YES; 327 | GCC_OPTIMIZATION_LEVEL = 0; 328 | GCC_PREPROCESSOR_DEFINITIONS = ( 329 | "DEBUG=1", 330 | "$(inherited)", 331 | ); 332 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 333 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 334 | GCC_WARN_UNDECLARED_SELECTOR = YES; 335 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 336 | GCC_WARN_UNUSED_FUNCTION = YES; 337 | GCC_WARN_UNUSED_VARIABLE = YES; 338 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 339 | MTL_ENABLE_DEBUG_INFO = YES; 340 | ONLY_ACTIVE_ARCH = YES; 341 | SDKROOT = iphoneos; 342 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 343 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 344 | SWIFT_VERSION = 4.0; 345 | TARGETED_DEVICE_FAMILY = "1,2"; 346 | }; 347 | name = Debug; 348 | }; 349 | DE524D721EC47CB100E8C2F9 /* Release */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ALWAYS_SEARCH_USER_PATHS = NO; 353 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 354 | CLANG_ANALYZER_NONNULL = YES; 355 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 356 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 357 | CLANG_CXX_LIBRARY = "libc++"; 358 | CLANG_ENABLE_MODULES = YES; 359 | CLANG_ENABLE_OBJC_ARC = YES; 360 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 361 | CLANG_WARN_BOOL_CONVERSION = YES; 362 | CLANG_WARN_COMMA = YES; 363 | CLANG_WARN_CONSTANT_CONVERSION = YES; 364 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 365 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 366 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 367 | CLANG_WARN_EMPTY_BODY = YES; 368 | CLANG_WARN_ENUM_CONVERSION = YES; 369 | CLANG_WARN_INFINITE_RECURSION = YES; 370 | CLANG_WARN_INT_CONVERSION = YES; 371 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 372 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 373 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 374 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 375 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 376 | CLANG_WARN_STRICT_PROTOTYPES = YES; 377 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 378 | CLANG_WARN_UNREACHABLE_CODE = YES; 379 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 380 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 381 | COPY_PHASE_STRIP = NO; 382 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 383 | ENABLE_NS_ASSERTIONS = NO; 384 | ENABLE_STRICT_OBJC_MSGSEND = YES; 385 | GCC_C_LANGUAGE_STANDARD = gnu99; 386 | GCC_NO_COMMON_BLOCKS = YES; 387 | GCC_OPTIMIZATION_LEVEL = fast; 388 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 389 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 390 | GCC_WARN_UNDECLARED_SELECTOR = YES; 391 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 392 | GCC_WARN_UNUSED_FUNCTION = YES; 393 | GCC_WARN_UNUSED_VARIABLE = YES; 394 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 395 | MTL_ENABLE_DEBUG_INFO = NO; 396 | SDKROOT = iphoneos; 397 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 398 | SWIFT_VERSION = 4.0; 399 | TARGETED_DEVICE_FAMILY = "1,2"; 400 | VALIDATE_PRODUCT = YES; 401 | }; 402 | name = Release; 403 | }; 404 | DE524D741EC47CB100E8C2F9 /* Debug */ = { 405 | isa = XCBuildConfiguration; 406 | baseConfigurationReference = 88E6DEC525FE3CD3BAB79EA8 /* Pods-Example.debug.xcconfig */; 407 | buildSettings = { 408 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 409 | CODE_SIGN_IDENTITY = "Apple Development"; 410 | CODE_SIGN_STYLE = Automatic; 411 | DEVELOPMENT_TEAM = XF6BW7ZJN2; 412 | INFOPLIST_FILE = Example/Info.plist; 413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 414 | PRODUCT_BUNDLE_IDENTIFIER = com.keita.oouchi.Example; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | PROVISIONING_PROFILE_SPECIFIER = ""; 417 | SWIFT_VERSION = 5.0; 418 | }; 419 | name = Debug; 420 | }; 421 | DE524D751EC47CB100E8C2F9 /* Release */ = { 422 | isa = XCBuildConfiguration; 423 | baseConfigurationReference = 7DB4CCAB27BE7F6CF35BA1E5 /* Pods-Example.release.xcconfig */; 424 | buildSettings = { 425 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 426 | CODE_SIGN_IDENTITY = "Apple Development"; 427 | CODE_SIGN_STYLE = Automatic; 428 | DEVELOPMENT_TEAM = XF6BW7ZJN2; 429 | INFOPLIST_FILE = Example/Info.plist; 430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 431 | PRODUCT_BUNDLE_IDENTIFIER = com.keita.oouchi.Example; 432 | PRODUCT_NAME = "$(TARGET_NAME)"; 433 | PROVISIONING_PROFILE_SPECIFIER = ""; 434 | SWIFT_VERSION = 5.0; 435 | }; 436 | name = Release; 437 | }; 438 | /* End XCBuildConfiguration section */ 439 | 440 | /* Begin XCConfigurationList section */ 441 | DE524D5C1EC47CB100E8C2F9 /* Build configuration list for PBXProject "Example" */ = { 442 | isa = XCConfigurationList; 443 | buildConfigurations = ( 444 | DE524D711EC47CB100E8C2F9 /* Debug */, 445 | DE524D721EC47CB100E8C2F9 /* Release */, 446 | ); 447 | defaultConfigurationIsVisible = 0; 448 | defaultConfigurationName = Release; 449 | }; 450 | DE524D731EC47CB100E8C2F9 /* Build configuration list for PBXNativeTarget "Example" */ = { 451 | isa = XCConfigurationList; 452 | buildConfigurations = ( 453 | DE524D741EC47CB100E8C2F9 /* Debug */, 454 | DE524D751EC47CB100E8C2F9 /* Release */, 455 | ); 456 | defaultConfigurationIsVisible = 0; 457 | defaultConfigurationName = Release; 458 | }; 459 | /* End XCConfigurationList section */ 460 | }; 461 | rootObject = DE524D591EC47CB100E8C2F9 /* Project object */; 462 | } 463 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | var window: UIWindow? 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 9 | return true 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Example/Example/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 | -------------------------------------------------------------------------------- /Example/Example/Base.lproj/Main.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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /Example/Example/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 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | NSAppTransportSecurity 45 | 46 | NSAllowsArbitraryLoads 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Example/Example/SwiftUI/SampleUI.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import MarkdownView 3 | 4 | struct SampleUI: View { 5 | var body: some View { 6 | ScrollView { 7 | MarkdownUI(body: markdown) 8 | .onTouchLink { link in 9 | print(link) 10 | return false 11 | } 12 | .onRendered { height in 13 | print(height) 14 | } 15 | } 16 | } 17 | 18 | private var markdown: String { 19 | let path = Bundle.main.path(forResource: "sample", ofType: "md")! 20 | let url = URL(fileURLWithPath: path) 21 | return try! String(contentsOf: url, encoding: String.Encoding.utf8) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Example/Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | 4 | final class ViewController: UIViewController { 5 | 6 | override func viewDidLoad() { 7 | super.viewDidLoad() 8 | 9 | let stackView = UIStackView() 10 | stackView.axis = .vertical 11 | 12 | let btn1 = UIButton() 13 | btn1.setTitle("Code Only Example", for: .normal) 14 | btn1.addTarget(self, action: #selector(openCodeOnlySample(sender:)), for: .touchUpInside) 15 | 16 | let btn2 = UIButton() 17 | btn2.setTitle("Storyboard Example", for: .normal) 18 | btn2.addTarget(self, action: #selector(openStoryboardSample(sender:)), for: .touchUpInside) 19 | 20 | let btn3 = UIButton() 21 | btn3.setTitle("ScrollView Example", for: .normal) 22 | btn3.addTarget(self, action: #selector(openScrollViewSample(sender:)), for: .touchUpInside) 23 | 24 | let btn4 = UIButton() 25 | btn4.setTitle("Custom CSS", for: .normal) 26 | btn4.addTarget(self, action: #selector(openCustomCssSample(sender:)), for: .touchUpInside) 27 | 28 | let btn5 = UIButton() 29 | btn5.setTitle("Add Plugin", for: .normal) 30 | btn5.addTarget(self, action: #selector(openPluginSample(sender:)), for: .touchUpInside) 31 | 32 | let btn6 = UIButton() 33 | btn6.setTitle("Non Styled", for: .normal) 34 | btn6.addTarget(self, action: #selector(openNonStyledSample(sender:)), for: .touchUpInside) 35 | 36 | let btn7 = UIButton() 37 | btn7.setTitle("Remote Stylesheet", for: .normal) 38 | btn7.addTarget(self, action: #selector(openRemoteStylesheetSample(sender:)), for: .touchUpInside) 39 | 40 | let btn8 = UIButton() 41 | btn8.setTitle("SwiftUI", for: .normal) 42 | btn8.addTarget(self, action: #selector(openSampleUI(sender:)), for: .touchUpInside) 43 | 44 | [ 45 | btn1, 46 | btn2, 47 | btn3, 48 | btn4, 49 | btn5, 50 | btn6, 51 | btn7, 52 | btn8 53 | ].forEach { button in 54 | 55 | button.setTitleColor(UIColor.systemBlue, for: .normal) 56 | button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) 57 | button.titleLabel?.font = UIFont.systemFont(ofSize: 20) 58 | stackView.addArrangedSubview(button) 59 | 60 | } 61 | 62 | view.addSubview(stackView) 63 | stackView.translatesAutoresizingMaskIntoConstraints = false 64 | stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 65 | stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 66 | } 67 | 68 | @objc func openCodeOnlySample(sender: Any) { 69 | let example = CodeOnlySampleViewController() 70 | navigationController?.pushViewController(example, animated: true) 71 | } 72 | 73 | @objc func openStoryboardSample(sender: Any) { 74 | let example = storyboard?.instantiateViewController( 75 | withIdentifier: "StoryboardSampleViewController") as! StoryboardSampleViewController 76 | navigationController?.pushViewController(example, animated: true) 77 | } 78 | 79 | @objc func openScrollViewSample(sender: Any) { 80 | let example = storyboard?.instantiateViewController( 81 | withIdentifier: "ScrollViewSampleViewController") as! ScrollViewSampleViewController 82 | navigationController?.pushViewController(example, animated: true) 83 | } 84 | 85 | @objc func openCustomCssSample(sender: Any) { 86 | let example = CustomCssSampleViewController() 87 | navigationController?.pushViewController(example, animated: true) 88 | } 89 | 90 | @objc func openPluginSample(sender: Any) { 91 | let example = PluginsSampleViewController() 92 | navigationController?.pushViewController(example, animated: true) 93 | } 94 | 95 | @objc func openNonStyledSample(sender: Any) { 96 | let example = NonStyledSampleViewController() 97 | navigationController?.pushViewController(example, animated: true) 98 | } 99 | 100 | @objc func openRemoteStylesheetSample(sender: Any) { 101 | let example = RemoteStyleSheetsSampleViewController() 102 | navigationController?.pushViewController(example, animated: true) 103 | } 104 | 105 | @objc func openSampleUI(sender: Any) { 106 | let example = UIHostingController(rootView: SampleUI()) 107 | navigationController?.pushViewController(example, animated: true) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Example/Example/ViewController/CodeOnly.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MarkdownView 3 | 4 | class CodeOnlySampleViewController: UIViewController { 5 | 6 | init() { 7 | super.init(nibName: nil, bundle: nil) 8 | view.backgroundColor = .systemBackground 9 | } 10 | 11 | required init?(coder: NSCoder) { 12 | fatalError("init(coder:) has not been implemented") 13 | } 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | let mdView = MarkdownView() 19 | view.addSubview(mdView) 20 | mdView.translatesAutoresizingMaskIntoConstraints = false 21 | mdView.topAnchor.constraint(equalTo: topLayoutGuide.topAnchor).isActive = true 22 | mdView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true 23 | mdView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 24 | mdView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true 25 | 26 | let path = Bundle.main.path(forResource: "sample", ofType: "md")! 27 | 28 | let url = URL(fileURLWithPath: path) 29 | let markdown = try! String(contentsOf: url, encoding: String.Encoding.utf8) 30 | mdView.load(markdown: markdown, enableImage: true) 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Example/Example/ViewController/CustomCss.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MarkdownView 3 | 4 | class CustomCssSampleViewController: UIViewController { 5 | 6 | init() { 7 | super.init(nibName: nil, bundle: nil) 8 | view.backgroundColor = .systemBackground 9 | } 10 | 11 | required init?(coder: NSCoder) { 12 | fatalError("init(coder:) has not been implemented") 13 | } 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | let css = [ 19 | "h1 { color:red; }", 20 | "h2 { color:green; }", 21 | "h3 { color:blue; }", 22 | ].joined(separator: "\n") 23 | 24 | let md = MarkdownView() 25 | view.addSubview(md) 26 | md.translatesAutoresizingMaskIntoConstraints = false 27 | NSLayoutConstraint.activate([ 28 | md.topAnchor.constraint(equalTo: view.topAnchor), 29 | md.leadingAnchor.constraint(equalTo: view.leadingAnchor), 30 | md.trailingAnchor.constraint(equalTo: view.trailingAnchor), 31 | md.bottomAnchor.constraint(equalTo: view.bottomAnchor) 32 | ]) 33 | 34 | let markdown = ["# h1 title", "## h2 title", "### h3 title"].joined(separator: "\n") 35 | md.load(markdown: markdown, enableImage: false, css: css, plugins: nil) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Example/Example/ViewController/NonStyled.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MarkdownView 3 | 4 | class NonStyledSampleViewController: UIViewController { 5 | 6 | init() { 7 | super.init(nibName: nil, bundle: nil) 8 | } 9 | 10 | required init?(coder: NSCoder) { 11 | fatalError("init(coder:) has not been implemented") 12 | } 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | view.backgroundColor = .systemBackground 17 | 18 | let md = MarkdownView() 19 | view.addSubview(md) 20 | md.translatesAutoresizingMaskIntoConstraints = false 21 | NSLayoutConstraint.activate([ 22 | md.topAnchor.constraint(equalTo: view.topAnchor), 23 | md.leadingAnchor.constraint(equalTo: view.leadingAnchor), 24 | md.trailingAnchor.constraint(equalTo: view.trailingAnchor), 25 | md.bottomAnchor.constraint(equalTo: view.bottomAnchor) 26 | ]) 27 | 28 | 29 | let markdown = """ 30 | # h1 Heading 8-) 31 | ## h2 Heading 32 | ### h3 Heading 33 | #### h4 Heading 34 | ##### h5 Heading 35 | ###### h6 Heading 36 | """ 37 | let css = try! String(contentsOf: URL(string: "https://raw.githubusercontent.com/gkroon/dracula-css/master/dracula.css")!, encoding: .utf8) 38 | md.load(markdown: markdown, css: css, styled: false) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Example/Example/ViewController/Plugins.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MarkdownView 3 | 4 | class PluginsSampleViewController: UIViewController { 5 | 6 | init() { 7 | super.init(nibName: nil, bundle: nil) 8 | } 9 | 10 | required init?(coder: NSCoder) { 11 | fatalError("init(coder:) has not been implemented") 12 | } 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | view.backgroundColor = .systemBackground 17 | 18 | let plugins = [ 19 | URL(string: "https://cdnjs.cloudflare.com/ajax/libs/markdown-it-footnote/3.0.3/markdown-it-footnote.js")!, 20 | URL(string: "https://cdn.jsdelivr.net/npm/markdown-it-sub@1.0.0/index.min.js")!, 21 | URL(string: "https://cdn.jsdelivr.net/npm/markdown-it-sup@1.0.0/index.min.js")!, 22 | ].map { 23 | try! String(contentsOf: $0, encoding: .utf8) 24 | } 25 | 26 | let md = MarkdownView() 27 | view.addSubview(md) 28 | md.translatesAutoresizingMaskIntoConstraints = false 29 | NSLayoutConstraint.activate([ 30 | md.topAnchor.constraint(equalTo: view.topAnchor), 31 | md.leadingAnchor.constraint(equalTo: view.leadingAnchor), 32 | md.trailingAnchor.constraint(equalTo: view.trailingAnchor), 33 | md.bottomAnchor.constraint(equalTo: view.bottomAnchor) 34 | ]) 35 | 36 | let markdown = """ 37 | # Plugins Sample 38 | ## Footnote 39 | Here is a footnote reference,[^1] and another.[^longnote] 40 | 41 | [^1]: Here is the footnote. 42 | 43 | [^longnote]: Here's one with multiple blocks. 44 | 45 | Subsequent paragraphs are indented to show that they 46 | belong to the previous footnote. 47 | 48 | ## Sub 49 | H~2~0 50 | 51 | ## Sup 52 | 29^th^ 53 | """ 54 | md.load(markdown: markdown, enableImage: false, plugins: plugins) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Example/Example/ViewController/RemoteStylesheets.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MarkdownView 3 | 4 | class RemoteStyleSheetsSampleViewController: UIViewController { 5 | 6 | init() { 7 | super.init(nibName: nil, bundle: nil) 8 | } 9 | 10 | required init?(coder: NSCoder) { 11 | fatalError("init(coder:) has not been implemented") 12 | } 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | view.backgroundColor = .systemBackground 17 | 18 | let md = MarkdownView() 19 | view.addSubview(md) 20 | md.translatesAutoresizingMaskIntoConstraints = false 21 | NSLayoutConstraint.activate([ 22 | md.topAnchor.constraint(equalTo: view.topAnchor), 23 | md.leadingAnchor.constraint(equalTo: view.leadingAnchor), 24 | md.trailingAnchor.constraint(equalTo: view.trailingAnchor), 25 | md.bottomAnchor.constraint(equalTo: view.bottomAnchor) 26 | ]) 27 | 28 | let markdown = """ 29 | # Katex 30 | 31 | $\\sqrt{3x-1}+(1+x)^2$ 32 | """ 33 | md.load(markdown: markdown, plugins: [js], stylesheets: [stylesheet]) 34 | } 35 | 36 | } 37 | 38 | extension RemoteStyleSheetsSampleViewController { 39 | var js: String { 40 | let url = URL(string: "https://raw.githubusercontent.com/keitaoouchi/markdownview-sample-plugin/master/dist/dst.js")! 41 | return try! String(contentsOf: url) 42 | } 43 | 44 | var stylesheet: URL { 45 | URL(string: "https://cdn.jsdelivr.net/npm/katex@0.13.18/dist/katex.min.css")! 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Example/Example/ViewController/ScrollView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MarkdownView 3 | import SafariServices 4 | 5 | class ScrollViewSampleViewController: UIViewController { 6 | 7 | @IBOutlet weak var mdView: MarkdownView! 8 | 9 | override func viewDidLoad() { 10 | super.viewDidLoad() 11 | 12 | mdView.isScrollEnabled = false 13 | 14 | mdView.onRendered = { height in 15 | print(height) 16 | } 17 | 18 | mdView.onTouchLink = { [weak self] request in 19 | guard let url = request.url else { return false } 20 | 21 | if url.scheme == "file" { 22 | return true 23 | } else if url.scheme == "https" { 24 | let safari = SFSafariViewController(url: url) 25 | self?.present(safari, animated: true, completion: nil) 26 | return false 27 | } else { 28 | return false 29 | } 30 | } 31 | 32 | let session = URLSession(configuration: .default) 33 | let url = URL(string: "https://raw.githubusercontent.com/matteocrippa/awesome-swift/master/README.md")! 34 | let task = session.dataTask(with: url) { [weak self] data, _, _ in 35 | let str = String(data: data!, encoding: String.Encoding.utf8) 36 | DispatchQueue.main.async { 37 | self?.mdView.load(markdown: str) 38 | } 39 | } 40 | task.resume() 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Example/Example/ViewController/Storyboard.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MarkdownView 3 | 4 | class StoryboardSampleViewController: UIViewController { 5 | 6 | @IBOutlet weak var mdView: MarkdownView! 7 | 8 | override func viewDidLoad() { 9 | super.viewDidLoad() 10 | 11 | let session = URLSession(configuration: .default) 12 | let url = URL(string: "https://raw.githubusercontent.com/matteocrippa/awesome-swift/master/README.md")! 13 | let task = session.dataTask(with: url) { [weak self] data, _, _ in 14 | let str = String(data: data!, encoding: String.Encoding.utf8) 15 | DispatchQueue.main.async { 16 | self?.mdView.load(markdown: str) 17 | } 18 | } 19 | task.resume() 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Example/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'cocoapods' 4 | -------------------------------------------------------------------------------- /Example/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.5) 5 | rexml 6 | activesupport (6.1.4.4) 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | i18n (>= 1.6, < 2) 9 | minitest (>= 5.1) 10 | tzinfo (~> 2.0) 11 | zeitwerk (~> 2.3) 12 | addressable (2.8.0) 13 | public_suffix (>= 2.0.2, < 5.0) 14 | algoliasearch (1.27.5) 15 | httpclient (~> 2.8, >= 2.8.3) 16 | json (>= 1.5.1) 17 | atomos (0.1.3) 18 | claide (1.0.3) 19 | cocoapods (1.11.2) 20 | addressable (~> 2.8) 21 | claide (>= 1.0.2, < 2.0) 22 | cocoapods-core (= 1.11.2) 23 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 24 | cocoapods-downloader (>= 1.4.0, < 2.0) 25 | cocoapods-plugins (>= 1.0.0, < 2.0) 26 | cocoapods-search (>= 1.0.0, < 2.0) 27 | cocoapods-trunk (>= 1.4.0, < 2.0) 28 | cocoapods-try (>= 1.1.0, < 2.0) 29 | colored2 (~> 3.1) 30 | escape (~> 0.0.4) 31 | fourflusher (>= 2.3.0, < 3.0) 32 | gh_inspector (~> 1.0) 33 | molinillo (~> 0.8.0) 34 | nap (~> 1.0) 35 | ruby-macho (>= 1.0, < 3.0) 36 | xcodeproj (>= 1.21.0, < 2.0) 37 | cocoapods-core (1.11.2) 38 | activesupport (>= 5.0, < 7) 39 | addressable (~> 2.8) 40 | algoliasearch (~> 1.0) 41 | concurrent-ruby (~> 1.1) 42 | fuzzy_match (~> 2.0.4) 43 | nap (~> 1.0) 44 | netrc (~> 0.11) 45 | public_suffix (~> 4.0) 46 | typhoeus (~> 1.0) 47 | cocoapods-deintegrate (1.0.5) 48 | cocoapods-downloader (1.5.1) 49 | cocoapods-plugins (1.0.0) 50 | nap 51 | cocoapods-search (1.0.1) 52 | cocoapods-trunk (1.6.0) 53 | nap (>= 0.8, < 2.0) 54 | netrc (~> 0.11) 55 | cocoapods-try (1.2.0) 56 | colored2 (3.1.2) 57 | concurrent-ruby (1.1.9) 58 | escape (0.0.4) 59 | ethon (0.15.0) 60 | ffi (>= 1.15.0) 61 | ffi (1.15.4) 62 | fourflusher (2.3.1) 63 | fuzzy_match (2.0.4) 64 | gh_inspector (1.1.3) 65 | httpclient (2.8.3) 66 | i18n (1.8.11) 67 | concurrent-ruby (~> 1.0) 68 | json (2.6.1) 69 | minitest (5.15.0) 70 | molinillo (0.8.0) 71 | nanaimo (0.3.0) 72 | nap (1.1.0) 73 | netrc (0.11.0) 74 | public_suffix (4.0.6) 75 | rexml (3.2.5) 76 | ruby-macho (2.5.1) 77 | typhoeus (1.4.0) 78 | ethon (>= 0.9.0) 79 | tzinfo (2.0.4) 80 | concurrent-ruby (~> 1.0) 81 | xcodeproj (1.21.0) 82 | CFPropertyList (>= 2.3.3, < 4.0) 83 | atomos (~> 0.1.3) 84 | claide (>= 1.0.2, < 2.0) 85 | colored2 (~> 3.1) 86 | nanaimo (~> 0.3.0) 87 | rexml (~> 3.2.4) 88 | zeitwerk (2.5.3) 89 | 90 | PLATFORMS 91 | ruby 92 | 93 | DEPENDENCIES 94 | cocoapods 95 | 96 | BUNDLED WITH 97 | 2.2.28 98 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '13.0' 3 | 4 | target 'Example' do 5 | use_modular_headers! 6 | 7 | pod 'MarkdownView', path: '../' 8 | end 9 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - MarkdownView (1.9.1) 3 | 4 | DEPENDENCIES: 5 | - MarkdownView (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | MarkdownView: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | MarkdownView: 22249778219cc22901f1454e8355298dd0bda7f0 13 | 14 | PODFILE CHECKSUM: aca8c849eff30cf020f460dc2fb5cc0b4a16e14a 15 | 16 | COCOAPODS: 1.11.2 17 | -------------------------------------------------------------------------------- /Example/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Example", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 keitaoouchi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /MarkdownView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "MarkdownView" 3 | s.version = "1.9.1" 4 | s.summary = "Markdown View for iOS." 5 | s.homepage = "https://github.com/keitaoouchi/MarkdownView" 6 | s.license = { :type => "MIT", :file => "LICENSE" } 7 | s.author = { "keitaoouchi" => "keita.oouchi@gmail.com" } 8 | s.source = { :git => "https://github.com/keitaoouchi/MarkdownView.git", :tag => "#{s.version}" } 9 | s.source_files = [ 10 | "Sources/MarkdownView/MarkdownView.swift", 11 | "Sources/MarkdownView/MarkdownUI.swift", 12 | ] 13 | s.resource_bundles = { 14 | 'MarkdownView' => [ 15 | 'Sources/MarkdownView/Resources/styled.html', 16 | 'Sources/MarkdownView/Resources/non_styled.html', 17 | 'Sources/MarkdownView/Resources/main.css', 18 | 'Sources/MarkdownView/Resources/main.js' 19 | ] 20 | } 21 | s.frameworks = "Foundation" 22 | s.ios.deployment_target = "13.0" 23 | s.swift_version = '5.2' 24 | end 25 | -------------------------------------------------------------------------------- /MarkdownView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3E3DF021278C4880009DD55A /* MarkdownUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E3DF020278C4880009DD55A /* MarkdownUI.swift */; }; 11 | FC9CC4C51EC43FA90013238C /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = FC9CC4BD1EC43FA90013238C /* index.html */; }; 12 | FC9CC4C61EC43FA90013238C /* main.css in Resources */ = {isa = PBXBuildFile; fileRef = FC9CC4BE1EC43FA90013238C /* main.css */; }; 13 | FC9CC4C71EC43FA90013238C /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = FC9CC4BF1EC43FA90013238C /* main.js */; }; 14 | FCA0501A1EC41211001DAD5F /* MarkdownView.h in Headers */ = {isa = PBXBuildFile; fileRef = FCA050181EC41211001DAD5F /* MarkdownView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | FCFB6FD51EC4381600297562 /* MarkdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCFB6FD21EC4381600297562 /* MarkdownView.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 3E3DF020278C4880009DD55A /* MarkdownUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownUI.swift; sourceTree = ""; }; 20 | FC9CC4BD1EC43FA90013238C /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = index.html; path = Sources/MarkdownView/Resources/index.html; sourceTree = SOURCE_ROOT; }; 21 | FC9CC4BE1EC43FA90013238C /* main.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = main.css; path = Sources/MarkdownView/Resources/main.css; sourceTree = SOURCE_ROOT; }; 22 | FC9CC4BF1EC43FA90013238C /* main.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = main.js; path = Sources/MarkdownView/Resources/main.js; sourceTree = SOURCE_ROOT; }; 23 | FCA050151EC41211001DAD5F /* MarkdownView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MarkdownView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | FCA050181EC41211001DAD5F /* MarkdownView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MarkdownView.h; path = ../MarkdownView.h; sourceTree = ""; }; 25 | FCA050191EC41211001DAD5F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../Info.plist; sourceTree = ""; }; 26 | FCFB6FD21EC4381600297562 /* MarkdownView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownView.swift; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | FCA050111EC41211001DAD5F /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | FCA0500B1EC41211001DAD5F = { 41 | isa = PBXGroup; 42 | children = ( 43 | FCA050171EC41211001DAD5F /* MarkdownView */, 44 | FCA050161EC41211001DAD5F /* Products */, 45 | ); 46 | sourceTree = ""; 47 | }; 48 | FCA050161EC41211001DAD5F /* Products */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | FCA050151EC41211001DAD5F /* MarkdownView.framework */, 52 | ); 53 | name = Products; 54 | sourceTree = ""; 55 | }; 56 | FCA050171EC41211001DAD5F /* MarkdownView */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | FCFB6FD21EC4381600297562 /* MarkdownView.swift */, 60 | 3E3DF020278C4880009DD55A /* MarkdownUI.swift */, 61 | FCA050181EC41211001DAD5F /* MarkdownView.h */, 62 | FCA050191EC41211001DAD5F /* Info.plist */, 63 | FCA050201EC4123C001DAD5F /* Assets */, 64 | ); 65 | name = MarkdownView; 66 | path = Sources/MarkdownView; 67 | sourceTree = ""; 68 | }; 69 | FCA050201EC4123C001DAD5F /* Assets */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | FC9CC4BD1EC43FA90013238C /* index.html */, 73 | FC9CC4BE1EC43FA90013238C /* main.css */, 74 | FC9CC4BF1EC43FA90013238C /* main.js */, 75 | ); 76 | name = Assets; 77 | sourceTree = ""; 78 | }; 79 | /* End PBXGroup section */ 80 | 81 | /* Begin PBXHeadersBuildPhase section */ 82 | FCA050121EC41211001DAD5F /* Headers */ = { 83 | isa = PBXHeadersBuildPhase; 84 | buildActionMask = 2147483647; 85 | files = ( 86 | FCA0501A1EC41211001DAD5F /* MarkdownView.h in Headers */, 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | /* End PBXHeadersBuildPhase section */ 91 | 92 | /* Begin PBXNativeTarget section */ 93 | FCA050141EC41211001DAD5F /* MarkdownView */ = { 94 | isa = PBXNativeTarget; 95 | buildConfigurationList = FCA0501D1EC41211001DAD5F /* Build configuration list for PBXNativeTarget "MarkdownView" */; 96 | buildPhases = ( 97 | FCA050101EC41211001DAD5F /* Sources */, 98 | FCA050111EC41211001DAD5F /* Frameworks */, 99 | FCA050121EC41211001DAD5F /* Headers */, 100 | FCA050131EC41211001DAD5F /* Resources */, 101 | ); 102 | buildRules = ( 103 | ); 104 | dependencies = ( 105 | ); 106 | name = MarkdownView; 107 | productName = MarkdownView; 108 | productReference = FCA050151EC41211001DAD5F /* MarkdownView.framework */; 109 | productType = "com.apple.product-type.framework"; 110 | }; 111 | /* End PBXNativeTarget section */ 112 | 113 | /* Begin PBXProject section */ 114 | FCA0500C1EC41211001DAD5F /* Project object */ = { 115 | isa = PBXProject; 116 | attributes = { 117 | LastUpgradeCheck = 0900; 118 | ORGANIZATIONNAME = com.keita.oouchi; 119 | TargetAttributes = { 120 | FCA050141EC41211001DAD5F = { 121 | CreatedOnToolsVersion = 8.3.2; 122 | LastSwiftMigration = 0900; 123 | ProvisioningStyle = Manual; 124 | }; 125 | }; 126 | }; 127 | buildConfigurationList = FCA0500F1EC41211001DAD5F /* Build configuration list for PBXProject "MarkdownView" */; 128 | compatibilityVersion = "Xcode 3.2"; 129 | developmentRegion = English; 130 | hasScannedForEncodings = 0; 131 | knownRegions = ( 132 | English, 133 | en, 134 | ); 135 | mainGroup = FCA0500B1EC41211001DAD5F; 136 | productRefGroup = FCA050161EC41211001DAD5F /* Products */; 137 | projectDirPath = ""; 138 | projectRoot = ""; 139 | targets = ( 140 | FCA050141EC41211001DAD5F /* MarkdownView */, 141 | ); 142 | }; 143 | /* End PBXProject section */ 144 | 145 | /* Begin PBXResourcesBuildPhase section */ 146 | FCA050131EC41211001DAD5F /* Resources */ = { 147 | isa = PBXResourcesBuildPhase; 148 | buildActionMask = 2147483647; 149 | files = ( 150 | FC9CC4C61EC43FA90013238C /* main.css in Resources */, 151 | FC9CC4C51EC43FA90013238C /* index.html in Resources */, 152 | FC9CC4C71EC43FA90013238C /* main.js in Resources */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXResourcesBuildPhase section */ 157 | 158 | /* Begin PBXSourcesBuildPhase section */ 159 | FCA050101EC41211001DAD5F /* Sources */ = { 160 | isa = PBXSourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | 3E3DF021278C4880009DD55A /* MarkdownUI.swift in Sources */, 164 | FCFB6FD51EC4381600297562 /* MarkdownView.swift in Sources */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXSourcesBuildPhase section */ 169 | 170 | /* Begin XCBuildConfiguration section */ 171 | FCA0501B1EC41211001DAD5F /* Debug */ = { 172 | isa = XCBuildConfiguration; 173 | buildSettings = { 174 | ALWAYS_SEARCH_USER_PATHS = NO; 175 | CLANG_ANALYZER_NONNULL = YES; 176 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 177 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 178 | CLANG_CXX_LIBRARY = "libc++"; 179 | CLANG_ENABLE_MODULES = YES; 180 | CLANG_ENABLE_OBJC_ARC = YES; 181 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 182 | CLANG_WARN_BOOL_CONVERSION = YES; 183 | CLANG_WARN_COMMA = YES; 184 | CLANG_WARN_CONSTANT_CONVERSION = YES; 185 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 186 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 187 | CLANG_WARN_EMPTY_BODY = YES; 188 | CLANG_WARN_ENUM_CONVERSION = YES; 189 | CLANG_WARN_INFINITE_RECURSION = YES; 190 | CLANG_WARN_INT_CONVERSION = YES; 191 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 192 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 193 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 194 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 195 | CLANG_WARN_STRICT_PROTOTYPES = YES; 196 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 197 | CLANG_WARN_UNREACHABLE_CODE = YES; 198 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 199 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 200 | COPY_PHASE_STRIP = NO; 201 | CURRENT_PROJECT_VERSION = 1; 202 | DEBUG_INFORMATION_FORMAT = dwarf; 203 | ENABLE_STRICT_OBJC_MSGSEND = YES; 204 | ENABLE_TESTABILITY = YES; 205 | GCC_C_LANGUAGE_STANDARD = gnu99; 206 | GCC_DYNAMIC_NO_PIC = NO; 207 | GCC_NO_COMMON_BLOCKS = YES; 208 | GCC_OPTIMIZATION_LEVEL = 0; 209 | GCC_PREPROCESSOR_DEFINITIONS = ( 210 | "DEBUG=1", 211 | "$(inherited)", 212 | ); 213 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 214 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 215 | GCC_WARN_UNDECLARED_SELECTOR = YES; 216 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 217 | GCC_WARN_UNUSED_FUNCTION = YES; 218 | GCC_WARN_UNUSED_VARIABLE = YES; 219 | MTL_ENABLE_DEBUG_INFO = YES; 220 | ONLY_ACTIVE_ARCH = YES; 221 | SDKROOT = iphoneos; 222 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 223 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 224 | TARGETED_DEVICE_FAMILY = "1,2"; 225 | VERSIONING_SYSTEM = "apple-generic"; 226 | VERSION_INFO_PREFIX = ""; 227 | }; 228 | name = Debug; 229 | }; 230 | FCA0501C1EC41211001DAD5F /* Release */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | ALWAYS_SEARCH_USER_PATHS = NO; 234 | CLANG_ANALYZER_NONNULL = YES; 235 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 237 | CLANG_CXX_LIBRARY = "libc++"; 238 | CLANG_ENABLE_MODULES = YES; 239 | CLANG_ENABLE_OBJC_ARC = YES; 240 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 241 | CLANG_WARN_BOOL_CONVERSION = YES; 242 | CLANG_WARN_COMMA = YES; 243 | CLANG_WARN_CONSTANT_CONVERSION = YES; 244 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 245 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 246 | CLANG_WARN_EMPTY_BODY = YES; 247 | CLANG_WARN_ENUM_CONVERSION = YES; 248 | CLANG_WARN_INFINITE_RECURSION = YES; 249 | CLANG_WARN_INT_CONVERSION = YES; 250 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 251 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 253 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 254 | CLANG_WARN_STRICT_PROTOTYPES = YES; 255 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 256 | CLANG_WARN_UNREACHABLE_CODE = YES; 257 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 258 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 259 | COPY_PHASE_STRIP = NO; 260 | CURRENT_PROJECT_VERSION = 1; 261 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 262 | ENABLE_NS_ASSERTIONS = NO; 263 | ENABLE_STRICT_OBJC_MSGSEND = YES; 264 | GCC_C_LANGUAGE_STANDARD = gnu99; 265 | GCC_NO_COMMON_BLOCKS = YES; 266 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 267 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 268 | GCC_WARN_UNDECLARED_SELECTOR = YES; 269 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 270 | GCC_WARN_UNUSED_FUNCTION = YES; 271 | GCC_WARN_UNUSED_VARIABLE = YES; 272 | MTL_ENABLE_DEBUG_INFO = NO; 273 | SDKROOT = iphoneos; 274 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 275 | TARGETED_DEVICE_FAMILY = "1,2"; 276 | VALIDATE_PRODUCT = YES; 277 | VERSIONING_SYSTEM = "apple-generic"; 278 | VERSION_INFO_PREFIX = ""; 279 | }; 280 | name = Release; 281 | }; 282 | FCA0501E1EC41211001DAD5F /* Debug */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | CLANG_ENABLE_MODULES = YES; 286 | CODE_SIGN_IDENTITY = ""; 287 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 288 | DEFINES_MODULE = YES; 289 | DEVELOPMENT_TEAM = ""; 290 | DYLIB_COMPATIBILITY_VERSION = 1; 291 | DYLIB_CURRENT_VERSION = 1; 292 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 293 | INFOPLIST_FILE = Sources/Info.plist; 294 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 295 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 296 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 297 | MARKETING_VERSION = 1.9.1; 298 | PRODUCT_BUNDLE_IDENTIFIER = com.keita.oouchi.MarkdownView; 299 | PRODUCT_NAME = "$(TARGET_NAME)"; 300 | PROVISIONING_PROFILE_SPECIFIER = ""; 301 | SKIP_INSTALL = YES; 302 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 303 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 304 | SWIFT_VERSION = 4.2; 305 | }; 306 | name = Debug; 307 | }; 308 | FCA0501F1EC41211001DAD5F /* Release */ = { 309 | isa = XCBuildConfiguration; 310 | buildSettings = { 311 | CLANG_ENABLE_MODULES = YES; 312 | CODE_SIGN_IDENTITY = ""; 313 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 314 | DEFINES_MODULE = YES; 315 | DEVELOPMENT_TEAM = ""; 316 | DYLIB_COMPATIBILITY_VERSION = 1; 317 | DYLIB_CURRENT_VERSION = 1; 318 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 319 | GCC_OPTIMIZATION_LEVEL = fast; 320 | INFOPLIST_FILE = Sources/Info.plist; 321 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 322 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 323 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 324 | MARKETING_VERSION = 1.9.1; 325 | PRODUCT_BUNDLE_IDENTIFIER = com.keita.oouchi.MarkdownView; 326 | PRODUCT_NAME = "$(TARGET_NAME)"; 327 | PROVISIONING_PROFILE_SPECIFIER = ""; 328 | SKIP_INSTALL = YES; 329 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 330 | SWIFT_VERSION = 4.2; 331 | }; 332 | name = Release; 333 | }; 334 | /* End XCBuildConfiguration section */ 335 | 336 | /* Begin XCConfigurationList section */ 337 | FCA0500F1EC41211001DAD5F /* Build configuration list for PBXProject "MarkdownView" */ = { 338 | isa = XCConfigurationList; 339 | buildConfigurations = ( 340 | FCA0501B1EC41211001DAD5F /* Debug */, 341 | FCA0501C1EC41211001DAD5F /* Release */, 342 | ); 343 | defaultConfigurationIsVisible = 0; 344 | defaultConfigurationName = Release; 345 | }; 346 | FCA0501D1EC41211001DAD5F /* Build configuration list for PBXNativeTarget "MarkdownView" */ = { 347 | isa = XCConfigurationList; 348 | buildConfigurations = ( 349 | FCA0501E1EC41211001DAD5F /* Debug */, 350 | FCA0501F1EC41211001DAD5F /* Release */, 351 | ); 352 | defaultConfigurationIsVisible = 0; 353 | defaultConfigurationName = Release; 354 | }; 355 | /* End XCConfigurationList section */ 356 | }; 357 | rootObject = FCA0500C1EC41211001DAD5F /* Project object */; 358 | } 359 | -------------------------------------------------------------------------------- /MarkdownView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MarkdownView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MarkdownView.xcodeproj/xcshareddata/xcschemes/MarkdownView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "MarkdownView", 8 | platforms: [ 9 | .iOS(.v13), 10 | ], 11 | products: [ 12 | .library( 13 | name: "MarkdownView", 14 | targets: ["MarkdownView"] 15 | ), 16 | ], 17 | targets: [ 18 | .target( 19 | name: "MarkdownView", 20 | path: "Sources/MarkdownView", 21 | exclude: [ 22 | "Resources/main.js.LICENSE.txt" 23 | ], 24 | resources: [ 25 | .copy("Resources/styled.html"), 26 | .copy("Resources/non_styled.html"), 27 | .copy("Resources/main.js"), 28 | .copy("Resources/main.css") 29 | ] 30 | ) 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MarkdownView 2 | 3 | [![CI Status](http://img.shields.io/travis/keitaoouchi/MArkdownView.svg?style=flat)](https://travis-ci.org/keitaoouchi/MarkdownView) 4 | [![Swift 5.2](https://img.shields.io/badge/Swift-5.2-orange.svg?style=flat)](https://swift.org/) 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | [![Version](https://img.shields.io/cocoapods/v/MarkdownView.svg?style=flat)](http://cocoapods.org/pods/MarkdownView) 7 | [![License](https://img.shields.io/cocoapods/l/MarkdownView.svg?style=flat)](http://cocoapods.org/pods/MarkdownView) 8 | [![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) 9 | 10 | > MarkdownView is a WKWebView based UI element, and internally use markdown-it, highlight-js. 11 | 12 | ![GIF](https://github.com/keitaoouchi/MarkdownView/blob/master/sample.gif "GIF") 13 | 14 | ## How to use 15 | 16 | #### UIViewController 17 | 18 | ```swift 19 | import MarkdownView 20 | 21 | let md = MarkdownView() 22 | md.load(markdown: "# Hello World!") 23 | ``` 24 | 25 | #### SwiftUI 26 | 27 | ```swift 28 | import SwiftUI 29 | import MarkdownView 30 | 31 | struct SampleUI: View { 32 | var body: some View { 33 | ScrollView { 34 | MarkdownUI(body: markdown) 35 | .onTouchLink { link in 36 | print(link) 37 | return false 38 | } 39 | .onRendered { height in 40 | print(height) 41 | } 42 | } 43 | } 44 | 45 | private var markdown: String { 46 | let path = Bundle.main.path(forResource: "sample", ofType: "md")! 47 | let url = URL(fileURLWithPath: path) 48 | return try! String(contentsOf: url, encoding: String.Encoding.utf8) 49 | } 50 | } 51 | 52 | ``` 53 | 54 | ### Options 55 | 56 | ```swift 57 | md.isScrollEnabled = false 58 | 59 | // called when rendering finished 60 | md.onRendered = { [weak self] height in 61 | self?.mdViewHeight.constant = height 62 | self?.view.setNeedsLayout() 63 | } 64 | 65 | // called when user touch link 66 | md.onTouchLink = { [weak self] request in 67 | guard let url = request.url else { return false } 68 | 69 | if url.scheme == "file" { 70 | return false 71 | } else if url.scheme == "https" { 72 | let safari = SFSafariViewController(url: url) 73 | self?.navigationController?.pushViewController(safari, animated: true) 74 | return false 75 | } else { 76 | return false 77 | } 78 | } 79 | ``` 80 | 81 | ### Experimental Features 82 | 83 | This is not stable :bow: 84 | 85 | #### Custom CSS Styling 86 | 87 | Please check [Example/ViewController/CustomCss.swift](https://github.com/keitaoouchi/MarkdownView/blob/master/Example/Example/ViewController/CustomCss.swift). 88 | 89 | 90 | 91 | #### Plugins 92 | 93 | Please check [Example/ViewController/Plugins.swift](https://github.com/keitaoouchi/MarkdownView/blob/master/Example/Example/ViewController/Plugins.swift). 94 | Each plugin should be self-contained, with no external dependent plugins. 95 | 96 | 97 | 98 | [Here](https://github.com/keitaoouchi/markdownview-sample-plugin) is a sample project that builds `markdown-it-new-katex` as a compatible library. 99 | 100 | ## Requirements 101 | 102 | | Target | Version | 103 | |-------------------|---------| 104 | | iOS | => 13.0 | 105 | | Swift | => 5.2 | 106 | 107 | ## Installation 108 | 109 | MarkdownView is available through [Swift Package Manager](https://swift.org/package-manager/) or [CocoaPods](http://cocoapods.org) or [Carthage](https://github.com/Carthage/Carthage). 110 | 111 | ### Swift Package Manager 112 | 113 | ```swift 114 | dependencies: [ 115 | .package(url: "https://github.com/keitaoouchi/MarkdownView.git", from: "1.7.1") 116 | ] 117 | ``` 118 | Alternatively, you can add the package directly via Xcode. 119 | 120 | ### CocoaPods 121 | 122 | ```ruby 123 | pod "MarkdownView" 124 | ``` 125 | 126 | ### Carthage 127 | 128 | ``` 129 | github "keitaoouchi/MarkdownView" 130 | ``` 131 | 132 | for detail, please follow the [Carthage Instruction](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos) 133 | 134 | 135 | ## Author 136 | 137 | keita.oouchi, keita.oouchi@gmail.com 138 | 139 | ## License 140 | 141 | [bootstrap](http://getbootstrap.com/) is licensed under [MIT license](https://github.com/twbs/bootstrap/blob/v4-dev/LICENSE). 142 | [highlight.js](https://highlightjs.org/) is licensed under [BSD-3-Clause license](https://github.com/isagalaev/highlight.js/blob/master/LICENSE). 143 | [markdown-it](https://markdown-it.github.io/) is licensed under [MIT license](https://github.com/markdown-it/markdown-it/blob/master/LICENSE). 144 | 145 | MarkdownView is available under the MIT license. See the LICENSE file for more info. 146 | -------------------------------------------------------------------------------- /Sources/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 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sources/MarkdownView.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | FOUNDATION_EXPORT double MarkdownViewVersionNumber; 4 | FOUNDATION_EXPORT const unsigned char MarkdownViewVersionString[]; 5 | 6 | // In this header, you should import all the public headers of your framework using statements like #import 7 | -------------------------------------------------------------------------------- /Sources/MarkdownView/MarkdownUI.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public final class MarkdownUI: UIViewRepresentable { 4 | private let markdownView: MarkdownView 5 | 6 | @Binding public var body: String 7 | 8 | public init(body: String? = nil, css: String? = nil, plugins: [String]? = nil, stylesheets: [URL]? = nil, styled: Bool = true) { 9 | self._body = .constant(body ?? "") 10 | self.markdownView = MarkdownView(css: css, plugins: plugins, stylesheets: stylesheets, styled: styled) 11 | self.markdownView.isScrollEnabled = false 12 | } 13 | 14 | public func onTouchLink(perform action: @escaping ((URLRequest) -> Bool)) -> MarkdownUI { 15 | self.markdownView.onTouchLink = action 16 | return self 17 | } 18 | 19 | public func onRendered(perform action: @escaping ((CGFloat) -> Void)) -> MarkdownUI { 20 | self.markdownView.onRendered = action 21 | return self 22 | } 23 | } 24 | 25 | extension MarkdownUI { 26 | 27 | public func makeUIView(context: Context) -> MarkdownView { 28 | return markdownView 29 | } 30 | 31 | public func updateUIView(_ uiView: MarkdownView, context: Context) { 32 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { 33 | self.markdownView.show(markdown: self.body) 34 | } 35 | } 36 | 37 | public func makeCoordinator() -> () { 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/MarkdownView/MarkdownView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import WebKit 3 | 4 | /** 5 | Markdown View for iOS. 6 | 7 | - Note: [How to get height of entire document with javascript](https://stackoverflow.com/questions/1145850/how-to-get-height-of-entire-document-with-javascript) 8 | */ 9 | open class MarkdownView: UIView { 10 | 11 | private var webView: WKWebView? 12 | private var updateHeightHandler: UpdateHeightHandler? 13 | 14 | private var intrinsicContentHeight: CGFloat? { 15 | didSet { 16 | self.invalidateIntrinsicContentSize() 17 | } 18 | } 19 | 20 | @objc public var isScrollEnabled: Bool = true { 21 | didSet { 22 | webView?.scrollView.isScrollEnabled = isScrollEnabled 23 | } 24 | } 25 | 26 | @objc public var onTouchLink: ((URLRequest) -> Bool)? 27 | 28 | @objc public var onRendered: ((CGFloat) -> Void)? 29 | 30 | public convenience init() { 31 | self.init(frame: .zero) 32 | } 33 | 34 | /// Reserve a web view before displaying markdown. 35 | /// You can use this for performance optimization. 36 | /// 37 | /// - Note: `webView` needs complete loading before invoking `show` method. 38 | public convenience init(css: String?, plugins: [String]?, stylesheets: [URL]? = nil, styled: Bool = true) { 39 | self.init(frame: .zero) 40 | 41 | let configuration = WKWebViewConfiguration() 42 | configuration.userContentController = makeContentController(css: css, plugins: plugins, stylesheets: stylesheets, markdown: nil, enableImage: nil) 43 | if let handler = updateHeightHandler { 44 | configuration.userContentController.add(handler, name: "updateHeight") 45 | } 46 | self.webView = makeWebView(with: configuration) 47 | self.webView?.load(URLRequest(url: styled ? Self.styledHtmlUrl : Self.nonStyledHtmlUrl)) 48 | } 49 | 50 | override init (frame: CGRect) { 51 | super.init(frame : frame) 52 | 53 | let updateHeightHandler = UpdateHeightHandler { [weak self] height in 54 | guard height > self?.intrinsicContentHeight ?? 0 else { return } 55 | self?.onRendered?(height) 56 | self?.intrinsicContentHeight = height 57 | } 58 | self.updateHeightHandler = updateHeightHandler 59 | } 60 | 61 | public required init?(coder aDecoder: NSCoder) { 62 | super.init(coder: aDecoder) 63 | } 64 | } 65 | 66 | extension MarkdownView { 67 | open override var intrinsicContentSize: CGSize { 68 | if let height = self.intrinsicContentHeight { 69 | return CGSize(width: UIView.noIntrinsicMetric, height: height) 70 | } else { 71 | return CGSize.zero 72 | } 73 | } 74 | 75 | /// Load markdown with a newly configured webView. 76 | /// 77 | /// If you want to preserve already applied css or plugins, use `show` instead. 78 | @objc public func load(markdown: String?, enableImage: Bool = true, css: String? = nil, plugins: [String]? = nil, stylesheets: [URL]? = nil, styled: Bool = true) { 79 | guard let markdown = markdown else { return } 80 | 81 | self.webView?.removeFromSuperview() 82 | let configuration = WKWebViewConfiguration() 83 | configuration.userContentController = makeContentController(css: css, plugins: plugins, stylesheets: stylesheets, markdown: markdown, enableImage: enableImage) 84 | if let handler = updateHeightHandler { 85 | configuration.userContentController.add(handler, name: "updateHeight") 86 | } 87 | self.webView = makeWebView(with: configuration) 88 | self.webView?.load(URLRequest(url: styled ? Self.styledHtmlUrl : Self.nonStyledHtmlUrl)) 89 | } 90 | 91 | public func show(markdown: String) { 92 | guard let webView = webView else { return } 93 | 94 | let escapedMarkdown = self.escape(markdown: markdown) ?? "" 95 | let script = "window.showMarkdown('\(escapedMarkdown)', true);" 96 | webView.evaluateJavaScript(script) { _, error in 97 | guard let error = error else { return } 98 | print("[MarkdownView][Error] \(error)") 99 | } 100 | } 101 | } 102 | 103 | // MARK: - WKNavigationDelegate 104 | 105 | extension MarkdownView: WKNavigationDelegate { 106 | 107 | public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 108 | 109 | switch navigationAction.navigationType { 110 | case .linkActivated: 111 | if let onTouchLink = onTouchLink, onTouchLink(navigationAction.request) { 112 | decisionHandler(.allow) 113 | } else { 114 | decisionHandler(.cancel) 115 | } 116 | default: 117 | decisionHandler(.allow) 118 | } 119 | 120 | } 121 | } 122 | 123 | // MARK: - 124 | private class UpdateHeightHandler: NSObject, WKScriptMessageHandler { 125 | var onUpdate: ((CGFloat) -> Void) 126 | 127 | init(onUpdate: @escaping (CGFloat) -> Void) { 128 | self.onUpdate = onUpdate 129 | } 130 | 131 | public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 132 | switch message.name { 133 | default: 134 | if let height = message.body as? CGFloat { 135 | self.onUpdate(height) 136 | } 137 | } 138 | } 139 | } 140 | 141 | // MARK: - Scripts 142 | 143 | private extension MarkdownView { 144 | 145 | func styleScript(_ css: String) -> String { 146 | [ 147 | "var s = document.createElement('style');", 148 | "s.innerHTML = `\(css)`;", 149 | "document.head.appendChild(s);" 150 | ].joined() 151 | } 152 | 153 | func linkScript(_ url: URL) -> String { 154 | [ 155 | "var link = document.createElement('link');", 156 | "link.href = '\(url.absoluteURL)';", 157 | "link.rel = 'stylesheet';", 158 | "document.head.appendChild(link);" 159 | ].joined() 160 | } 161 | 162 | func usePluginScript(_ pluginBody: String) -> String { 163 | """ 164 | var _module = {}; 165 | var _exports = {}; 166 | (function(module, exports) { 167 | \(pluginBody) 168 | })(_module, _exports); 169 | window.usePlugin(_module.exports || _exports); 170 | """ 171 | } 172 | } 173 | 174 | // MARK: - Misc 175 | 176 | private extension MarkdownView { 177 | static var styledHtmlUrl: URL = { 178 | #if SWIFT_PACKAGE 179 | let bundle = Bundle.module 180 | #else 181 | let bundle = Bundle(for: MarkdownView.self) 182 | #endif 183 | return bundle.url(forResource: "styled", 184 | withExtension: "html") ?? 185 | bundle.url(forResource: "styled", 186 | withExtension: "html", 187 | subdirectory: "MarkdownView.bundle")! 188 | }() 189 | 190 | static var nonStyledHtmlUrl: URL = { 191 | #if SWIFT_PACKAGE 192 | let bundle = Bundle.module 193 | #else 194 | let bundle = Bundle(for: MarkdownView.self) 195 | #endif 196 | return bundle.url(forResource: "non_styled", 197 | withExtension: "html") ?? 198 | bundle.url(forResource: "non_styled", 199 | withExtension: "html", 200 | subdirectory: "MarkdownView.bundle")! 201 | }() 202 | 203 | func escape(markdown: String) -> String? { 204 | return markdown.addingPercentEncoding(withAllowedCharacters: CharacterSet.alphanumerics) 205 | } 206 | 207 | func makeWebView(with configuration: WKWebViewConfiguration) -> WKWebView { 208 | let wv = WKWebView(frame: self.bounds, configuration: configuration) 209 | wv.scrollView.isScrollEnabled = self.isScrollEnabled 210 | wv.translatesAutoresizingMaskIntoConstraints = false 211 | wv.navigationDelegate = self 212 | addSubview(wv) 213 | wv.topAnchor.constraint(equalTo: self.topAnchor).isActive = true 214 | wv.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true 215 | wv.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true 216 | wv.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true 217 | wv.isOpaque = false 218 | wv.backgroundColor = .clear 219 | wv.scrollView.backgroundColor = .clear 220 | return wv 221 | } 222 | 223 | func makeContentController(css: String?, 224 | plugins: [String]?, 225 | stylesheets: [URL]?, 226 | markdown: String?, 227 | enableImage: Bool?) -> WKUserContentController { 228 | let controller = WKUserContentController() 229 | 230 | if let css = css { 231 | let styleInjection = WKUserScript(source: styleScript(css), injectionTime: .atDocumentEnd, forMainFrameOnly: true) 232 | controller.addUserScript(styleInjection) 233 | } 234 | 235 | plugins?.forEach({ plugin in 236 | let scriptInjection = WKUserScript(source: usePluginScript(plugin), injectionTime: .atDocumentEnd, forMainFrameOnly: true) 237 | controller.addUserScript(scriptInjection) 238 | }) 239 | 240 | stylesheets?.forEach({ url in 241 | let linkInjection = WKUserScript(source: linkScript(url), injectionTime: .atDocumentEnd, forMainFrameOnly: true) 242 | controller.addUserScript(linkInjection) 243 | }) 244 | 245 | if let markdown = markdown { 246 | let escapedMarkdown = self.escape(markdown: markdown) ?? "" 247 | let imageOption = (enableImage ?? true) ? "true" : "false" 248 | let script = "window.showMarkdown('\(escapedMarkdown)', \(imageOption));" 249 | let userScript = WKUserScript(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: true) 250 | controller.addUserScript(userScript) 251 | } 252 | 253 | return controller 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /Sources/MarkdownView/Resources/main.css: -------------------------------------------------------------------------------- 1 | html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;font-family:sans-serif}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*,:after,:before{background:transparent!important;-webkit-box-shadow:none!important;box-shadow:none!important;color:#000!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}*,:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{-webkit-tap-highlight-color:rgba(0,0,0,0);font-size:10px}body{font:-apple-system-body}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;height:auto;max-width:100%}.img-rounded{border-radius:6px}.img-thumbnail{background-color:#fff;border:1px solid #ddd;border-radius:4px;display:inline-block;height:auto;line-height:1.42857143;max-width:100%;padding:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{border:0;border-top:1px solid #eee;margin-bottom:20px;margin-top:20px}.sr-only{clip:rect(0,0,0,0);border:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{color:inherit;font-family:inherit;font-weight:500;line-height:1.1}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{color:#777;font-weight:400;line-height:1}.h1,.h2,.h3,h1,h2,h3{margin-bottom:10px;margin-top:20px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-bottom:10px;margin-top:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{font-size:16px;font-weight:300;line-height:1.4;margin-bottom:20px}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{background-color:#337ab7;color:#fff}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{border-bottom:1px solid #eee;margin:40px 0 20px;padding-bottom:9px}ol,ul{margin-bottom:10px;margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-inline,.list-unstyled{list-style:none;padding-left:0}.list-inline{margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-bottom:20px;margin-top:0}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{clear:left;float:left;overflow:hidden;text-align:right;text-overflow:ellipsis;white-space:nowrap;width:160px}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{border-bottom:1px dotted #777;cursor:help}.initialism{font-size:90%;text-transform:uppercase}blockquote{border-left:5px solid #eee;font-size:17.5px;margin:0 0 20px;padding:10px 20px}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{color:#777;display:block;font-size:80%;line-height:1.42857143}blockquote .small:before,blockquote footer:before,blockquote small:before{content:"\2014 \00A0"}.blockquote-reverse,blockquote.pull-right{border-left:0;border-right:5px solid #eee;padding-left:0;padding-right:15px;text-align:right}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:""}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:"\00A0 \2014"}address{font-style:normal;line-height:1.42857143;margin-bottom:20px}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,Courier New,monospace}code{background-color:#f9f2f4;border-radius:4px;color:#c7254e}code,kbd{font-size:90%;padding:2px 4px}kbd{background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);color:#fff}kbd kbd{-webkit-box-shadow:none;box-shadow:none;font-size:100%;font-weight:700;padding:0}pre{word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px;color:#333;display:block;font-size:13px;line-height:1.42857143;margin:0 0 10px;padding:9.5px;word-break:break-all}pre code{background-color:transparent;border-radius:0;color:inherit;font-size:inherit;padding:0;white-space:pre-wrap}.pre-scrollable{max-height:340px;overflow-y:scroll}table{background-color:transparent}caption{color:#777;padding-bottom:8px;padding-top:8px}caption,th{text-align:left}.table{margin-bottom:20px;max-width:100%;width:100%}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{border-top:1px solid #ddd;line-height:1.42857143;padding:8px;vertical-align:top}.table>thead>tr>th{border-bottom:2px solid #ddd;vertical-align:bottom}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered,.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{display:table-column;float:none;position:static}table td[class*=col-],table th[class*=col-]{display:table-cell;float:none;position:static}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd;margin-bottom:15px;overflow-y:hidden;width:100%}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}.clearfix:after,.clearfix:before,.dl-horizontal dd:after,.dl-horizontal dd:before{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{background-color:transparent;border:0;color:transparent;font:0/0 a;text-shadow:none}.hidden{display:none!important}.affix{position:fixed}.hljs{background:#fff}.bash .hljs-shebang,.hljs-comment,.java .hljs-javadoc,.javascript .hljs-javadoc,.rust .hljs-preprocessor{color:#969896}.apache .hljs-sqbracket,.c .hljs-preprocessor,.coffeescript .hljs-regexp,.coffeescript .hljs-subst,.cpp .hljs-preprocessor,.hljs-string,.javascript .hljs-regexp,.json .hljs-attribute,.less .hljs-built_in,.makefile .hljs-variable,.markdown .hljs-blockquote,.markdown .hljs-emphasis,.markdown .hljs-link_label,.markdown .hljs-strong,.markdown .hljs-value,.nginx .hljs-number,.nginx .hljs-regexp,.objectivec .hljs-preprocessor .hljs-title,.perl .hljs-regexp,.php .hljs-regexp,.scss .hljs-built_in,.xml .hljs-value{color:#df5000}.css .hljs-at_rule,.css .hljs-important,.go .hljs-typename,.haskell .hljs-type,.hljs-keyword,.http .hljs-request,.ini .hljs-setting,.java .hljs-javadoctag,.javascript .hljs-javadoctag,.javascript .hljs-tag,.less .hljs-at_rule,.less .hljs-tag,.nginx .hljs-title,.objectivec .hljs-preprocessor,.php .hljs-phpdoc,.scss .hljs-at_rule,.scss .hljs-important,.scss .hljs-tag,.sql .hljs-built_in,.stylus .hljs-at_rule,.swift .hljs-preprocessor{color:#a71d5d}.apache .hljs-cbracket,.apache .hljs-common,.apache .hljs-keyword,.bash .hljs-built_in,.bash .hljs-literal,.c .hljs-built_in,.c .hljs-number,.coffeescript .hljs-built_in,.coffeescript .hljs-literal,.coffeescript .hljs-number,.cpp .hljs-built_in,.cpp .hljs-number,.cs .hljs-built_in,.cs .hljs-number,.css .hljs-attribute,.css .hljs-function,.css .hljs-hexcolor,.css .hljs-number,.go .hljs-built_in,.go .hljs-constant,.haskell .hljs-number,.http .hljs-attribute,.http .hljs-literal,.java .hljs-number,.javascript .hljs-built_in,.javascript .hljs-literal,.javascript .hljs-number,.json .hljs-number,.less .hljs-attribute,.less .hljs-function,.less .hljs-hexcolor,.less .hljs-number,.makefile .hljs-keyword,.markdown .hljs-link_reference,.nginx .hljs-built_in,.objectivec .hljs-built_in,.objectivec .hljs-literal,.objectivec .hljs-number,.php .hljs-literal,.php .hljs-number,.puppet .hljs-function,.python .hljs-number,.ruby .hljs-constant,.ruby .hljs-number,.ruby .hljs-prompt,.ruby .hljs-subst .hljs-keyword,.ruby .hljs-symbol,.rust .hljs-number,.scss .hljs-attribute,.scss .hljs-function,.scss .hljs-hexcolor,.scss .hljs-number,.scss .hljs-preprocessor,.sql .hljs-number,.stylus .hljs-attribute,.stylus .hljs-hexcolor,.stylus .hljs-number,.stylus .hljs-params,.swift .hljs-built_in,.swift .hljs-number{color:#0086b3}.apache .hljs-tag,.cs .hljs-xmlDocTag,.css .hljs-tag,.stylus .hljs-tag,.xml .hljs-title{color:#63a35c}.bash .hljs-variable,.cs .hljs-preprocessor,.cs .hljs-preprocessor .hljs-keyword,.css .hljs-attr_selector,.css .hljs-value,.ini .hljs-keyword,.ini .hljs-value,.javascript .hljs-tag .hljs-title,.makefile .hljs-constant,.nginx .hljs-variable,.scss .hljs-variable,.xml .hljs-tag{color:#333}.bash .hljs-title,.c .hljs-title,.coffeescript .hljs-title,.cpp .hljs-title,.cs .hljs-title,.css .hljs-class,.css .hljs-id,.css .hljs-pseudo,.diff .hljs-chunk,.haskell .hljs-pragma,.haskell .hljs-title,.ini .hljs-title,.java .hljs-title,.javascript .hljs-title,.less .hljs-class,.less .hljs-id,.less .hljs-pseudo,.makefile .hljs-title,.objectivec .hljs-title,.perl .hljs-sub,.php .hljs-title,.puppet .hljs-title,.python .hljs-decorator,.python .hljs-title,.ruby .hljs-parent,.ruby .hljs-title,.rust .hljs-title,.scss .hljs-class,.scss .hljs-id,.scss .hljs-pseudo,.stylus .hljs-class,.stylus .hljs-id,.stylus .hljs-pseudo,.stylus .hljs-title,.swift .hljs-title,.xml .hljs-attribute{color:#795da3}.coffeescript .hljs-attribute,.coffeescript .hljs-reserved{color:#1d3e81}.diff .hljs-chunk{font-weight:700}.diff .hljs-addition{background-color:#eaffea;color:#55a532}.diff .hljs-deletion{background-color:#ffecec;color:#bd2c00}.markdown .hljs-link_url{text-decoration:underline}.hljs{-webkit-text-size-adjust:none;background:#f8f8f8;color:#333;display:block;overflow-x:auto;padding:.5em}.diff .hljs-header,.hljs-comment{color:#998;font-style:italic}.css .rule .hljs-keyword,.hljs-keyword,.hljs-request,.hljs-status,.hljs-subst,.hljs-winutils,.nginx .hljs-title{color:#333;font-weight:700}.hljs-hexcolor,.hljs-number,.ruby .hljs-constant{color:teal}.hljs-doctag,.hljs-string,.hljs-tag .hljs-value,.tex .hljs-formula{color:#d14}.hljs-id,.hljs-title,.scss .hljs-preprocessor{color:#900;font-weight:700}.hljs-list .hljs-keyword,.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type,.tex .hljs-command,.vhdl .hljs-literal{color:#458;font-weight:700}.django .hljs-tag .hljs-keyword,.hljs-rule .hljs-property,.hljs-tag,.hljs-tag .hljs-title{color:navy;font-weight:400}.hljs-attribute,.hljs-name,.hljs-variable,.lisp .hljs-body{color:teal}.hljs-regexp{color:#009926}.clojure .hljs-keyword,.hljs-prompt,.hljs-symbol,.lisp .hljs-keyword,.ruby .hljs-symbol .hljs-string,.scheme .hljs-keyword,.tex .hljs-special{color:#990073}.hljs-built_in{color:#0086b3}.hljs-cdata,.hljs-doctype,.hljs-pi,.hljs-pragma,.hljs-preprocessor,.hljs-shebang{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.diff .hljs-change{background:#0086b3}.hljs-chunk{color:#aaa}img{max-width:100%}*{-webkit-touch-callout:none}.container{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px}:root{--background-color:-apple-system-background;--color:-apple-system-label;color-scheme:light dark}@media screen and (prefers-color-scheme:dark){a{color:#2997ff}a:focus,a:hover{color:#006dd3}}body{background-color:var(--background);color:var(--color)} -------------------------------------------------------------------------------- /Sources/MarkdownView/Resources/non_styled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /Sources/MarkdownView/Resources/styled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keitaoouchi/MarkdownView/5e62a27f1002cd63c350ef1a4c640ddcd1f1ed48/sample.gif -------------------------------------------------------------------------------- /sample.md: -------------------------------------------------------------------------------- 1 | # h1 Heading 8-) 2 | ## h2 Heading 3 | ### h3 Heading 4 | #### h4 Heading 5 | ##### h5 Heading 6 | ###### h6 Heading 7 | 8 | 9 | ## Horizontal Rules 10 | 11 | ___ 12 | 13 | --- 14 | 15 | *** 16 | 17 | 18 | ## Emphasis 19 | 20 | **This is bold text** 21 | 22 | __This is bold text__ 23 | 24 | *This is italic text* 25 | 26 | _This is italic text_ 27 | 28 | ~~Strikethrough~~ 29 | 30 | 31 | ## Blockquotes 32 | 33 | 34 | > Blockquotes can also be nested... 35 | >> ...by using additional greater-than signs right next to each other... 36 | > > > ...or with spaces between arrows. 37 | 38 | 39 | ## Lists 40 | 41 | Unordered 42 | 43 | + Create a list by starting a line with `+`, `-`, or `*` 44 | + Sub-lists are made by indenting 2 spaces: 45 | - Marker character change forces new list start: 46 | * Ac tristique libero volutpat at 47 | + Facilisis in pretium nisl aliquet 48 | - Nulla volutpat aliquam velit 49 | + Very easy! 50 | 51 | Ordered 52 | 53 | 1. Lorem ipsum dolor sit amet 54 | 2. Consectetur adipiscing elit 55 | 3. Integer molestie lorem at massa 56 | 57 | 58 | 1. You can use sequential numbers... 59 | 1. ...or keep all the numbers as `1.` 60 | 61 | Start numbering with offset: 62 | 63 | 57. foo 64 | 1. bar 65 | 66 | 67 | ## Code 68 | 69 | Inline `code` 70 | 71 | Indented code 72 | 73 | // Some comments 74 | line 1 of code 75 | line 2 of code 76 | line 3 of code 77 | 78 | 79 | Block code "fences" 80 | 81 | ``` 82 | Sample text here... 83 | ``` 84 | 85 | Syntax highlighting 86 | 87 | ``` js 88 | var foo = function (bar) { 89 | return bar++; 90 | }; 91 | 92 | console.log(foo(5)); 93 | ``` 94 | 95 | ## Tables 96 | 97 | | Option | Description | 98 | | ------ | ----------- | 99 | | data | path to data files to supply the data that will be passed into templates. | 100 | | engine | engine to be used for processing templates. Handlebars is the default. | 101 | | ext | extension to be used for dest files. | 102 | 103 | Right aligned columns 104 | 105 | | Option | Description | 106 | | ------:| -----------:| 107 | | data | path to data files to supply the data that will be passed into templates. | 108 | | engine | engine to be used for processing templates. Handlebars is the default. | 109 | | ext | extension to be used for dest files. | 110 | 111 | 112 | ## Links 113 | 114 | [link text](http://dev.nodeca.com) 115 | 116 | [link with title](http://nodeca.github.io/pica/demo/ "title text!") 117 | 118 | Autoconverted link https://github.com/nodeca/pica 119 | 120 | 121 | ## Images 122 | 123 | ![Minion](https://octodex.github.com/images/minion.png) 124 | ![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat") 125 | 126 | 127 | ## Emoji 128 | 129 | :bow: 130 | -------------------------------------------------------------------------------- /sample_css.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keitaoouchi/MarkdownView/5e62a27f1002cd63c350ef1a4c640ddcd1f1ed48/sample_css.png -------------------------------------------------------------------------------- /sample_plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keitaoouchi/MarkdownView/5e62a27f1002cd63c350ef1a4c640ddcd1f1ed48/sample_plugin.png -------------------------------------------------------------------------------- /webassets/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "ios": 13 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /webassets/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /webassets/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | npm install 3 | npm run build 4 | ``` 5 | -------------------------------------------------------------------------------- /webassets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "webpack --config ./webpack.config.js" 4 | }, 5 | "devDependencies": { 6 | "@babel/core": "*", 7 | "babel-loader": "*", 8 | "babel-preset-env": "*", 9 | "css-loader": "*", 10 | "css-minimizer-webpack-plugin": "*", 11 | "file-loader": "*", 12 | "mini-css-extract-plugin": "*", 13 | "style-loader": "*", 14 | "terser-webpack-plugin": "*", 15 | "webpack": "*", 16 | "webpack-cli": "*" 17 | }, 18 | "dependencies": { 19 | "babel-core": "*", 20 | "highlight.js": "*", 21 | "markdown-it": "*", 22 | "markdown-it-emoji": "*" 23 | } 24 | } -------------------------------------------------------------------------------- /webassets/src/css/bootstrap.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2017 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | /*! 8 | * Generated using the Bootstrap Customizer () 9 | * Config saved to config.json and 10 | */ 11 | /*! 12 | * Bootstrap v3.3.7 (http://getbootstrap.com) 13 | * Copyright 2011-2016 Twitter, Inc. 14 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 15 | */ 16 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ 17 | html { 18 | font-family: sans-serif; 19 | -ms-text-size-adjust: 100%; 20 | -webkit-text-size-adjust: 100%; 21 | } 22 | body { 23 | margin: 0; 24 | } 25 | article, 26 | aside, 27 | details, 28 | figcaption, 29 | figure, 30 | footer, 31 | header, 32 | hgroup, 33 | main, 34 | menu, 35 | nav, 36 | section, 37 | summary { 38 | display: block; 39 | } 40 | audio, 41 | canvas, 42 | progress, 43 | video { 44 | display: inline-block; 45 | vertical-align: baseline; 46 | } 47 | audio:not([controls]) { 48 | display: none; 49 | height: 0; 50 | } 51 | [hidden], 52 | template { 53 | display: none; 54 | } 55 | a { 56 | background-color: transparent; 57 | } 58 | a:active, 59 | a:hover { 60 | outline: 0; 61 | } 62 | abbr[title] { 63 | border-bottom: 1px dotted; 64 | } 65 | b, 66 | strong { 67 | font-weight: bold; 68 | } 69 | dfn { 70 | font-style: italic; 71 | } 72 | h1 { 73 | font-size: 2em; 74 | margin: 0.67em 0; 75 | } 76 | mark { 77 | background: #ff0; 78 | color: #000; 79 | } 80 | small { 81 | font-size: 80%; 82 | } 83 | sub, 84 | sup { 85 | font-size: 75%; 86 | line-height: 0; 87 | position: relative; 88 | vertical-align: baseline; 89 | } 90 | sup { 91 | top: -0.5em; 92 | } 93 | sub { 94 | bottom: -0.25em; 95 | } 96 | img { 97 | border: 0; 98 | } 99 | svg:not(:root) { 100 | overflow: hidden; 101 | } 102 | figure { 103 | margin: 1em 40px; 104 | } 105 | hr { 106 | -webkit-box-sizing: content-box; 107 | -moz-box-sizing: content-box; 108 | box-sizing: content-box; 109 | height: 0; 110 | } 111 | pre { 112 | overflow: auto; 113 | } 114 | code, 115 | kbd, 116 | pre, 117 | samp { 118 | font-family: monospace, monospace; 119 | font-size: 1em; 120 | } 121 | button, 122 | input, 123 | optgroup, 124 | select, 125 | textarea { 126 | color: inherit; 127 | font: inherit; 128 | margin: 0; 129 | } 130 | button { 131 | overflow: visible; 132 | } 133 | button, 134 | select { 135 | text-transform: none; 136 | } 137 | button, 138 | html input[type="button"], 139 | input[type="reset"], 140 | input[type="submit"] { 141 | -webkit-appearance: button; 142 | cursor: pointer; 143 | } 144 | button[disabled], 145 | html input[disabled] { 146 | cursor: default; 147 | } 148 | button::-moz-focus-inner, 149 | input::-moz-focus-inner { 150 | border: 0; 151 | padding: 0; 152 | } 153 | input { 154 | line-height: normal; 155 | } 156 | input[type="checkbox"], 157 | input[type="radio"] { 158 | -webkit-box-sizing: border-box; 159 | -moz-box-sizing: border-box; 160 | box-sizing: border-box; 161 | padding: 0; 162 | } 163 | input[type="number"]::-webkit-inner-spin-button, 164 | input[type="number"]::-webkit-outer-spin-button { 165 | height: auto; 166 | } 167 | input[type="search"] { 168 | -webkit-appearance: textfield; 169 | -webkit-box-sizing: content-box; 170 | -moz-box-sizing: content-box; 171 | box-sizing: content-box; 172 | } 173 | input[type="search"]::-webkit-search-cancel-button, 174 | input[type="search"]::-webkit-search-decoration { 175 | -webkit-appearance: none; 176 | } 177 | fieldset { 178 | border: 1px solid #c0c0c0; 179 | margin: 0 2px; 180 | padding: 0.35em 0.625em 0.75em; 181 | } 182 | legend { 183 | border: 0; 184 | padding: 0; 185 | } 186 | textarea { 187 | overflow: auto; 188 | } 189 | optgroup { 190 | font-weight: bold; 191 | } 192 | table { 193 | border-collapse: collapse; 194 | border-spacing: 0; 195 | } 196 | td, 197 | th { 198 | padding: 0; 199 | } 200 | /*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ 201 | @media print { 202 | *, 203 | *:before, 204 | *:after { 205 | background: transparent !important; 206 | color: #000 !important; 207 | -webkit-box-shadow: none !important; 208 | box-shadow: none !important; 209 | text-shadow: none !important; 210 | } 211 | a, 212 | a:visited { 213 | text-decoration: underline; 214 | } 215 | a[href]:after { 216 | content: " (" attr(href) ")"; 217 | } 218 | abbr[title]:after { 219 | content: " (" attr(title) ")"; 220 | } 221 | a[href^="#"]:after, 222 | a[href^="javascript:"]:after { 223 | content: ""; 224 | } 225 | pre, 226 | blockquote { 227 | border: 1px solid #999; 228 | page-break-inside: avoid; 229 | } 230 | thead { 231 | display: table-header-group; 232 | } 233 | tr, 234 | img { 235 | page-break-inside: avoid; 236 | } 237 | img { 238 | max-width: 100% !important; 239 | } 240 | p, 241 | h2, 242 | h3 { 243 | orphans: 3; 244 | widows: 3; 245 | } 246 | h2, 247 | h3 { 248 | page-break-after: avoid; 249 | } 250 | .navbar { 251 | display: none; 252 | } 253 | .btn > .caret, 254 | .dropup > .btn > .caret { 255 | border-top-color: #000 !important; 256 | } 257 | .label { 258 | border: 1px solid #000; 259 | } 260 | .table { 261 | border-collapse: collapse !important; 262 | } 263 | .table td, 264 | .table th { 265 | background-color: #fff !important; 266 | } 267 | .table-bordered th, 268 | .table-bordered td { 269 | border: 1px solid #ddd !important; 270 | } 271 | } 272 | * { 273 | -webkit-box-sizing: border-box; 274 | -moz-box-sizing: border-box; 275 | box-sizing: border-box; 276 | } 277 | *:before, 278 | *:after { 279 | -webkit-box-sizing: border-box; 280 | -moz-box-sizing: border-box; 281 | box-sizing: border-box; 282 | } 283 | html { 284 | font-size: 10px; 285 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 286 | } 287 | body { 288 | font: -apple-system-body; 289 | } 290 | input, 291 | button, 292 | select, 293 | textarea { 294 | font-family: inherit; 295 | font-size: inherit; 296 | line-height: inherit; 297 | } 298 | a { 299 | color: #337ab7; 300 | text-decoration: none; 301 | } 302 | a:hover, 303 | a:focus { 304 | color: #23527c; 305 | text-decoration: underline; 306 | } 307 | a:focus { 308 | outline: 5px auto -webkit-focus-ring-color; 309 | outline-offset: -2px; 310 | } 311 | figure { 312 | margin: 0; 313 | } 314 | img { 315 | vertical-align: middle; 316 | } 317 | .img-responsive { 318 | display: block; 319 | max-width: 100%; 320 | height: auto; 321 | } 322 | .img-rounded { 323 | border-radius: 6px; 324 | } 325 | .img-thumbnail { 326 | padding: 4px; 327 | line-height: 1.42857143; 328 | background-color: #ffffff; 329 | border: 1px solid #dddddd; 330 | border-radius: 4px; 331 | -webkit-transition: all 0.2s ease-in-out; 332 | -o-transition: all 0.2s ease-in-out; 333 | transition: all 0.2s ease-in-out; 334 | display: inline-block; 335 | max-width: 100%; 336 | height: auto; 337 | } 338 | .img-circle { 339 | border-radius: 50%; 340 | } 341 | hr { 342 | margin-top: 20px; 343 | margin-bottom: 20px; 344 | border: 0; 345 | border-top: 1px solid #eeeeee; 346 | } 347 | .sr-only { 348 | position: absolute; 349 | width: 1px; 350 | height: 1px; 351 | margin: -1px; 352 | padding: 0; 353 | overflow: hidden; 354 | clip: rect(0, 0, 0, 0); 355 | border: 0; 356 | } 357 | .sr-only-focusable:active, 358 | .sr-only-focusable:focus { 359 | position: static; 360 | width: auto; 361 | height: auto; 362 | margin: 0; 363 | overflow: visible; 364 | clip: auto; 365 | } 366 | [role="button"] { 367 | cursor: pointer; 368 | } 369 | h1, 370 | h2, 371 | h3, 372 | h4, 373 | h5, 374 | h6, 375 | .h1, 376 | .h2, 377 | .h3, 378 | .h4, 379 | .h5, 380 | .h6 { 381 | font-family: inherit; 382 | font-weight: 500; 383 | line-height: 1.1; 384 | color: inherit; 385 | } 386 | h1 small, 387 | h2 small, 388 | h3 small, 389 | h4 small, 390 | h5 small, 391 | h6 small, 392 | .h1 small, 393 | .h2 small, 394 | .h3 small, 395 | .h4 small, 396 | .h5 small, 397 | .h6 small, 398 | h1 .small, 399 | h2 .small, 400 | h3 .small, 401 | h4 .small, 402 | h5 .small, 403 | h6 .small, 404 | .h1 .small, 405 | .h2 .small, 406 | .h3 .small, 407 | .h4 .small, 408 | .h5 .small, 409 | .h6 .small { 410 | font-weight: normal; 411 | line-height: 1; 412 | color: #777777; 413 | } 414 | h1, 415 | .h1, 416 | h2, 417 | .h2, 418 | h3, 419 | .h3 { 420 | margin-top: 20px; 421 | margin-bottom: 10px; 422 | } 423 | h1 small, 424 | .h1 small, 425 | h2 small, 426 | .h2 small, 427 | h3 small, 428 | .h3 small, 429 | h1 .small, 430 | .h1 .small, 431 | h2 .small, 432 | .h2 .small, 433 | h3 .small, 434 | .h3 .small { 435 | font-size: 65%; 436 | } 437 | h4, 438 | .h4, 439 | h5, 440 | .h5, 441 | h6, 442 | .h6 { 443 | margin-top: 10px; 444 | margin-bottom: 10px; 445 | } 446 | h4 small, 447 | .h4 small, 448 | h5 small, 449 | .h5 small, 450 | h6 small, 451 | .h6 small, 452 | h4 .small, 453 | .h4 .small, 454 | h5 .small, 455 | .h5 .small, 456 | h6 .small, 457 | .h6 .small { 458 | font-size: 75%; 459 | } 460 | h1, 461 | .h1 { 462 | font-size: 36px; 463 | } 464 | h2, 465 | .h2 { 466 | font-size: 30px; 467 | } 468 | h3, 469 | .h3 { 470 | font-size: 24px; 471 | } 472 | h4, 473 | .h4 { 474 | font-size: 18px; 475 | } 476 | h5, 477 | .h5 { 478 | font-size: 14px; 479 | } 480 | h6, 481 | .h6 { 482 | font-size: 12px; 483 | } 484 | p { 485 | margin: 0 0 10px; 486 | } 487 | .lead { 488 | margin-bottom: 20px; 489 | font-size: 16px; 490 | font-weight: 300; 491 | line-height: 1.4; 492 | } 493 | @media (min-width: 768px) { 494 | .lead { 495 | font-size: 21px; 496 | } 497 | } 498 | small, 499 | .small { 500 | font-size: 85%; 501 | } 502 | mark, 503 | .mark { 504 | background-color: #fcf8e3; 505 | padding: .2em; 506 | } 507 | .text-left { 508 | text-align: left; 509 | } 510 | .text-right { 511 | text-align: right; 512 | } 513 | .text-center { 514 | text-align: center; 515 | } 516 | .text-justify { 517 | text-align: justify; 518 | } 519 | .text-nowrap { 520 | white-space: nowrap; 521 | } 522 | .text-lowercase { 523 | text-transform: lowercase; 524 | } 525 | .text-uppercase { 526 | text-transform: uppercase; 527 | } 528 | .text-capitalize { 529 | text-transform: capitalize; 530 | } 531 | .text-muted { 532 | color: #777777; 533 | } 534 | .text-primary { 535 | color: #337ab7; 536 | } 537 | a.text-primary:hover, 538 | a.text-primary:focus { 539 | color: #286090; 540 | } 541 | .text-success { 542 | color: #3c763d; 543 | } 544 | a.text-success:hover, 545 | a.text-success:focus { 546 | color: #2b542c; 547 | } 548 | .text-info { 549 | color: #31708f; 550 | } 551 | a.text-info:hover, 552 | a.text-info:focus { 553 | color: #245269; 554 | } 555 | .text-warning { 556 | color: #8a6d3b; 557 | } 558 | a.text-warning:hover, 559 | a.text-warning:focus { 560 | color: #66512c; 561 | } 562 | .text-danger { 563 | color: #a94442; 564 | } 565 | a.text-danger:hover, 566 | a.text-danger:focus { 567 | color: #843534; 568 | } 569 | .bg-primary { 570 | color: #fff; 571 | background-color: #337ab7; 572 | } 573 | a.bg-primary:hover, 574 | a.bg-primary:focus { 575 | background-color: #286090; 576 | } 577 | .bg-success { 578 | background-color: #dff0d8; 579 | } 580 | a.bg-success:hover, 581 | a.bg-success:focus { 582 | background-color: #c1e2b3; 583 | } 584 | .bg-info { 585 | background-color: #d9edf7; 586 | } 587 | a.bg-info:hover, 588 | a.bg-info:focus { 589 | background-color: #afd9ee; 590 | } 591 | .bg-warning { 592 | background-color: #fcf8e3; 593 | } 594 | a.bg-warning:hover, 595 | a.bg-warning:focus { 596 | background-color: #f7ecb5; 597 | } 598 | .bg-danger { 599 | background-color: #f2dede; 600 | } 601 | a.bg-danger:hover, 602 | a.bg-danger:focus { 603 | background-color: #e4b9b9; 604 | } 605 | .page-header { 606 | padding-bottom: 9px; 607 | margin: 40px 0 20px; 608 | border-bottom: 1px solid #eeeeee; 609 | } 610 | ul, 611 | ol { 612 | margin-top: 0; 613 | margin-bottom: 10px; 614 | } 615 | ul ul, 616 | ol ul, 617 | ul ol, 618 | ol ol { 619 | margin-bottom: 0; 620 | } 621 | .list-unstyled { 622 | padding-left: 0; 623 | list-style: none; 624 | } 625 | .list-inline { 626 | padding-left: 0; 627 | list-style: none; 628 | margin-left: -5px; 629 | } 630 | .list-inline > li { 631 | display: inline-block; 632 | padding-left: 5px; 633 | padding-right: 5px; 634 | } 635 | dl { 636 | margin-top: 0; 637 | margin-bottom: 20px; 638 | } 639 | dt, 640 | dd { 641 | line-height: 1.42857143; 642 | } 643 | dt { 644 | font-weight: bold; 645 | } 646 | dd { 647 | margin-left: 0; 648 | } 649 | @media (min-width: 768px) { 650 | .dl-horizontal dt { 651 | float: left; 652 | width: 160px; 653 | clear: left; 654 | text-align: right; 655 | overflow: hidden; 656 | text-overflow: ellipsis; 657 | white-space: nowrap; 658 | } 659 | .dl-horizontal dd { 660 | margin-left: 180px; 661 | } 662 | } 663 | abbr[title], 664 | abbr[data-original-title] { 665 | cursor: help; 666 | border-bottom: 1px dotted #777777; 667 | } 668 | .initialism { 669 | font-size: 90%; 670 | text-transform: uppercase; 671 | } 672 | blockquote { 673 | padding: 10px 20px; 674 | margin: 0 0 20px; 675 | font-size: 17.5px; 676 | border-left: 5px solid #eeeeee; 677 | } 678 | blockquote p:last-child, 679 | blockquote ul:last-child, 680 | blockquote ol:last-child { 681 | margin-bottom: 0; 682 | } 683 | blockquote footer, 684 | blockquote small, 685 | blockquote .small { 686 | display: block; 687 | font-size: 80%; 688 | line-height: 1.42857143; 689 | color: #777777; 690 | } 691 | blockquote footer:before, 692 | blockquote small:before, 693 | blockquote .small:before { 694 | content: '\2014 \00A0'; 695 | } 696 | .blockquote-reverse, 697 | blockquote.pull-right { 698 | padding-right: 15px; 699 | padding-left: 0; 700 | border-right: 5px solid #eeeeee; 701 | border-left: 0; 702 | text-align: right; 703 | } 704 | .blockquote-reverse footer:before, 705 | blockquote.pull-right footer:before, 706 | .blockquote-reverse small:before, 707 | blockquote.pull-right small:before, 708 | .blockquote-reverse .small:before, 709 | blockquote.pull-right .small:before { 710 | content: ''; 711 | } 712 | .blockquote-reverse footer:after, 713 | blockquote.pull-right footer:after, 714 | .blockquote-reverse small:after, 715 | blockquote.pull-right small:after, 716 | .blockquote-reverse .small:after, 717 | blockquote.pull-right .small:after { 718 | content: '\00A0 \2014'; 719 | } 720 | address { 721 | margin-bottom: 20px; 722 | font-style: normal; 723 | line-height: 1.42857143; 724 | } 725 | code, 726 | kbd, 727 | pre, 728 | samp { 729 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; 730 | } 731 | code { 732 | padding: 2px 4px; 733 | font-size: 90%; 734 | color: #c7254e; 735 | background-color: #f9f2f4; 736 | border-radius: 4px; 737 | } 738 | kbd { 739 | padding: 2px 4px; 740 | font-size: 90%; 741 | color: #ffffff; 742 | background-color: #333333; 743 | border-radius: 3px; 744 | -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); 745 | box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); 746 | } 747 | kbd kbd { 748 | padding: 0; 749 | font-size: 100%; 750 | font-weight: bold; 751 | -webkit-box-shadow: none; 752 | box-shadow: none; 753 | } 754 | pre { 755 | display: block; 756 | padding: 9.5px; 757 | margin: 0 0 10px; 758 | font-size: 13px; 759 | line-height: 1.42857143; 760 | word-break: break-all; 761 | word-wrap: break-word; 762 | color: #333333; 763 | background-color: #f5f5f5; 764 | border: 1px solid #cccccc; 765 | border-radius: 4px; 766 | } 767 | pre code { 768 | padding: 0; 769 | font-size: inherit; 770 | color: inherit; 771 | white-space: pre-wrap; 772 | background-color: transparent; 773 | border-radius: 0; 774 | } 775 | .pre-scrollable { 776 | max-height: 340px; 777 | overflow-y: scroll; 778 | } 779 | table { 780 | background-color: transparent; 781 | } 782 | caption { 783 | padding-top: 8px; 784 | padding-bottom: 8px; 785 | color: #777777; 786 | text-align: left; 787 | } 788 | th { 789 | text-align: left; 790 | } 791 | .table { 792 | width: 100%; 793 | max-width: 100%; 794 | margin-bottom: 20px; 795 | } 796 | .table > thead > tr > th, 797 | .table > tbody > tr > th, 798 | .table > tfoot > tr > th, 799 | .table > thead > tr > td, 800 | .table > tbody > tr > td, 801 | .table > tfoot > tr > td { 802 | padding: 8px; 803 | line-height: 1.42857143; 804 | vertical-align: top; 805 | border-top: 1px solid #dddddd; 806 | } 807 | .table > thead > tr > th { 808 | vertical-align: bottom; 809 | border-bottom: 2px solid #dddddd; 810 | } 811 | .table > caption + thead > tr:first-child > th, 812 | .table > colgroup + thead > tr:first-child > th, 813 | .table > thead:first-child > tr:first-child > th, 814 | .table > caption + thead > tr:first-child > td, 815 | .table > colgroup + thead > tr:first-child > td, 816 | .table > thead:first-child > tr:first-child > td { 817 | border-top: 0; 818 | } 819 | .table > tbody + tbody { 820 | border-top: 2px solid #dddddd; 821 | } 822 | .table .table { 823 | background-color: #ffffff; 824 | } 825 | .table-condensed > thead > tr > th, 826 | .table-condensed > tbody > tr > th, 827 | .table-condensed > tfoot > tr > th, 828 | .table-condensed > thead > tr > td, 829 | .table-condensed > tbody > tr > td, 830 | .table-condensed > tfoot > tr > td { 831 | padding: 5px; 832 | } 833 | .table-bordered { 834 | border: 1px solid #dddddd; 835 | } 836 | .table-bordered > thead > tr > th, 837 | .table-bordered > tbody > tr > th, 838 | .table-bordered > tfoot > tr > th, 839 | .table-bordered > thead > tr > td, 840 | .table-bordered > tbody > tr > td, 841 | .table-bordered > tfoot > tr > td { 842 | border: 1px solid #dddddd; 843 | } 844 | .table-bordered > thead > tr > th, 845 | .table-bordered > thead > tr > td { 846 | border-bottom-width: 2px; 847 | } 848 | .table-striped > tbody > tr:nth-of-type(odd) { 849 | background-color: #f9f9f9; 850 | } 851 | .table-hover > tbody > tr:hover { 852 | background-color: #f5f5f5; 853 | } 854 | table col[class*="col-"] { 855 | position: static; 856 | float: none; 857 | display: table-column; 858 | } 859 | table td[class*="col-"], 860 | table th[class*="col-"] { 861 | position: static; 862 | float: none; 863 | display: table-cell; 864 | } 865 | .table > thead > tr > td.active, 866 | .table > tbody > tr > td.active, 867 | .table > tfoot > tr > td.active, 868 | .table > thead > tr > th.active, 869 | .table > tbody > tr > th.active, 870 | .table > tfoot > tr > th.active, 871 | .table > thead > tr.active > td, 872 | .table > tbody > tr.active > td, 873 | .table > tfoot > tr.active > td, 874 | .table > thead > tr.active > th, 875 | .table > tbody > tr.active > th, 876 | .table > tfoot > tr.active > th { 877 | background-color: #f5f5f5; 878 | } 879 | .table-hover > tbody > tr > td.active:hover, 880 | .table-hover > tbody > tr > th.active:hover, 881 | .table-hover > tbody > tr.active:hover > td, 882 | .table-hover > tbody > tr:hover > .active, 883 | .table-hover > tbody > tr.active:hover > th { 884 | background-color: #e8e8e8; 885 | } 886 | .table > thead > tr > td.success, 887 | .table > tbody > tr > td.success, 888 | .table > tfoot > tr > td.success, 889 | .table > thead > tr > th.success, 890 | .table > tbody > tr > th.success, 891 | .table > tfoot > tr > th.success, 892 | .table > thead > tr.success > td, 893 | .table > tbody > tr.success > td, 894 | .table > tfoot > tr.success > td, 895 | .table > thead > tr.success > th, 896 | .table > tbody > tr.success > th, 897 | .table > tfoot > tr.success > th { 898 | background-color: #dff0d8; 899 | } 900 | .table-hover > tbody > tr > td.success:hover, 901 | .table-hover > tbody > tr > th.success:hover, 902 | .table-hover > tbody > tr.success:hover > td, 903 | .table-hover > tbody > tr:hover > .success, 904 | .table-hover > tbody > tr.success:hover > th { 905 | background-color: #d0e9c6; 906 | } 907 | .table > thead > tr > td.info, 908 | .table > tbody > tr > td.info, 909 | .table > tfoot > tr > td.info, 910 | .table > thead > tr > th.info, 911 | .table > tbody > tr > th.info, 912 | .table > tfoot > tr > th.info, 913 | .table > thead > tr.info > td, 914 | .table > tbody > tr.info > td, 915 | .table > tfoot > tr.info > td, 916 | .table > thead > tr.info > th, 917 | .table > tbody > tr.info > th, 918 | .table > tfoot > tr.info > th { 919 | background-color: #d9edf7; 920 | } 921 | .table-hover > tbody > tr > td.info:hover, 922 | .table-hover > tbody > tr > th.info:hover, 923 | .table-hover > tbody > tr.info:hover > td, 924 | .table-hover > tbody > tr:hover > .info, 925 | .table-hover > tbody > tr.info:hover > th { 926 | background-color: #c4e3f3; 927 | } 928 | .table > thead > tr > td.warning, 929 | .table > tbody > tr > td.warning, 930 | .table > tfoot > tr > td.warning, 931 | .table > thead > tr > th.warning, 932 | .table > tbody > tr > th.warning, 933 | .table > tfoot > tr > th.warning, 934 | .table > thead > tr.warning > td, 935 | .table > tbody > tr.warning > td, 936 | .table > tfoot > tr.warning > td, 937 | .table > thead > tr.warning > th, 938 | .table > tbody > tr.warning > th, 939 | .table > tfoot > tr.warning > th { 940 | background-color: #fcf8e3; 941 | } 942 | .table-hover > tbody > tr > td.warning:hover, 943 | .table-hover > tbody > tr > th.warning:hover, 944 | .table-hover > tbody > tr.warning:hover > td, 945 | .table-hover > tbody > tr:hover > .warning, 946 | .table-hover > tbody > tr.warning:hover > th { 947 | background-color: #faf2cc; 948 | } 949 | .table > thead > tr > td.danger, 950 | .table > tbody > tr > td.danger, 951 | .table > tfoot > tr > td.danger, 952 | .table > thead > tr > th.danger, 953 | .table > tbody > tr > th.danger, 954 | .table > tfoot > tr > th.danger, 955 | .table > thead > tr.danger > td, 956 | .table > tbody > tr.danger > td, 957 | .table > tfoot > tr.danger > td, 958 | .table > thead > tr.danger > th, 959 | .table > tbody > tr.danger > th, 960 | .table > tfoot > tr.danger > th { 961 | background-color: #f2dede; 962 | } 963 | .table-hover > tbody > tr > td.danger:hover, 964 | .table-hover > tbody > tr > th.danger:hover, 965 | .table-hover > tbody > tr.danger:hover > td, 966 | .table-hover > tbody > tr:hover > .danger, 967 | .table-hover > tbody > tr.danger:hover > th { 968 | background-color: #ebcccc; 969 | } 970 | .table-responsive { 971 | overflow-x: auto; 972 | min-height: 0.01%; 973 | } 974 | @media screen and (max-width: 767px) { 975 | .table-responsive { 976 | width: 100%; 977 | margin-bottom: 15px; 978 | overflow-y: hidden; 979 | -ms-overflow-style: -ms-autohiding-scrollbar; 980 | border: 1px solid #dddddd; 981 | } 982 | .table-responsive > .table { 983 | margin-bottom: 0; 984 | } 985 | .table-responsive > .table > thead > tr > th, 986 | .table-responsive > .table > tbody > tr > th, 987 | .table-responsive > .table > tfoot > tr > th, 988 | .table-responsive > .table > thead > tr > td, 989 | .table-responsive > .table > tbody > tr > td, 990 | .table-responsive > .table > tfoot > tr > td { 991 | white-space: nowrap; 992 | } 993 | .table-responsive > .table-bordered { 994 | border: 0; 995 | } 996 | .table-responsive > .table-bordered > thead > tr > th:first-child, 997 | .table-responsive > .table-bordered > tbody > tr > th:first-child, 998 | .table-responsive > .table-bordered > tfoot > tr > th:first-child, 999 | .table-responsive > .table-bordered > thead > tr > td:first-child, 1000 | .table-responsive > .table-bordered > tbody > tr > td:first-child, 1001 | .table-responsive > .table-bordered > tfoot > tr > td:first-child { 1002 | border-left: 0; 1003 | } 1004 | .table-responsive > .table-bordered > thead > tr > th:last-child, 1005 | .table-responsive > .table-bordered > tbody > tr > th:last-child, 1006 | .table-responsive > .table-bordered > tfoot > tr > th:last-child, 1007 | .table-responsive > .table-bordered > thead > tr > td:last-child, 1008 | .table-responsive > .table-bordered > tbody > tr > td:last-child, 1009 | .table-responsive > .table-bordered > tfoot > tr > td:last-child { 1010 | border-right: 0; 1011 | } 1012 | .table-responsive > .table-bordered > tbody > tr:last-child > th, 1013 | .table-responsive > .table-bordered > tfoot > tr:last-child > th, 1014 | .table-responsive > .table-bordered > tbody > tr:last-child > td, 1015 | .table-responsive > .table-bordered > tfoot > tr:last-child > td { 1016 | border-bottom: 0; 1017 | } 1018 | } 1019 | .clearfix:before, 1020 | .clearfix:after, 1021 | .dl-horizontal dd:before, 1022 | .dl-horizontal dd:after { 1023 | content: " "; 1024 | display: table; 1025 | } 1026 | .clearfix:after, 1027 | .dl-horizontal dd:after { 1028 | clear: both; 1029 | } 1030 | .center-block { 1031 | display: block; 1032 | margin-left: auto; 1033 | margin-right: auto; 1034 | } 1035 | .pull-right { 1036 | float: right !important; 1037 | } 1038 | .pull-left { 1039 | float: left !important; 1040 | } 1041 | .hide { 1042 | display: none !important; 1043 | } 1044 | .show { 1045 | display: block !important; 1046 | } 1047 | .invisible { 1048 | visibility: hidden; 1049 | } 1050 | .text-hide { 1051 | font: 0/0 a; 1052 | color: transparent; 1053 | text-shadow: none; 1054 | background-color: transparent; 1055 | border: 0; 1056 | } 1057 | .hidden { 1058 | display: none !important; 1059 | } 1060 | .affix { 1061 | position: fixed; 1062 | } 1063 | -------------------------------------------------------------------------------- /webassets/src/css/gist.css: -------------------------------------------------------------------------------- 1 | .hljs { 2 | display: block; 3 | background: white; 4 | padding: 0.5em; 5 | color: #333333; 6 | overflow-x: auto; 7 | -webkit-text-size-adjust: none; 8 | } 9 | 10 | .hljs-comment, 11 | .bash .hljs-shebang, 12 | .java .hljs-javadoc, 13 | .javascript .hljs-javadoc, 14 | .rust .hljs-preprocessor { 15 | color: #969896; 16 | } 17 | 18 | .hljs-string, 19 | .apache .hljs-sqbracket, 20 | .coffeescript .hljs-subst, 21 | .coffeescript .hljs-regexp, 22 | .cpp .hljs-preprocessor, 23 | .c .hljs-preprocessor, 24 | .javascript .hljs-regexp, 25 | .json .hljs-attribute, 26 | .makefile .hljs-variable, 27 | .markdown .hljs-value, 28 | .markdown .hljs-link_label, 29 | .markdown .hljs-strong, 30 | .markdown .hljs-emphasis, 31 | .markdown .hljs-blockquote, 32 | .nginx .hljs-regexp, 33 | .nginx .hljs-number, 34 | .objectivec .hljs-preprocessor .hljs-title, 35 | .perl .hljs-regexp, 36 | .php .hljs-regexp, 37 | .xml .hljs-value, 38 | .less .hljs-built_in, 39 | .scss .hljs-built_in { 40 | color: #df5000; 41 | } 42 | 43 | .hljs-keyword, 44 | .css .hljs-at_rule, 45 | .css .hljs-important, 46 | .http .hljs-request, 47 | .ini .hljs-setting, 48 | .haskell .hljs-type, 49 | .java .hljs-javadoctag, 50 | .javascript .hljs-tag, 51 | .javascript .hljs-javadoctag, 52 | .nginx .hljs-title, 53 | .objectivec .hljs-preprocessor, 54 | .php .hljs-phpdoc, 55 | .sql .hljs-built_in, 56 | .less .hljs-tag, 57 | .less .hljs-at_rule, 58 | .scss .hljs-tag, 59 | .scss .hljs-at_rule, 60 | .scss .hljs-important, 61 | .stylus .hljs-at_rule, 62 | .go .hljs-typename, 63 | .swift .hljs-preprocessor { 64 | color: #a71d5d; 65 | } 66 | 67 | .apache .hljs-common, 68 | .apache .hljs-cbracket, 69 | .apache .hljs-keyword, 70 | .bash .hljs-literal, 71 | .bash .hljs-built_in, 72 | .coffeescript .hljs-literal, 73 | .coffeescript .hljs-built_in, 74 | .coffeescript .hljs-number, 75 | .cpp .hljs-number, 76 | .cpp .hljs-built_in, 77 | .c .hljs-number, 78 | .c .hljs-built_in, 79 | .cs .hljs-number, 80 | .cs .hljs-built_in, 81 | .css .hljs-attribute, 82 | .css .hljs-hexcolor, 83 | .css .hljs-number, 84 | .css .hljs-function, 85 | .haskell .hljs-number, 86 | .http .hljs-literal, 87 | .http .hljs-attribute, 88 | .java .hljs-number, 89 | .javascript .hljs-built_in, 90 | .javascript .hljs-literal, 91 | .javascript .hljs-number, 92 | .json .hljs-number, 93 | .makefile .hljs-keyword, 94 | .markdown .hljs-link_reference, 95 | .nginx .hljs-built_in, 96 | .objectivec .hljs-literal, 97 | .objectivec .hljs-number, 98 | .objectivec .hljs-built_in, 99 | .php .hljs-literal, 100 | .php .hljs-number, 101 | .python .hljs-number, 102 | .ruby .hljs-prompt, 103 | .ruby .hljs-constant, 104 | .ruby .hljs-number, 105 | .ruby .hljs-subst .hljs-keyword, 106 | .ruby .hljs-symbol, 107 | .rust .hljs-number, 108 | .sql .hljs-number, 109 | .puppet .hljs-function, 110 | .less .hljs-number, 111 | .less .hljs-hexcolor, 112 | .less .hljs-function, 113 | .less .hljs-attribute, 114 | .scss .hljs-preprocessor, 115 | .scss .hljs-number, 116 | .scss .hljs-hexcolor, 117 | .scss .hljs-function, 118 | .scss .hljs-attribute, 119 | .stylus .hljs-number, 120 | .stylus .hljs-hexcolor, 121 | .stylus .hljs-attribute, 122 | .stylus .hljs-params, 123 | .go .hljs-built_in, 124 | .go .hljs-constant, 125 | .swift .hljs-built_in, 126 | .swift .hljs-number { 127 | color: #0086b3; 128 | } 129 | 130 | .apache .hljs-tag, 131 | .cs .hljs-xmlDocTag, 132 | .css .hljs-tag, 133 | .xml .hljs-title, 134 | .stylus .hljs-tag { 135 | color: #63a35c; 136 | } 137 | 138 | .bash .hljs-variable, 139 | .cs .hljs-preprocessor, 140 | .cs .hljs-preprocessor .hljs-keyword, 141 | .css .hljs-attr_selector, 142 | .css .hljs-value, 143 | .ini .hljs-value, 144 | .ini .hljs-keyword, 145 | .javascript .hljs-tag .hljs-title, 146 | .makefile .hljs-constant, 147 | .nginx .hljs-variable, 148 | .xml .hljs-tag, 149 | .scss .hljs-variable { 150 | color: #333333; 151 | } 152 | 153 | .bash .hljs-title, 154 | .coffeescript .hljs-title, 155 | .cpp .hljs-title, 156 | .c .hljs-title, 157 | .cs .hljs-title, 158 | .css .hljs-id, 159 | .css .hljs-class, 160 | .css .hljs-pseudo, 161 | .ini .hljs-title, 162 | .haskell .hljs-title, 163 | .haskell .hljs-pragma, 164 | .java .hljs-title, 165 | .javascript .hljs-title, 166 | .makefile .hljs-title, 167 | .objectivec .hljs-title, 168 | .perl .hljs-sub, 169 | .php .hljs-title, 170 | .python .hljs-decorator, 171 | .python .hljs-title, 172 | .ruby .hljs-parent, 173 | .ruby .hljs-title, 174 | .rust .hljs-title, 175 | .xml .hljs-attribute, 176 | .puppet .hljs-title, 177 | .less .hljs-id, 178 | .less .hljs-pseudo, 179 | .less .hljs-class, 180 | .scss .hljs-id, 181 | .scss .hljs-pseudo, 182 | .scss .hljs-class, 183 | .stylus .hljs-class, 184 | .stylus .hljs-id, 185 | .stylus .hljs-pseudo, 186 | .stylus .hljs-title, 187 | .swift .hljs-title, 188 | .diff .hljs-chunk { 189 | color: #795da3; 190 | } 191 | 192 | .coffeescript .hljs-reserved, 193 | .coffeescript .hljs-attribute { 194 | color: #1d3e81; 195 | } 196 | 197 | .diff .hljs-chunk { 198 | font-weight: bold; 199 | } 200 | 201 | .diff .hljs-addition { 202 | color: #55a532; 203 | background-color: #eaffea; 204 | } 205 | 206 | .diff .hljs-deletion { 207 | color: #bd2c00; 208 | background-color: #ffecec; 209 | } 210 | 211 | .markdown .hljs-link_url { 212 | text-decoration: underline; 213 | } 214 | -------------------------------------------------------------------------------- /webassets/src/css/github.css: -------------------------------------------------------------------------------- 1 | .hljs { 2 | display: block; 3 | overflow-x: auto; 4 | padding: 0.5em; 5 | color: #333; 6 | background: #f8f8f8; 7 | -webkit-text-size-adjust: none; 8 | } 9 | 10 | .hljs-comment, 11 | .diff .hljs-header { 12 | color: #998; 13 | font-style: italic; 14 | } 15 | 16 | .hljs-keyword, 17 | .css .rule .hljs-keyword, 18 | .hljs-winutils, 19 | .nginx .hljs-title, 20 | .hljs-subst, 21 | .hljs-request, 22 | .hljs-status { 23 | color: #333; 24 | font-weight: bold; 25 | } 26 | 27 | .hljs-number, 28 | .hljs-hexcolor, 29 | .ruby .hljs-constant { 30 | color: #008080; 31 | } 32 | 33 | .hljs-string, 34 | .hljs-tag .hljs-value, 35 | .hljs-doctag, 36 | .tex .hljs-formula { 37 | color: #d14; 38 | } 39 | 40 | .hljs-title, 41 | .hljs-id, 42 | .scss .hljs-preprocessor { 43 | color: #900; 44 | font-weight: bold; 45 | } 46 | 47 | .hljs-list .hljs-keyword, 48 | .hljs-subst { 49 | font-weight: normal; 50 | } 51 | 52 | .hljs-class .hljs-title, 53 | .hljs-type, 54 | .vhdl .hljs-literal, 55 | .tex .hljs-command { 56 | color: #458; 57 | font-weight: bold; 58 | } 59 | 60 | .hljs-tag, 61 | .hljs-tag .hljs-title, 62 | .hljs-rule .hljs-property, 63 | .django .hljs-tag .hljs-keyword { 64 | color: #000080; 65 | font-weight: normal; 66 | } 67 | 68 | .hljs-attribute, 69 | .hljs-variable, 70 | .lisp .hljs-body, 71 | .hljs-name { 72 | color: #008080; 73 | } 74 | 75 | .hljs-regexp { 76 | color: #009926; 77 | } 78 | 79 | .hljs-symbol, 80 | .ruby .hljs-symbol .hljs-string, 81 | .lisp .hljs-keyword, 82 | .clojure .hljs-keyword, 83 | .scheme .hljs-keyword, 84 | .tex .hljs-special, 85 | .hljs-prompt { 86 | color: #990073; 87 | } 88 | 89 | .hljs-built_in { 90 | color: #0086b3; 91 | } 92 | 93 | .hljs-preprocessor, 94 | .hljs-pragma, 95 | .hljs-pi, 96 | .hljs-doctype, 97 | .hljs-shebang, 98 | .hljs-cdata { 99 | color: #999; 100 | font-weight: bold; 101 | } 102 | 103 | .hljs-deletion { 104 | background: #fdd; 105 | } 106 | 107 | .hljs-addition { 108 | background: #dfd; 109 | } 110 | 111 | .diff .hljs-change { 112 | background: #0086b3; 113 | } 114 | 115 | .hljs-chunk { 116 | color: #aaa; 117 | } 118 | -------------------------------------------------------------------------------- /webassets/src/css/index.css: -------------------------------------------------------------------------------- 1 | img { max-width: 100%; } 2 | * { 3 | -webkit-touch-callout: none; 4 | } 5 | .container { 6 | margin-right: auto; 7 | margin-left: auto; 8 | padding-left: 15px; 9 | padding-right: 15px; 10 | } 11 | 12 | :root { 13 | color-scheme: light dark; 14 | --background-color: -apple-system-background; 15 | --color: -apple-system-label; 16 | } 17 | 18 | @media screen and (prefers-color-scheme: dark) { 19 | a { 20 | color: #2997ff; 21 | } 22 | a:hover, 23 | a:focus { 24 | color: #006dd3; 25 | } 26 | } 27 | 28 | body { 29 | background-color: var(--background); 30 | color: var(--color); 31 | } -------------------------------------------------------------------------------- /webassets/src/js/index.js: -------------------------------------------------------------------------------- 1 | import hljs from "highlight.js"; 2 | import MarkdownIt from "markdown-it"; 3 | import emoji from "markdown-it-emoji"; 4 | import "./../css/bootstrap.css"; 5 | import "./../css/gist.css"; 6 | import "./../css/github.css"; 7 | import "./../css/index.css"; 8 | 9 | let markdown = new MarkdownIt({ 10 | html: true, 11 | breaks: true, 12 | linkify: true, 13 | highlight: function (code) { 14 | return hljs.highlightAuto(code).value; 15 | }, 16 | }); 17 | 18 | const postDocumentHeight = () => { 19 | var _body = document.body; 20 | var _html = document.documentElement; 21 | var height = Math.max( 22 | _body.scrollHeight, 23 | _body.offsetHeight, 24 | _html.clientHeight, 25 | _html.scrollHeight, 26 | _html.offsetHeight 27 | ); 28 | console.log(height) 29 | window?.webkit?.messageHandlers?.updateHeight?.postMessage(height); 30 | }; 31 | 32 | markdown.use(emoji); 33 | 34 | window.usePlugin = (plugin) => markdown.use(plugin); 35 | 36 | window.showMarkdown = (percentEncodedMarkdown, enableImage = true) => { 37 | if (!percentEncodedMarkdown) { 38 | return; 39 | } 40 | 41 | const markdownText = decodeURIComponent(percentEncodedMarkdown); 42 | 43 | if (!enableImage) { 44 | markdown = markdown.disable("image"); 45 | } 46 | 47 | let html = markdown.render(markdownText); 48 | 49 | document.getElementById("contents").innerHTML = html; 50 | 51 | var imgs = document.querySelectorAll("img"); 52 | 53 | imgs.forEach((img) => { 54 | img.loading = "lazy"; 55 | img.onload = () => { 56 | postDocumentHeight(); 57 | }; 58 | }); 59 | 60 | window.imgs = imgs; 61 | 62 | let tables = document.querySelectorAll("table"); 63 | 64 | tables.forEach((table) => { 65 | table.classList.add("table"); 66 | }); 67 | 68 | let codes = document.querySelectorAll("pre code"); 69 | 70 | codes.forEach((code) => { 71 | hljs.highlightBlock(code); 72 | }); 73 | 74 | postDocumentHeight(); 75 | }; 76 | -------------------------------------------------------------------------------- /webassets/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require("path"); 3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 4 | const TerserPlugin = require('terser-webpack-plugin'); 5 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); 6 | 7 | module.exports = { 8 | mode: 'production', 9 | entry: __dirname + "/src/js/index.js", 10 | output: { 11 | path: __dirname + '/../Sources/MarkdownView/Resources', 12 | filename: 'main.js' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.js$/, 18 | loader: "babel-loader" 19 | }, { 20 | test: /\.css$/, 21 | use: [MiniCssExtractPlugin.loader, 'css-loader'] 22 | }, { 23 | test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/, 24 | loader: "file-loader", 25 | options: { 26 | name: "[name].[ext]" 27 | } 28 | } 29 | ] 30 | }, 31 | plugins: [ 32 | new MiniCssExtractPlugin({ 33 | filename: "[name].css" 34 | }), 35 | new webpack.LoaderOptionsPlugin({minimize: true}) 36 | ], 37 | optimization: { 38 | minimize: true, 39 | minimizer: [ 40 | new TerserPlugin({ 41 | extractComments: 'all', 42 | terserOptions: { 43 | compress: true, 44 | output: { 45 | comments: false, 46 | beautify: false 47 | } 48 | } 49 | }), 50 | new CssMinimizerPlugin({ 51 | minimizerOptions: { 52 | preset: [ 53 | 'default', 54 | { 55 | discardComments: { removeAll: true }, 56 | }, 57 | ], 58 | }, 59 | }), 60 | ], 61 | } 62 | } 63 | --------------------------------------------------------------------------------