├── Mintfile ├── DangerTools ├── Sources │ └── DangerTools │ │ └── Empty.swift └── Package.swift ├── Binding ├── src │ ├── vite-env.d.ts │ ├── main.ts │ ├── binding.ts │ └── inputs.ts ├── .gitignore ├── package.json ├── tsconfig.json └── vite.config.ts ├── clean.sh ├── docs ├── img │ ├── carat.png │ ├── dash.png │ └── spinner.gif ├── docsets │ ├── Marindeck.tgz │ └── Marindeck.docset │ │ └── Contents │ │ ├── Resources │ │ ├── docSet.dsidx │ │ └── Documents │ │ │ ├── img │ │ │ ├── dash.png │ │ │ ├── carat.png │ │ │ └── spinner.gif │ │ │ ├── js │ │ │ ├── jazzy.js │ │ │ └── jazzy.search.js │ │ │ └── css │ │ │ └── highlight.css │ │ └── Info.plist ├── badge.svg ├── js │ ├── jazzy.js │ └── jazzy.search.js └── css │ └── highlight.css ├── Marindeck ├── Icons │ ├── BlackIcon.png │ └── RainbowIcon.png ├── Assets.xcassets │ ├── Contents.json │ ├── Theme │ │ ├── Contents.json │ │ ├── Frostclear-icon.imageset │ │ │ ├── Frostclear-icon.png │ │ │ └── Contents.json │ │ └── Dolce-icon.imageset │ │ │ ├── DolceThemeIcon - しらかばるなこ.png │ │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ └── Contents.json │ ├── logo2.imageset │ │ ├── logo2.pdf │ │ └── Contents.json │ ├── BlackIcon.imageset │ │ ├── BlackIcon.png │ │ └── Contents.json │ ├── icon-white.imageset │ │ ├── icon-white.png │ │ ├── icon-white@2x.png │ │ ├── icon-white@3x.png │ │ └── Contents.json │ ├── DefaultIcon.imageset │ │ ├── DefaultIcon.png │ │ └── Contents.json │ ├── RainbowIcon.imageset │ │ ├── RainbowIcon.png │ │ └── Contents.json │ ├── MarinDeckWithText-dark.imageset │ │ ├── MarinDeckWithText.png │ │ └── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── gif.imageset │ │ ├── Contents.json │ │ └── gif.svg │ ├── photo.imageset │ │ ├── Contents.json │ │ └── photo.svg │ ├── tweet.imageset │ │ ├── Contents.json │ │ └── tweet.svg │ ├── Marindeck_logo.imageset │ │ ├── Contents.json │ │ └── Marindeck_logo.svg │ └── TwitterLogo.imageset │ │ ├── Contents.json │ │ └── Logo white.svg ├── Marindeck-Bridging-Header.h ├── Extensions │ ├── String+localized.swift │ ├── Array+safe.swift │ ├── WKProcessPool+Singleton.swift │ ├── WKWebView+safeAreaInsets.swift │ ├── Error+.swift │ ├── UIViewController+setSwipeBack.swift │ ├── String+leftPadding.swift │ ├── UIAlertController+setContentViewController.swift │ ├── UIView+.swift │ ├── Data+mimeType.swift │ ├── Notification+Keyboard.swift │ ├── UIColor+themes.swift │ ├── UIImage+URL.swift │ ├── WKWebView+evalute.swift │ ├── WKWebView+inject.swift │ ├── UIColor+hex.swift │ ├── Date+timeAgoSinceDate.swift │ ├── WKWebView+loadFile.swift │ ├── UILabel+Padding.swift │ └── ViewController+biometrics.swift ├── Application │ ├── Test.swift │ ├── Update.swift │ ├── SceneDelegate.swift │ └── AppDelegate.swift ├── Strings │ ├── en.lproj │ │ ├── InfoPlist.strings │ │ └── Localizable.strings │ └── ja.lproj │ │ └── Localizable.strings ├── Marindeck.entitlements ├── Model │ ├── Model.xcdatamodeld │ │ └── customCSS.xcdatamodel │ │ │ └── contents │ ├── Remote │ │ ├── RemoteJSData.swift │ │ └── RemoteJSIds.swift │ ├── TD │ │ ├── TD+Settings.swift │ │ ├── TDTools.swift │ │ ├── TweetDeck.swift │ │ ├── TD+Account.swift │ │ └── TD+Actions.swift │ └── Handlers │ │ └── General.swift ├── Const │ ├── DebugSettings.swift │ └── JSCallbackFlag.swift ├── Asset │ ├── TermsOfUse.md │ └── license.md ├── MarinDeck.storekit ├── View │ ├── Common │ │ ├── ImageHapticPreviewViewController.swift │ │ ├── SimpleMarkDownViewerViewController.swift │ │ └── AppDebugView.swift │ ├── OnBoarding │ │ └── OnBoardingViewController.swift │ ├── Settings │ │ ├── Custom │ │ │ ├── CustomAddCellTableViewCell.swift │ │ │ └── CustomCellTableViewCell.swift │ │ ├── Theme │ │ │ ├── ThemeTableViewCell.swift │ │ │ └── ThemeDetailViewController.swift │ │ ├── Status │ │ │ └── StatusView.swift │ │ ├── Icon │ │ │ └── IconSettingsView.swift │ │ ├── TwitterSettingsViewController.swift │ │ ├── CustomCSS │ │ │ └── EditCustomCSSViewController.xib │ │ └── CustomJS │ │ │ └── EditCustomJSViewController.xib │ ├── Core │ │ ├── ViewController+Action.swift │ │ ├── ViewController+Giphy.swift │ │ ├── ViewController+ImagePicker.swift │ │ ├── ViewController+WKNavigationDelegate.swift │ │ └── ViewController+MenuView.swift │ ├── Other │ │ ├── DraftViewController.swift │ │ └── ModalBrowserViewController.swift │ ├── Menu │ │ └── MenuItemView.swift │ ├── ja.lproj │ │ └── Main.strings │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ └── Login │ │ └── LoginViewController.swift ├── Database │ └── Database.swift ├── Util │ └── RemoteJS.swift ├── Info-dev.plist └── Info.plist ├── MarinDeckExtension ├── Assets.xcassets │ ├── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── WidgetBackground.colorset │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── MarinDeckExtension.intentdefinition └── MarinDeckExtension.swift ├── .gitmodules ├── Gemfile ├── .gitignore ├── scripts ├── vite.sh └── swiftlint.sh ├── Dangerfile ├── MarinDeckExtensionExtension.entitlements ├── swiftgen.yml ├── .jazzy.yaml ├── Dangerfile.swift ├── .github ├── FUNDING.yml └── workflows │ └── danger_swift.yml ├── Podfile ├── Podfile.lock ├── .swiftlint.yml ├── MarindeckTests └── MarindeckTests.swift ├── README-ja.md ├── README.md └── project.yml /Mintfile: -------------------------------------------------------------------------------- 1 | realm/SwiftLint@0.48.0 2 | -------------------------------------------------------------------------------- /DangerTools/Sources/DangerTools/Empty.swift: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Binding/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | rm -rf *.xcodeproj 2 | rm -rf *xcworkspace 3 | rm -rf Pods 4 | -------------------------------------------------------------------------------- /Binding/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist-ssr 4 | *.local 5 | -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/docs/img/spinner.gif -------------------------------------------------------------------------------- /docs/docsets/Marindeck.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/docs/docsets/Marindeck.tgz -------------------------------------------------------------------------------- /Marindeck/Icons/BlackIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Icons/BlackIcon.png -------------------------------------------------------------------------------- /Marindeck/Icons/RainbowIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Icons/RainbowIcon.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/Theme/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MarinDeckExtension/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Binding/MarinDeckObject"] 2 | path = Binding/MarinDeckObject 3 | url = https://github.com/RiniaOkyama/MarinDeckObject 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'cocoapods-keys', '2.2.1' 3 | gem 'cocoapods' , '1.2.1' 4 | gem 'danger' 5 | gem 'danger-swiftlint' 6 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/logo2.imageset/logo2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/logo2.imageset/logo2.pdf -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.zip 3 | *.ipa 4 | .idea/ 5 | Pods/ 6 | Marindeck/Application/.env 7 | Marindeck.xcworkspace 8 | Marindeck.xcodeproj 9 | MarindeckKeys.m 10 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/BlackIcon.imageset/BlackIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/BlackIcon.imageset/BlackIcon.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/icon-white.imageset/icon-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/icon-white.imageset/icon-white.png -------------------------------------------------------------------------------- /docs/docsets/Marindeck.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/docs/docsets/Marindeck.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/DefaultIcon.imageset/DefaultIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/DefaultIcon.imageset/DefaultIcon.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/RainbowIcon.imageset/RainbowIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/RainbowIcon.imageset/RainbowIcon.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/icon-white.imageset/icon-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/icon-white.imageset/icon-white@2x.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/icon-white.imageset/icon-white@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/icon-white.imageset/icon-white@3x.png -------------------------------------------------------------------------------- /Marindeck/Marindeck-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import 6 | -------------------------------------------------------------------------------- /docs/docsets/Marindeck.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/docs/docsets/Marindeck.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/Marindeck.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/docs/docsets/Marindeck.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /scripts/vite.sh: -------------------------------------------------------------------------------- 1 | export PATH=$HOME/.nodebrew/current/bin:$PATH 2 | 3 | if which yarn > /dev/null; then 4 | cd ./Binding && yarn && yarn build 5 | else 6 | echo "warning: Yarn not installed." 7 | fi 8 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # Github 2 | warn("PRのサイズでかい。") if git.lines_of_code > 1000 3 | 4 | # github.dismiss_out_of_range_messages 5 | swiftlint.config_file = '.swiftlint.yml' 6 | swiftlint.lint_files inline_mode: true 7 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/Theme/Frostclear-icon.imageset/Frostclear-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/Theme/Frostclear-icon.imageset/Frostclear-icon.png -------------------------------------------------------------------------------- /docs/docsets/Marindeck.docset/Contents/Resources/Documents/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/docs/docsets/Marindeck.docset/Contents/Resources/Documents/img/spinner.gif -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/MarinDeckWithText-dark.imageset/MarinDeckWithText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/MarinDeckWithText-dark.imageset/MarinDeckWithText.png -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/Theme/Dolce-icon.imageset/DolceThemeIcon - しらかばるなこ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS/HEAD/Marindeck/Assets.xcassets/Theme/Dolce-icon.imageset/DolceThemeIcon - しらかばるなこ.png -------------------------------------------------------------------------------- /Marindeck/Extensions/String+localized.swift: -------------------------------------------------------------------------------- 1 | // MARK: String extension 2 | import Foundation 3 | 4 | extension String { 5 | var localized: String { 6 | return NSLocalizedString(self, comment: "") 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MarinDeckExtension/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MarinDeckExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Marindeck/Application/Test.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Test.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/05. 6 | // 7 | #if DEBUG 8 | import Foundation 9 | 10 | class Test { 11 | 12 | init() { 13 | } 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /Marindeck/Extensions/Array+safe.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+safe.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/01/04. 6 | // 7 | 8 | extension Array { 9 | subscript (safe index: Index) -> Element? { indices.contains(index) ? self[index] : nil } 10 | } 11 | -------------------------------------------------------------------------------- /Marindeck/Extensions/WKProcessPool+Singleton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WKProcessPool+Singleton.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/11/07. 6 | // 7 | 8 | import WebKit 9 | 10 | extension WKProcessPool { 11 | static let shared = WKProcessPool() 12 | } 13 | -------------------------------------------------------------------------------- /DangerTools/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "DangerTools", 6 | dependencies: [ 7 | .package(url: "https://github.com/realm/SwiftLint.git", from: "0.45.1") 8 | ], 9 | targets: [.target(name: "DangerTools", path: "")] 10 | ) 11 | -------------------------------------------------------------------------------- /Marindeck/Strings/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | infoPlist.strings 3 | Marindeck 4 | 5 | Created by a on 2022/01/22. 6 | 7 | */ 8 | NSCameraUsageDescription = "Use the camera to post images"; 9 | NSFaceIDUsageDescription = "Used to lock the application"; 10 | NSPhotoLibraryAddUsageDescription = "Save the posted image"; 11 | -------------------------------------------------------------------------------- /scripts/swiftlint.sh: -------------------------------------------------------------------------------- 1 | if [ "$CONFIGURATION" == "Debug" ]; then 2 | if which mint >/dev/null; then 3 | xcrun --sdk macosx mint run swiftlint swiftlint autocorrect --format 4 | xcrun --sdk macosx mint run swiftlint swiftlint 5 | else 6 | echo "warning: Mint not installed, download from https://github.com/yonaskolb/Mint" 7 | fi 8 | fi 9 | -------------------------------------------------------------------------------- /Marindeck/Marindeck.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Marindeck/Extensions/WKWebView+safeAreaInsets.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+safeAreaInsets.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/05/16. 6 | // 7 | 8 | import WebKit 9 | 10 | extension WKWebView { 11 | override open var safeAreaInsets: UIEdgeInsets { 12 | return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Marindeck/Model/Model.xcdatamodeld/customCSS.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /MarinDeckExtensionExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Marindeck/Extensions/Error+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error+.swift 3 | // Marindecker 4 | // 5 | // Created by Rinia on 2021/01/17. 6 | // 7 | 8 | import Foundation 9 | extension Error { 10 | var code: Int { return (self as NSError).code } 11 | var domain: String { return (self as NSError).domain } 12 | var info: [String: Any] { return (self as NSError).userInfo } 13 | } 14 | -------------------------------------------------------------------------------- /Marindeck/Model/Remote/RemoteJSData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteJSData.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/04. 6 | // 7 | import GRDB 8 | 9 | struct RemoteJSData: Codable, FetchableRecord, PersistableRecord { 10 | let _id: Int64? 11 | let id: String 12 | let title: String 13 | var version: Int 14 | var jsUrl: String 15 | var js: String? 16 | } 17 | -------------------------------------------------------------------------------- /Binding/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marindeck", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "tsc && vite build", 7 | "preview": "vite preview" 8 | }, 9 | "devDependencies": { 10 | "typescript": "^4.4.4", 11 | "vite": "^2.7.2" 12 | }, 13 | "dependencies": { 14 | "object": "link:MarinDeckObject", 15 | "@types/jquery": "^3.5.13", 16 | "@types/twit": "^2.2.30" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/gif.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "gif.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/logo2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "logo2.pdf", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/photo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "photo.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/tweet.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "tweet.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marindeck/Model/Remote/RemoteJSIds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteJSIds.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/04. 6 | // 7 | 8 | enum RemoteJSDataId: String { 9 | case millisecondDisplay = "6251e263c1ee45ef94894399898c6b2c" 10 | case navigationTab = "f3beff0c72184100bb4f1f55a1147586" 11 | case bottomTweetModal = "1923ebc903de49269205b9b8e345c091" 12 | case emergency = "25554b54c7b848e3b027bb097f4a37d9" 13 | } 14 | -------------------------------------------------------------------------------- /swiftgen.yml: -------------------------------------------------------------------------------- 1 | strings: 2 | inputs: 3 | - MarinDeck/Base.lproj 4 | - Marindeck/Strings/ja.lproj 5 | # - Marindeck/Strings/en.lproj 6 | filter: .+\.strings$ 7 | outputs: 8 | - templateName: structured-swift5 9 | output: MarinDeck/Generated/strings.swift 10 | xcassets: 11 | inputs: 12 | - MarinDeck/Assets.xcassets 13 | outputs: 14 | - templateName: swift5 15 | output: MarinDeck/Generated/assets-images.swift 16 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/BlackIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BlackIcon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/DefaultIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "DefaultIcon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/RainbowIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "RainbowIcon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/Marindeck_logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "Marindeck_logo.svg", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.jazzy.yaml: -------------------------------------------------------------------------------- 1 | # minimum access control level。[private | fileprivate | internal | public | open] 2 | min_acl: internal 3 | 4 | #実行前に出力先フォルダをクリーン 5 | clean: true 6 | 7 | # ドキュメント出力先フォルダ 8 | output: docs 9 | 10 | # 著者 11 | author: RiniaOk_yama 12 | 13 | # 著者のURL 14 | author_url: github.com/riniaokyama/marindeck4ios 15 | 16 | # ドキュメント化するモジュールの名前 17 | module: Marindeck 18 | 19 | # xcodebuildの引数 20 | xcodebuild_arguments: [-workspace,Marindeck.xcworkspace,-scheme,Marindeck] 21 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/MarinDeckWithText-dark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "MarinDeckWithText.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/Theme/Frostclear-icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Frostclear-icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/Theme/Dolce-icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "DolceThemeIcon - しらかばるなこ.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Marindeck/Const/DebugSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Rinia on 2021/11/02. 3 | // 4 | 5 | import Foundation 6 | 7 | struct DebugSettings { 8 | #if DEBUG 9 | static let isWebViewLoad = true 10 | static let remoteJsUrl = "https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS-RemoteJS/main/v1.json" 11 | #else 12 | static let isWebViewLoad = true 13 | static let remoteJsUrl = "https://raw.githubusercontent.com/RiniaOkyama/MarinDeck4iOS-RemoteJS/main/v1.json" 14 | #endif 15 | } 16 | -------------------------------------------------------------------------------- /Marindeck/Asset/TermsOfUse.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ## クラッシュレポート 13 | アプリがクラッシュすると、問題の原因を修正するためにレポートが作成されます。このレポートには、通常は特徴的なコンピュータシステムに関する技術情報が含まれています。これらのレポートを送信するかどうかを選択できます。過去にレポートを送信することを選択した場合でも、設定でレポートをオフにすることができます。 14 | 15 | ## お問い合わせ 16 | * ご質問やご要望等がございましたらDiscordでお知らせください。 17 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/TwitterLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Logo white.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Binding/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": false, 15 | "noImplicitReturns": true, 16 | "noImplicitAny": false 17 | }, 18 | "include": ["./src"] 19 | } 20 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/icon-white.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon-white.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icon-white@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "icon-white@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Marindeck/Extensions/UIViewController+setSwipeBack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+setSwipeBack.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/03/09. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIViewController { 11 | func setSwipeBack() { 12 | let target = self.navigationController?.value(forKey: "_cachedInteractionController") 13 | let recognizer = UIPanGestureRecognizer(target: target, action: Selector(("handleNavigationTransition:"))) 14 | self.view.addGestureRecognizer(recognizer) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Marindeck/Extensions/String+leftPadding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+leftPadding.swift 3 | // Marindecker 4 | // 5 | // Created by Rinia on 2021/01/13. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | 12 | func leftPadding(toLength: Int, withPad: String) -> String { 13 | let stringLength = self.count 14 | if stringLength < toLength { 15 | return String(repeating: withPad, count: toLength - stringLength) + self 16 | } else { 17 | return String(self.suffix(toLength)) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Marindeck/Extensions/UIAlertController+setContentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertController+setContentViewController.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/21. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIAlertController { 11 | func setContentViewController(vc: UIViewController, height: CGFloat? = nil) { 12 | setValue(vc, forKey: "contentViewController") 13 | vc.preferredContentSize.width = 300 14 | 15 | if let height = height { 16 | vc.preferredContentSize.height = height 17 | preferredContentSize.height = height 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Dangerfile.swift: -------------------------------------------------------------------------------- 1 | import Danger 2 | 3 | // fileImport: DangerExtensions/Shell.swift 4 | let danger = Danger() 5 | 6 | if let swiftLintVersion = shell("swiftlint", "version") { 7 | danger.message("SwiftLint: \(swiftLintVersion)") 8 | } 9 | if let dangerJSVersion = shell("danger-js", "--version") { 10 | danger.message("danger-js: \(dangerJSVersion)") 11 | } 12 | if let dangerSwiftVersion = shell("danger-swift", "--version") { 13 | danger.message("danger-swift: \(dangerSwiftVersion)") 14 | } 15 | if let swiftVersion = shell("swift", "--version") { 16 | danger.message(swiftVersion) 17 | } 18 | danger.message("Validation passed! 🎉") 19 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/photo.imageset/photo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/gif.imageset/gif.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Marindeck/Extensions/UIView+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+.swift 3 | // Marindecker 4 | // 5 | // Created by Rinia on 2021/01/13. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | extension UIView { 12 | func anchorAll(equalTo: UIView) { 13 | translatesAutoresizingMaskIntoConstraints = false 14 | topAnchor.constraint(equalTo: equalTo.topAnchor, constant: 0).isActive = true 15 | leftAnchor.constraint(equalTo: equalTo.leftAnchor, constant: 0).isActive = true 16 | bottomAnchor.constraint(equalTo: equalTo.bottomAnchor, constant: 0).isActive = true 17 | rightAnchor.constraint(equalTo: equalTo.rightAnchor, constant: 0).isActive = true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Marindeck/Model/TD/TD+Settings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TD+Settings.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/01/25. 6 | // 7 | 8 | extension TD.Settings { 9 | 10 | enum Theme: String { 11 | case light = "light" 12 | case dark = "dark" 13 | } 14 | 15 | func getTheme(completion: @escaping (_ theme: Theme?) -> Void) { 16 | webView?.evaluateJavaScript("TD.settings.getTheme()") { value, _ in 17 | completion(.init(rawValue: value as? String ?? "")) 18 | } 19 | } 20 | 21 | func setTheme(_ theme: Theme) { 22 | webView?.evaluateJavaScript("TD.settings.setTheme(\(theme.rawValue)", completionHandler: nil) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Binding/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | // import copy from 'rollup-plugin-copy' 3 | 4 | const { resolve } = require('path') 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | esbuild: { 9 | minify: false 10 | }, 11 | build: { 12 | minify: false, 13 | rollupOptions: { 14 | input: 'src/main.ts', 15 | output: { 16 | entryFileNames: 'marindeck.js' 17 | } 18 | }, 19 | outDir: '../Marindeck/js' 20 | }, 21 | // plugins: [ 22 | // reactRefresh(), 23 | // copy({ 24 | // verbose: true, 25 | // hook: 'writeBundle', 26 | // targets: [ 27 | // ], 28 | // }), 29 | // ] 30 | }) 31 | -------------------------------------------------------------------------------- /Marindeck/Extensions/Data+mimeType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+mimeType.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/01/26. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Data { 11 | private static let mimeTypeSignatures: [UInt8: String] = [ 12 | 0xFF: "image/jpeg", 13 | 0x89: "image/png", 14 | 0x47: "image/gif", 15 | 0x49: "image/tiff", 16 | 0x4D: "image/tiff", 17 | 0x25: "application/pdf", 18 | 0xD0: "application/vnd", 19 | 0x46: "text/plain" 20 | ] 21 | 22 | var mimeType: String { 23 | var c: UInt8 = 0 24 | copyBytes(to: &c, count: 1) 25 | return Data.mimeTypeSignatures[c] ?? "application/octet-stream" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Binding/src/main.ts: -------------------------------------------------------------------------------- 1 | import * as marindeck from './marindeck' 2 | //@ts-ignore TS6133: 'req' is declared but its value is never read. 3 | import { MarinDeckBindings, MarinDeckBindingsInterface } from './binding' 4 | import { MarinDeckInputs, MarinDeckInputsInterface } from './inputs' 5 | import * as marindeckObject from "../MarinDeckObject/src" 6 | 7 | declare global { 8 | interface Window { 9 | MarinDeckInputs: MarinDeckInputsInterface, 10 | Bindings: MarinDeckBindingsInterface 11 | } 12 | } 13 | 14 | 15 | marindeck 16 | window.MarinDeckInputs = new MarinDeckInputs() 17 | window.Bindings = new MarinDeckBindings() 18 | marindeckObject.configure() 19 | 20 | // binding.openSettings() 21 | // marindeck.postTweet("hello") -------------------------------------------------------------------------------- /docs/docsets/Marindeck.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.marindeck 7 | CFBundleName 8 | Marindeck 9 | DocSetPlatformFamily 10 | marindeck 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /Binding/src/binding.ts: -------------------------------------------------------------------------------- 1 | export interface MarinDeckBindingsInterface { 2 | openSettings(): void 3 | alert(text: string): void 4 | openUrl(url: string): void 5 | } 6 | 7 | export class MarinDeckBindings implements MarinDeckBindingsInterface { 8 | openSettings() { 9 | window.MD.Native.post({ 10 | type: 'openSettings', 11 | body: { value: true } 12 | }) 13 | } 14 | 15 | alert(text: string): void { 16 | window.MD.Native.post({ 17 | type: "presentAlert", 18 | body: { text: text } 19 | }) 20 | } 21 | 22 | openUrl(url: string): void { 23 | window.MD.Native.post({ 24 | type: "openUrl", 25 | body: { url: url } 26 | }) 27 | } 28 | } -------------------------------------------------------------------------------- /Marindeck/Model/TD/TDTools.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TDTools.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/11. 6 | // 7 | 8 | import Foundation 9 | 10 | class TDTools { 11 | 12 | static func url2SmallImg(_ str: String) -> String { 13 | var r = str.replacingOccurrences(of: "url(\"", with: "") 14 | r = r.replacingOccurrences(of: "\")", with: "") 15 | return r 16 | } 17 | 18 | static func url2NomalImg(_ str: String) -> String { 19 | var r = url2SmallImg(str) 20 | if let index = r.range(of: "&name")?.lowerBound { 21 | r = String(r[...index]) // + "name=orig" 22 | return r.replacingOccurrences(of: "format=jpg", with: "format=png") 23 | } 24 | return "" 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Marindeck/Extensions/Notification+Keyboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notification+Keyboard.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/11/25. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | extension Notification { 12 | 13 | // キーボードの高さ 14 | var keyboardHeight: CGFloat? { 15 | return (self.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height 16 | } 17 | 18 | // キーボードのアニメーション時間 19 | var keybaordAnimationDuration: TimeInterval? { 20 | return self.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval 21 | } 22 | 23 | // キーボードのアニメーション曲線 24 | var keyboardAnimationCurve: UInt? { 25 | return self.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Marindeck/MarinDeck.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "DC4893CB", 3 | "nonRenewingSubscriptions" : [ 4 | 5 | ], 6 | "products" : [ 7 | { 8 | "displayPrice" : "1.99", 9 | "familyShareable" : false, 10 | "internalID" : "6445212370", 11 | "localizations" : [ 12 | { 13 | "description" : "MarinDeckに320円の寄付ができます。", 14 | "displayName" : "320円寄付", 15 | "locale" : "ja" 16 | } 17 | ], 18 | "productID" : "300yen", 19 | "referenceName" : "300円", 20 | "type" : "Consumable" 21 | } 22 | ], 23 | "settings" : { 24 | "_applicationInternalID" : "1558663979", 25 | "_developerTeamID" : "726VR75V6L", 26 | "_lastSynchronizedDate" : 693436252.22703195 27 | }, 28 | "subscriptionGroups" : [ 29 | 30 | ], 31 | "version" : { 32 | "major" : 2, 33 | "minor" : 0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [RiniaOkyama, HiSubway] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: hisubway # Replace with a single Patreon username 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # otechie: # Replace with a single Otechie username 12 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /Marindeck/Model/TD/TweetDeck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweetDeck.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/11/24. 6 | // 7 | 8 | // MARK: DO NOT USE. WIP. 9 | // MARK: 使わないでネ 10 | 11 | import Foundation 12 | import WebKit 13 | 14 | class TDBase { 15 | weak var webView: WKWebView? 16 | } 17 | 18 | final class TD: TDBase { 19 | static let shared = TD() 20 | 21 | public let account: AccountController = .init() 22 | public let settings: Settings = .init() 23 | public let actions: ActionsController = .init() 24 | 25 | override var webView: WKWebView? { 26 | get { super.webView } 27 | set(value) { 28 | super.webView = value 29 | 30 | account.webView = value 31 | settings.webView = value 32 | actions.webView = value 33 | } 34 | } 35 | 36 | class Settings: TDBase {} 37 | class AccountController: TDBase {} 38 | class ActionsController: TDBase {} 39 | } 40 | -------------------------------------------------------------------------------- /Marindeck/View/Common/ImageHapticPreviewViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageHapticPreviewViewController.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/05/08. 6 | // 7 | 8 | import UIKit 9 | import AVFoundation 10 | 11 | class ImageHapticPreviewViewController: UIViewController { 12 | private let imageView = UIImageView() 13 | 14 | public var image: UIImage? { 15 | didSet { 16 | preferredContentSize = image?.size ?? CGSize(width: 100, height: 100) 17 | imageView.image = image 18 | } 19 | } 20 | 21 | init(image: UIImage?) { 22 | super.init(nibName: nil, bundle: nil) 23 | preferredContentSize = image?.size ?? CGSize(width: 100, height: 100) // FXIME 24 | imageView.contentMode = .scaleAspectFill 25 | imageView.clipsToBounds = true 26 | imageView.image = image 27 | view = imageView 28 | } 29 | 30 | required init?(coder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Marindeck/Extensions/UIColor+themes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+themes.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/05/10. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIColor { 11 | 12 | static var backgroundColor: UIColor { get { fetchTheme().backgroundColor } } 13 | static var secondaryBackgroundColor: UIColor { get { fetchTheme().secondaryBackgroundColor } } 14 | static var labelColor: UIColor { get { fetchTheme().labelColor } } 15 | static var subLabelColor: UIColor { get { fetchTheme().subLabelColor } } 16 | static var tweetButtonColor: UIColor { get { fetchTheme().tweetButtonColor } } 17 | static var lightTopBarColor: UIColor { get { fetchTheme().lightTopBarColor } } 18 | static var darkTopBarColor: UIColor { get { fetchTheme().darkTopBarColor ?? fetchTheme().lightTopBarColor } } 19 | 20 | static func ldColor(light: UIColor, dark: UIColor) -> UIColor { 21 | UIColor { (traits) -> UIColor in 22 | traits.userInterfaceStyle == .dark ? dark: light 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Marindeck/View/OnBoarding/OnBoardingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnBoardingViewController.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/11/06. 6 | // 7 | 8 | import UIKit 9 | 10 | class OnBoardingViewController: UIViewController { 11 | 12 | @IBOutlet weak var startMarinDeckButton: UIButton! 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | startMarinDeckButton.setTitle(L10n.OnBoarding.StartMarinDeck.title, for: .normal) 18 | startMarinDeckButton.backgroundColor = .tweetButtonColor 19 | startMarinDeckButton.setTitleColor(.labelColor, for: .normal) 20 | startMarinDeckButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) 21 | 22 | view.backgroundColor = .backgroundColor 23 | 24 | startMarinDeckButton.clipsToBounds = true 25 | startMarinDeckButton.layer.cornerRadius = 6 26 | } 27 | 28 | @IBAction func start() { 29 | UserDefaults.standard.set(true, forKey: .isOnBoarding) 30 | dismiss(animated: true) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MarinDeckExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | MarinDeckExtension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionPointIdentifier 26 | com.apple.widgetkit-extension 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Marindeck/View/Settings/Custom/CustomAddCellTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomJSAddCellTableViewCell.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/04/12. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol CustomAddCellOutput { 11 | func create() 12 | } 13 | 14 | class CustomAddCellTableViewCell: UITableViewCell { 15 | public var delegate: CustomAddCellOutput! 16 | 17 | @IBOutlet weak var backView: UIView! 18 | 19 | override func awakeFromNib() { 20 | super.awakeFromNib() 21 | 22 | self.backgroundColor = .backgroundColor 23 | backView.layer.cornerRadius = 8 24 | backView.backgroundColor = .secondaryBackgroundColor 25 | 26 | let gesture = UITapGestureRecognizer(target: self, action: #selector(self.create)) 27 | backView.addGestureRecognizer(gesture) 28 | 29 | } 30 | 31 | @objc 32 | func create() { 33 | delegate?.create() 34 | } 35 | 36 | override func setSelected(_ selected: Bool, animated: Bool) { 37 | super.setSelected(selected, animated: animated) 38 | // Configure the view for the selected state 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Marindeck/Extensions/UIImage+URL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+URL.swift 3 | // Marindecker 4 | // 5 | // Created by Rinia on 2021/03/15. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | extension UIImage { 12 | enum FetchError: Error { 13 | case badUrl, badImage, apiError(Error) 14 | } 15 | 16 | public convenience init?(url: String) async throws { 17 | guard let url = URL(string: url) else { 18 | self.init() 19 | return 20 | } 21 | do { 22 | var request = URLRequest(url: url) 23 | // https://developer.apple.com/documentation/foundation/nsurlrequest/cachepolicy/useprotocolcachepolicy 24 | request.cachePolicy = .returnCacheDataElseLoad 25 | 26 | let (data, response) = try await URLSession.shared.data(for: request) 27 | guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badUrl } 28 | self.init(data: data) 29 | return 30 | } catch let err { 31 | print("Error : \(err.localizedDescription)") 32 | } 33 | self.init() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/tweet.imageset/tweet.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 9% 23 | 24 | 25 | 9% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/TwitterLogo.imageset/Logo white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'Marindeck' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | pod "Optik", :git => "https://github.com/RiniaOkyama/Optik" # image preview 8 | pod "AlamofireImage" 9 | pod 'Highlightr' # js highlight 10 | #pod 'HighlightJS' 11 | #pod 'FLEX', :configurations => ['Debug'] # Debuger 12 | pod 'SPAlert' 13 | 14 | pod "SwiftGen" 15 | 16 | # Pods for Marindecker 17 | 18 | end 19 | 20 | target 'Marindeck-dev' do 21 | # Comment the next line if you don't want to use dynamic frameworks 22 | use_frameworks! 23 | pod "Optik", :git => "https://github.com/RiniaOkyama/Optik" #image preview 24 | pod "AlamofireImage" 25 | pod 'Highlightr' # js highlight 26 | #pod 'HighlightJS' 27 | #pod 'FLEX', :configurations => ['Debug'] # Debuger 28 | pod 'SPAlert' 29 | 30 | pod "SwiftGen" 31 | 32 | # Pods for Marindecker 33 | 34 | end 35 | 36 | plugin 'cocoapods-keys', { 37 | :projects => "Marindeck", 38 | :target => ["Marindeck-dev", "Marindeck"], 39 | :keys => [ 40 | "GiphyApiKey", 41 | "DeploygateUsername", 42 | "DeploygateSdkApiKey" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /Marindeck/View/Settings/Custom/CustomCellTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomJSCellTableViewCell.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/04/12. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol CustomCellTableViewCellOutput { 11 | func switched(isOn: Bool, index: Int?) 12 | } 13 | 14 | class CustomCellTableViewCell: UITableViewCell { 15 | public var delegate: CustomCellTableViewCellOutput? 16 | public var index: Int? 17 | 18 | @IBOutlet weak var titleLabel: UILabel! 19 | @IBOutlet weak var dateLabel: UILabel! 20 | @IBOutlet weak var switchView: UISwitch! 21 | 22 | @IBOutlet weak var backView: UIView! 23 | 24 | override func awakeFromNib() { 25 | super.awakeFromNib() 26 | self.backgroundColor = .backgroundColor 27 | self.backView.layer.cornerRadius = 8 28 | backView.backgroundColor = .secondaryBackgroundColor 29 | titleLabel.textColor = .labelColor 30 | dateLabel.textColor = .subLabelColor 31 | } 32 | 33 | override func setSelected(_ selected: Bool, animated: Bool) { 34 | super.setSelected(selected, animated: animated) 35 | 36 | } 37 | 38 | @IBAction func switched() { 39 | delegate?.switched(isOn: switchView.isOn, index: index) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Marindeck/Extensions/WKWebView+evalute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+evalute.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/04/07. 6 | // 7 | 8 | import WebKit 9 | 10 | extension WKWebView { 11 | @discardableResult 12 | func evaluate(javaScript script: String) -> Any? { 13 | var result: Any? 14 | var isCompletion: Bool = false 15 | 16 | self.evaluateJavaScript(script) { value, _ in 17 | result = value 18 | isCompletion = true 19 | } 20 | // FIXME: wtf 21 | while !isCompletion { 22 | RunLoop.current.run(mode: .default, before: Date() + 0.1) 23 | } 24 | return result 25 | } 26 | 27 | @discardableResult 28 | func evaluateWithError(javaScript script: String) -> (Any?, Error?) { 29 | var result: Any? 30 | var error: Error? 31 | var isCompletion: Bool = false 32 | 33 | self.evaluateJavaScript(script) { value, errorLog in 34 | result = value 35 | error = errorLog 36 | isCompletion = true 37 | } 38 | // FXIME: crap 39 | while !isCompletion { 40 | RunLoop.current.run(mode: .default, before: Date() + 0.1) 41 | } 42 | return (result, error) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Marindeck/Const/JSCallbackFlag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSCallbackFlag.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/10/06. 6 | // 7 | 8 | enum JSCallbackFlag: String, CaseIterable, Codable { 9 | case jsCallbackHandler = "jsCallbackHandler" // デバッグ用 10 | case imagePreviewer = "imagePreviewer" 11 | case imageViewPos = "imageViewPos" // 画像の座標 12 | case viewDidLoad = "viewDidLoad" 13 | case openSettings = "openSettings" 14 | case presentAlert = "presentAlert" 15 | case fetchImage = "fetchImage" 16 | case isTweetButtonHidden = "isTweetButtonHidden" 17 | case openYoutube = "openYoutube" 18 | case sidebar = "sidebar" 19 | case config = "config" 20 | case openUrl = "openUrl" 21 | } 22 | 23 | protocol JSCallback { 24 | func viewDidLoad() 25 | func jsCallbackHandler(log: Any?) 26 | func imageViewPos(positions: [[Float]]) 27 | func imagePreviewer(selectedIndex: Int, urls: [String]) 28 | func openSettings() 29 | func presentAlert(message: String) 30 | func fetchImage(url: String) 31 | func isTweetButtonState(isHidden: Bool) 32 | func openYoutube(url: String) 33 | func sidebarState(isOpen: Bool) 34 | func setConfig(key: String, value: String) 35 | func getConfig(key: String) -> Any? 36 | func openUrl(url: String) 37 | } 38 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.5.0) 3 | - AlamofireImage (4.2.0): 4 | - Alamofire (~> 5.4) 5 | - Highlightr (2.1.0) 6 | - Keys (1.0.1) 7 | - Optik (0.4.0) 8 | - SPAlert (4.2.0) 9 | - SwiftGen (6.5.1) 10 | 11 | DEPENDENCIES: 12 | - AlamofireImage 13 | - Highlightr 14 | - Keys (from `Pods/CocoaPodsKeys`) 15 | - Optik (from `https://github.com/RiniaOkyama/Optik`) 16 | - SPAlert 17 | - SwiftGen 18 | 19 | SPEC REPOS: 20 | trunk: 21 | - Alamofire 22 | - AlamofireImage 23 | - Highlightr 24 | - SPAlert 25 | - SwiftGen 26 | 27 | EXTERNAL SOURCES: 28 | Keys: 29 | :path: Pods/CocoaPodsKeys 30 | Optik: 31 | :git: https://github.com/RiniaOkyama/Optik 32 | 33 | CHECKOUT OPTIONS: 34 | Optik: 35 | :commit: bea6b823f3902b05084f49c721c940a3f8f69c27 36 | :git: https://github.com/RiniaOkyama/Optik 37 | 38 | SPEC CHECKSUMS: 39 | Alamofire: 1c4fb5369c3fe93d2857c780d8bbe09f06f97e7c 40 | AlamofireImage: 34a2d90b0e5fe6a5605f85ae4b7b01e784c60192 41 | Highlightr: 595f3e100737c8de41113385da8bd0b5b65212c6 42 | Keys: a576f4c9c1c641ca913a959a9c62ed3f215a8de9 43 | Optik: 64066ad701b3d440bc4b3847a5f354b602e188aa 44 | SPAlert: 735da1f16a887e294719217572ce1f936d8c8782 45 | SwiftGen: a6d22010845f08fe18fbdf3a07a8e380fd22e0ea 46 | 47 | PODFILE CHECKSUM: b4e556e0501898e1865f58d204a9f8fe207c3517 48 | 49 | COCOAPODS: 1.11.2 50 | -------------------------------------------------------------------------------- /Marindeck/Extensions/WKWebView+inject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+inject.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/11. 6 | // 7 | 8 | import Foundation 9 | import WebKit 10 | 11 | extension WKWebView { 12 | 13 | // FIXME: evaluteWithErrorは使用しない実装に変更 14 | // JS デバッグ 15 | @discardableResult 16 | func inject(js: String) -> (String, Error?) { 17 | let (ret, error) = evaluateWithError(javaScript: js) 18 | return ((ret as? String) ?? "", error) 19 | } 20 | 21 | // CSSをjsに変換 22 | static func css2JS(css: String) -> String { 23 | var deletecomment = css.replacingOccurrences(of: "[\\s\\t]*/\\*/?(\\n|[^/]|[^*]/)*\\*/", with: "") 24 | deletecomment = deletecomment.replacingOccurrences(of: "\"", with: "\\\"") 25 | deletecomment = deletecomment.replacingOccurrences(of: "\n", with: "\\\n") 26 | let script = """ 27 | (() => { 28 | const h = document.documentElement; 29 | const s = document.createElement('style'); 30 | s.insertAdjacentHTML('beforeend', "\(deletecomment)"); 31 | h.insertAdjacentElement('beforeend', s); 32 | })(); 33 | """ 34 | return script 35 | } 36 | 37 | // CSS デバッグ 38 | func inject(css: String) { 39 | let script = WKWebView.css2JS(css: css) 40 | evaluateJavaScript(script) { _, error in 41 | print("stylecss : ", error ?? "成功") 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | - Carthage 3 | - Pods 4 | 5 | opt_in_rules: 6 | - empty_count 7 | - explicit_init 8 | - closure_spacing 9 | - overridden_super_call 10 | - redundant_nil_coalescing 11 | - private_outlet 12 | - nimble_operator 13 | - attributes 14 | - operator_usage_whitespace 15 | - closure_end_indentation 16 | - first_where 17 | - object_literal 18 | - number_separator 19 | - prohibited_super_call 20 | - fatal_error_message 21 | - todo 22 | 23 | disabled_rules: 24 | - trailing_whitespace 25 | - cyclomatic_complexity 26 | - function_body_length 27 | - type_name 28 | - shorthand_operator 29 | - multiple_closures_with_trailing_closure 30 | - closure_end_indentation 31 | - empty_count 32 | - identifier_name 33 | - force_cast 34 | - file_length 35 | - type_body_length 36 | - unused_setter_value 37 | - function_parameter_count 38 | - switch_case_on_newline 39 | - implicit_getter 40 | - force_try 41 | - variable_name 42 | - redundant_string_enum_value 43 | - private_outlet 44 | # Custom rules 45 | identifier_name: 46 | min_length: 1 47 | force_cast: warning 48 | force_try: 49 | severity: warning 50 | 51 | # 1行の文字列制限 52 | line_length: 53 | - 200 # warning 54 | - 300 # error 55 | 56 | # 型の行数制限 57 | type_body_length: 58 | - 400 # warning 59 | - 600 # error 60 | 61 | # 1ファイルの行数制限 62 | file_length: 63 | - 500 # warning 64 | - 1000 # error 65 | 66 | # メソッドの行数制限 67 | function_body_length: 68 | - 100 # warning 69 | - 200 # error 70 | number_separator: 71 | minimum_length: 5 72 | large_tuple: 3 73 | -------------------------------------------------------------------------------- /Marindeck/Extensions/UIColor+hex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+hex.swift 3 | // Marindecker 4 | // 5 | // Created by Rinia on 2021/01/13. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | // hex UIColor 12 | extension UIColor { 13 | convenience init(hex: String, alpha: CGFloat = 1.0) { 14 | let v = Int("000000" + hex, radix: 16) ?? 0 15 | let r = CGFloat(v / Int(powf(256, 2)) % 256) / 255 16 | let g = CGFloat(v / Int(powf(256, 1)) % 256) / 255 17 | let b = CGFloat(v / Int(powf(256, 0)) % 256) / 255 18 | self.init(red: r, green: g, blue: b, alpha: min(max(alpha, 0), 1)) 19 | } 20 | 21 | func toHexString() -> String { 22 | var red: CGFloat = 1.0 23 | var green: CGFloat = 1.0 24 | var blue: CGFloat = 1.0 25 | var alpha: CGFloat = 1.0 26 | self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 27 | print(Int(red * 255), Int(green * 255), Int(blue * 255)) 28 | 29 | let r = Int(String(Int(floor(red * 100) / 100 * 255)).replacingOccurrences(of: "-", with: ""))! 30 | let g = Int(String(Int(floor(green * 100) / 100 * 255)).replacingOccurrences(of: "-", with: ""))! 31 | let b = Int(String(Int(floor(blue * 100) / 100 * 255)).replacingOccurrences(of: "-", with: ""))! 32 | 33 | let res = String(r, radix: 16) 34 | .leftPadding(toLength: 2, withPad: "0") + 35 | String(g, radix: 16).leftPadding(toLength: 2, withPad: "0") + 36 | String(b, radix: 16).leftPadding(toLength: 2, withPad: "0") 37 | return res 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Marindeck/View/Settings/Theme/ThemeTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThemeTableViewCell.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/05/06. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol ThemeTableViewCellDelegate: AnyObject { 11 | func apply(tag: Int) 12 | } 13 | 14 | class ThemeTableViewCell: UITableViewCell { 15 | @IBOutlet weak var backView: UIView! 16 | @IBOutlet weak var iconView: UIImageView! 17 | @IBOutlet weak var titleLabel: UILabel! 18 | @IBOutlet weak var descriptionLabel: UILabel! 19 | @IBOutlet weak var applyButton: UIButton! 20 | 21 | public var delegate: ThemeTableViewCellDelegate! 22 | 23 | override func awakeFromNib() { 24 | super.awakeFromNib() 25 | 26 | iconView.clipsToBounds = true 27 | iconView.layer.cornerRadius = iconView.frame.width / 2 28 | backView.layer.cornerRadius = 8 29 | applyButton.layer.cornerRadius = 8 30 | applyButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 0) 31 | 32 | selectionStyle = .none 33 | 34 | // self.backView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapped))) 35 | } 36 | 37 | @IBAction func apply() { 38 | delegate.apply(tag: self.tag) 39 | } 40 | 41 | // @objc func tapped() { 42 | // backView.backgroundColor = .gray 43 | // UIView.animate(withDuration: 0.3, animations: { 44 | // self.backView.backgroundColor = .systemBackground 45 | // }) 46 | // } 47 | 48 | override func setSelected(_ selected: Bool, animated: Bool) { 49 | super.setSelected(selected, animated: animated) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Marindeck/View/Settings/Status/StatusView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusView.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/01/05. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct StatusView: View { 11 | // @Environment(\.presentationMode) var presentationMode 12 | 13 | init() { 14 | } 15 | 16 | var body: some View { 17 | VStack { 18 | HStack { 19 | Text("利用日数") 20 | .frame(width: .infinity) 21 | Text("100日") 22 | .fontWeight(.bold) 23 | .font(.title) 24 | .frame(width: 100) 25 | } 26 | 27 | HStack { 28 | Text("利用日数") 29 | .frame(width: .infinity) 30 | Text("101日") 31 | .fontWeight(.bold) 32 | .font(.title) 33 | .frame(width: 100) 34 | } 35 | 36 | HStack { 37 | Text("利用日数") 38 | .frame(width: .infinity) 39 | Text("100日") 40 | .fontWeight(.bold) 41 | .font(.title) 42 | .frame(width: 100) 43 | } 44 | 45 | HStack { 46 | Text("利用日数") 47 | .frame(width: .infinity) 48 | Text("100日") 49 | .fontWeight(.bold) 50 | .font(.title) 51 | .frame(width: 100) 52 | } 53 | 54 | } 55 | } 56 | } 57 | 58 | #if DEBUG 59 | struct SatusView_Previews: PreviewProvider { 60 | static var previews: some View { 61 | StatusView() 62 | } 63 | } 64 | #endif 65 | -------------------------------------------------------------------------------- /Marindeck/Extensions/Date+timeAgoSinceDate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+timeAgoSinceDate.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/04/12. 6 | // 7 | 8 | import UIKit 9 | 10 | extension Date { 11 | 12 | func offsetFrom() -> String { 13 | if yearsFrom() > 0 { return "\(yearsFrom())年前" } 14 | if monthsFrom() > 0 { return "\(monthsFrom())ヶ月前" } 15 | if weeksFrom() > 0 { return "\(weeksFrom())週間前" } 16 | if daysFrom() > 0 { return "\(daysFrom())日前" } 17 | if hoursFrom() > 0 { return "\(hoursFrom())時間前" } 18 | if minutesFrom() > 0 { return "\(minutesFrom())分前" } 19 | if secondsFrom() > 0 { return "\(secondsFrom())秒前" } 20 | return "" 21 | } 22 | 23 | func yearsFrom() -> Int { 24 | return Calendar.current.dateComponents([.year], from: self, to: Date()).year ?? 0 25 | } 26 | func monthsFrom() -> Int { 27 | return Calendar.current.dateComponents([.month], from: self, to: Date()).month ?? 0 28 | } 29 | func weeksFrom() -> Int { 30 | return Calendar.current.dateComponents([.weekOfYear], from: self, to: Date()).weekOfYear ?? 0 31 | } 32 | func daysFrom() -> Int { 33 | return Calendar.current.dateComponents([.day], from: self, to: Date()).day ?? 0 34 | } 35 | func hoursFrom() -> Int { 36 | return Calendar.current.dateComponents([.hour], from: self, to: Date()).hour ?? 0 37 | } 38 | func minutesFrom() -> Int { 39 | return Calendar.current.dateComponents([.minute], from: self, to: Date()).minute ?? 0 40 | } 41 | func secondsFrom() -> Int { 42 | return Calendar.current.dateComponents([.second], from: self, to: Date()).second ?? 0 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Marindeck/View/Core/ViewController+Action.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController+Action.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/05. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | protocol ViewControllerAction { 12 | func getPositionElements(x: Int, y: Int) -> (Int, [String]) 13 | func openWebViewTweetModal() 14 | func presentAlert(_ msg: String) 15 | } 16 | 17 | extension ViewController: ViewControllerAction { 18 | // x, yに画像があれば取ってくる 19 | func getPositionElements(x: Int, y: Int) -> (Int, [String]) { 20 | guard let value = webView.evaluate(javaScript: "positionElement(\(x), \(y))") else { 21 | return (0, []) 22 | } 23 | let valueStrings = value as! [Any] 24 | let index = valueStrings[0] as! Int 25 | let urls = valueStrings[1] as! [String] 26 | 27 | let imgUrls = urls.map({ 28 | TDTools.url2NomalImg($0) 29 | }) 30 | print("getPositionElements", index, imgUrls) 31 | 32 | return (index, imgUrls) 33 | } 34 | 35 | // TweetDeckのツイートモーダルに遷移 36 | func openWebViewTweetModal() { 37 | webView.evaluateJavaScript("document.querySelector('.tweet-button.js-show-drawer:not(.is-hidden)').click()") { _, error in 38 | print("webViewLog : ", error ?? "成功") 39 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 40 | self.td.actions.focusTweetTextArea() 41 | } 42 | } 43 | } 44 | 45 | func presentAlert(_ msg: String) { 46 | let alert = UIAlertController(title: "Debugger", message: msg, preferredStyle: .alert) 47 | alert.addAction(UIAlertAction(title: "OK", style: .default)) 48 | present(alert, animated: true) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Marindeck/View/Core/ViewController+Giphy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController+Giphy.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/05. 6 | // 7 | import GiphyUISDK 8 | 9 | extension ViewController: GiphyDelegate { 10 | func didSearch(for term: String) { 11 | print("your user made a search! ", term) 12 | } 13 | 14 | func didSelectMedia(giphyViewController: GiphyViewController, media: GPHMedia) { 15 | giphyViewController.dismiss(animated: true, completion: { [weak self] in 16 | // print(media.url(rendition: .original, fileType: .gif)) 17 | self?.loadingIndicator.startAnimating() 18 | self?.mainDeckBlurView.backgroundColor = UIColor.black.withAlphaComponent(0.3) 19 | DispatchQueue(label: "tweetgifload.async").async { 20 | let url: URL = URL(string: media.url(rendition: .original, fileType: .gif)!)! 21 | // Now use image to create into NSData format 22 | let imageData: NSData = NSData(contentsOf: url)! 23 | let data = imageData.base64EncodedString(options: []) 24 | DispatchQueue.main.sync { 25 | self?.webView.evaluateJavaScript("window.MarinDeckInputs.addTweetImage(\"data:image/gif;base64,\(data)\", \"image/gif\", \"test.gif\")") { _, error in 26 | print("gifLoad : ", error ?? "成功") 27 | self?.loadingIndicator.stopAnimating() 28 | self?.mainDeckBlurView.backgroundColor = .clear 29 | } 30 | } 31 | } 32 | }) 33 | GPHCache.shared.clear() 34 | } 35 | 36 | func didDismiss(controller: GiphyViewController?) { 37 | GPHCache.shared.clear() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/Marindeck_logo.imageset/Marindeck_logo.svg: -------------------------------------------------------------------------------- 1 | 3-2 -------------------------------------------------------------------------------- /MarinDeckExtension/MarinDeckExtension.intentdefinition: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | INEnums 6 | 7 | INIntentDefinitionModelVersion 8 | 1.2 9 | INIntentDefinitionNamespace 10 | 88xZPY 11 | INIntentDefinitionSystemVersion 12 | 20A294 13 | INIntentDefinitionToolsBuildVersion 14 | 12A6144 15 | INIntentDefinitionToolsVersion 16 | 12.0 17 | INIntents 18 | 19 | 20 | INIntentCategory 21 | information 22 | INIntentDescriptionID 23 | tVvJ9c 24 | INIntentEligibleForWidgets 25 | 26 | INIntentIneligibleForSuggestions 27 | 28 | INIntentName 29 | Configuration 30 | INIntentResponse 31 | 32 | INIntentResponseCodes 33 | 34 | 35 | INIntentResponseCodeName 36 | success 37 | INIntentResponseCodeSuccess 38 | 39 | 40 | 41 | INIntentResponseCodeName 42 | failure 43 | 44 | 45 | 46 | INIntentTitle 47 | Configuration 48 | INIntentTitleID 49 | gpCwrM 50 | INIntentType 51 | Custom 52 | INIntentVerb 53 | View 54 | 55 | 56 | INTypes 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /.github/workflows/danger_swift.yml: -------------------------------------------------------------------------------- 1 | name: Danger Swift 2 | 3 | on: [pull_request] 4 | 5 | # jobs: 6 | # Danger: 7 | # runs-on: ubuntu-latest 8 | # steps: 9 | # - uses: actions/checkout@v2 10 | # - name: Danger 11 | # uses: 417-72KI/danger-swiftlint@v5 12 | # env: 13 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | #jobs: 15 | # build: 16 | # runs-on: ubuntu-latest 17 | # name: "Run Danger" 18 | # steps: 19 | # - uses: actions/checkout@v1 20 | # - name: Danger 21 | # uses: docker://ghcr.io/danger/danger-swift-with-swiftlint:3.12.3 22 | # with: 23 | # args: --failOnErrors --no-publish-check 24 | # env: 25 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | jobs: 28 | danger: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v2 32 | - name: Setup Ruby 33 | uses: actions/setup-ruby@v1 34 | with: 35 | ruby-version: '2.5' 36 | - name: Setup gems 37 | run: gem install bundler danger danger-swiftlint 38 | - name: Run Danger 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | run: | 42 | # デフォルトの JSON のパスを取得 43 | event_path=${{ github.event_path }} 44 | 45 | if [ $GITHUB_EVENT_NAME = "pull_request_review" ]; then 46 | echo "Override event json file for pull_request_review event." 47 | 48 | # 読み込む JSON のパスを書き換え 49 | event_path="event.json" 50 | # jq コマンドを使って加工した JSON を↑で置き換えたパスに出力する 51 | cat ${{ github.event_path }} | jq '. |= .+ {"number": .pull_request.number}' > $event_path 52 | fi 53 | 54 | # イベントのパスとイベント名を明示的に指定して Danger を走らせる 55 | GITHUB_EVENT_PATH=$event_path GITHUB_EVENT_NAME=pull_request danger 56 | -------------------------------------------------------------------------------- /Marindeck/View/Common/SimpleMarkDownViewerViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MarkdownView 3 | 4 | final class SimpleMarkDownViewerViewController: UIViewController { 5 | var markdownView = MarkdownView() 6 | var textView = UITextView() 7 | var markdown: String = "" 8 | 9 | init(markdown: String) { 10 | super.init(nibName: nil, bundle: nil) 11 | 12 | self.markdown = markdown 13 | } 14 | 15 | required init?(coder: NSCoder) { 16 | fatalError("init(coder:) has not been implemented") 17 | } 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | view.backgroundColor = .secondaryBackgroundColor 23 | 24 | let subbg = "#\(UIColor.backgroundColor.toHexString())" 25 | let sublb = "#\(UIColor.subLabelColor.toHexString())" 26 | let lb = "#\(UIColor.labelColor.toHexString())" 27 | 28 | markdownView.load( 29 | markdown: markdown, 30 | css: "body {color:#\(UIColor.labelColor.toHexString());}\n pre{background: \(subbg);border: unset} \n .hljs{background-color: \(subbg); color:\(sublb)} \n .hljs-keyword{color:\(lb)}") 31 | markdownView.frame = view.bounds 32 | markdownView.webView?.scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 94, right: 0) 33 | view.addSubview(markdownView) 34 | 35 | markdownView.translatesAutoresizingMaskIntoConstraints = false 36 | markdownView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true 37 | markdownView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 38 | markdownView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true 39 | markdownView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true 40 | 41 | setSwipeBack() 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Marindeck/Application/Update.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Update.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/11/28. 6 | // 7 | 8 | import Foundation 9 | 10 | class Update { 11 | static let shared = Update() 12 | 13 | public let appId = "1558663979" 14 | 15 | func checkForUpdate(completion: @escaping (_ update: Bool) -> Void) { 16 | guard let url = URL(string: "https://itunes.apple.com/jp/lookup?id=\(appId)") else { 17 | completion(false) 18 | return 19 | } 20 | 21 | let request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60) 22 | 23 | let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, _, _) in 24 | guard let data = data else { 25 | completion(false) 26 | return 27 | } 28 | 29 | do { 30 | let jsonData = try JSONSerialization.jsonObject(with: data) as? [String: Any] 31 | guard let storeVersion = ((jsonData?["results"] as? [Any])? 32 | .first as? [String: Any])?["version"] as? String, 33 | let appVersion = Bundle.main 34 | .object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { 35 | completion(false) 36 | return 37 | } 38 | switch storeVersion.compare(appVersion, options: .numeric) { 39 | case .orderedDescending: 40 | completion(true) 41 | return 42 | case .orderedSame, .orderedAscending: 43 | completion(false) 44 | return 45 | } 46 | } catch { 47 | } 48 | }) 49 | task.resume() 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /Marindeck/Model/TD/TD+Account.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TD+Account.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/01/25. 6 | // 7 | 8 | extension TD.AccountController { 9 | 10 | struct Account { 11 | var name: String? 12 | var profileImageUrl: String? 13 | var userId: String? 14 | var username: String? 15 | // FIXME 16 | // var isPrivate: Bool? 17 | // var verified: Bool? 18 | // var updated: String? 19 | // var key: String? 20 | } 21 | 22 | func getAccount(completion: @escaping (_ account: Account) -> Void) { 23 | webView?.evaluateJavaScript("TD.storage.accountController.getDefault().state") {(obj, _) in 24 | guard let dict = obj as? [String: Any] else { return } 25 | completion(Account(name: dict["name"] as? String, 26 | profileImageUrl: dict["profileImageURL"] as? String, 27 | userId: dict["username"] as? String, 28 | username: dict["username"] as? String)) 29 | } 30 | } 31 | 32 | func getAllAccount(completion: @escaping(_ accounts: [Account]) -> Void) { 33 | webView?.evaluateJavaScript("window.TD.storage.accountController.getAll().filter(({managed}) => managed)map(({state: {name: fullname, username, userId, profileImageURL}}) => ({fullname, username, userId, profileImageURL}))") {(obj, _) in 34 | guard let ary = obj as? [[String: Any]] else { return } 35 | 36 | var accounts: [Account] = [] 37 | for dict in ary { 38 | accounts.append(Account(name: dict["fullname"] as? String, 39 | profileImageUrl: dict["profileImageURL"] as? String, 40 | userId: dict["username"] as? String, 41 | username: dict["username"] as? String)) 42 | } 43 | 44 | completion(accounts) 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /MarinDeckExtension/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 | -------------------------------------------------------------------------------- /MarindeckTests/MarindeckTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MarindeckTests.swift 3 | // MarindeckTests 4 | // 5 | // Created by a on 2022/01/29. 6 | // 7 | 8 | import XCTest 9 | @testable import Marindeck 10 | 11 | class MarindeckTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | // Any test you write for XCTest can be annotated as throws and async. 25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 27 | } 28 | 29 | func testJsSwiftBindingParse() throws { 30 | let imageViewerJsonString = """ 31 | { 32 | type: 'imageViewer', 33 | content: { 34 | value: [ 35 | { 36 | url: '', 37 | isSelected: true, 38 | position: {left: 1, top: 2, width: 3, height: 4} 39 | }, 40 | ], 41 | selectIndex: 1 42 | } 43 | } 44 | """ 45 | let fetchImageJsonString = """ 46 | { 47 | type: 'fetchImage', 48 | content: { 49 | url: 'https://...' 50 | // ... 51 | } 52 | } 53 | """ 54 | let decoder = JSONDecoder() 55 | guard let decoded = try? decoder.decode(General.self, from: fetchImageJsonString.data(using: .utf8)!) else { return } 56 | print(decoded.content) 57 | 58 | } 59 | 60 | func testPerformanceExample() throws { 61 | // This is an example of a performance test case. 62 | measure { 63 | // Put the code you want to measure the time of here. 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Marindeck/Asset/license.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | # License 13 | 14 | ## MIT License 15 | 16 | The following component(s) are licensed under the MIT License reproduced below 17 | 18 | * MarkdownView, Copyright (c) 2017 keitaoouchi 19 | * GRDB.swift, Copyright (C) 2015-2020 Gwendal Roué 20 | * AlamofireImage, Copyright (c) 2015-2021 Alamofire Software Foundation (http://alamofire.org/) 21 | * Highlightr, Copyright (c) 2016 Illanes, Juan Pablo 22 | * SPAlert, Copyright © 2021 Ivan Vorobei 23 | * SwiftGen, Copyright (c) 2020 SwiftGen 24 | * Optik, Copyright (c) 2017 Prolific Interactive. 25 | * [moduleRaid](https://github.com/pixeldesu/moduleRaid), Copyright (c) 2018 Andreas N. 26 | 27 | ``` 28 | Permission is hereby granted, free of charge, to any person obtaining a copy 29 | of this software and associated documentation files (the "Software"), to deal 30 | in the Software without restriction, including without limitation the rights 31 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 32 | copies of the Software, and to permit persons to whom the Software is 33 | furnished to do so, subject to the following conditions: 34 | 35 | The above copyright notice and this permission notice shall be included in all 36 | copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 39 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 40 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 41 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 42 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 43 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 44 | SOFTWARE. 45 | ``` 46 | 47 | ## MPL-2.0 License 48 | 49 | * Giphy 50 | 51 | ----------- 52 | 53 | ## Thanks!!!! 54 | 55 | -------------------------------------------------------------------------------- /Marindeck/Extensions/WKWebView+loadFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+loadFile.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/01/25. 6 | // 7 | 8 | import Foundation 9 | import WebKit 10 | 11 | extension WKWebView { 12 | // CSSファイルをロード 13 | func loadCSSFile(forResource: String, ofType: String = "css") { 14 | guard let mtPath = Bundle.main.path(forResource: forResource, ofType: ofType) else { 15 | print("failed load style.css") 16 | return 17 | } 18 | let mtFile = FileHandle(forReadingAtPath: mtPath)! 19 | let mtContentData = mtFile.readDataToEndOfFile() 20 | let css = String(data: mtContentData, encoding: .utf8)! 21 | mtFile.closeFile() 22 | var deletecomment = css.replacingOccurrences(of: "[\\s\\t]*/\\*/?(\\n|[^/]|[^*]/)*\\*/", with: "") 23 | deletecomment = deletecomment.replacingOccurrences(of: "\"", with: "\\\"") 24 | deletecomment = deletecomment.replacingOccurrences(of: "\n", with: "\\\n") 25 | let script = """ 26 | const h = document.documentElement; 27 | const s = document.createElement('style'); 28 | s.insertAdjacentHTML('beforeend', "\(deletecomment)"); 29 | h.insertAdjacentElement('beforeend', s) 30 | """ 31 | self.evaluateJavaScript(script) { _, error in 32 | print("stylecss : ", error ?? "成功") 33 | } 34 | } 35 | 36 | // JSファイルをロード 37 | func loadJsFile(forResource: String, ofType: String = "js") { 38 | guard let mtPath = Bundle.main.path(forResource: forResource, ofType: ofType) else { 39 | print("ERROR") 40 | return 41 | } 42 | let mtFile = FileHandle(forReadingAtPath: mtPath)! 43 | let mtContentData = mtFile.readDataToEndOfFile() 44 | let mtContentString = String(data: mtContentData, encoding: .utf8)! 45 | mtFile.closeFile() 46 | 47 | let mtScript = mtContentString 48 | self.evaluateJavaScript(mtScript) { _, error in 49 | print("webViewLog : ", error ?? "成功") 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | window.jazzy = {'docset': false} 6 | if (typeof window.dash != 'undefined') { 7 | document.documentElement.className += ' dash' 8 | window.jazzy.docset = true 9 | } 10 | if (navigator.userAgent.match(/xcode/i)) { 11 | document.documentElement.className += ' xcode' 12 | window.jazzy.docset = true 13 | } 14 | 15 | function toggleItem($link, $content) { 16 | var animationDuration = 300; 17 | $link.toggleClass('token-open'); 18 | $content.slideToggle(animationDuration); 19 | } 20 | 21 | function itemLinkToContent($link) { 22 | return $link.parent().parent().next(); 23 | } 24 | 25 | // On doc load + hash-change, open any targetted item 26 | function openCurrentItemIfClosed() { 27 | if (window.jazzy.docset) { 28 | return; 29 | } 30 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 31 | $content = itemLinkToContent($link); 32 | if ($content.is(':hidden')) { 33 | toggleItem($link, $content); 34 | } 35 | } 36 | 37 | $(openCurrentItemIfClosed); 38 | $(window).on('hashchange', openCurrentItemIfClosed); 39 | 40 | // On item link ('token') click, toggle its discussion 41 | $('.token').on('click', function(event) { 42 | if (window.jazzy.docset) { 43 | return; 44 | } 45 | var $link = $(this); 46 | toggleItem($link, itemLinkToContent($link)); 47 | 48 | // Keeps the document from jumping to the hash. 49 | var href = $link.attr('href'); 50 | if (history.pushState) { 51 | history.pushState({}, '', href); 52 | } else { 53 | location.hash = href; 54 | } 55 | event.preventDefault(); 56 | }); 57 | 58 | // Clicks on links to the current, closed, item need to open the item 59 | $("a:not('.token')").on('click', function() { 60 | if (location == this.href) { 61 | openCurrentItemIfClosed(); 62 | } 63 | }); 64 | 65 | // KaTeX rendering 66 | if ("katex" in window) { 67 | $($('.math').each( (_, element) => { 68 | katex.render(element.textContent, element, { 69 | displayMode: $(element).hasClass('m-block'), 70 | throwOnError: false, 71 | trust: true 72 | }); 73 | })) 74 | } 75 | -------------------------------------------------------------------------------- /Marindeck/Extensions/UILabel+Padding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UILabel+Padding.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/11/11. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UILabel { 11 | private struct AssociatedKeys { 12 | static var padding = UIEdgeInsets() 13 | } 14 | 15 | public var padding: UIEdgeInsets? { 16 | get { 17 | return objc_getAssociatedObject(self, &AssociatedKeys.padding) as? UIEdgeInsets 18 | } 19 | set { 20 | if let newValue = newValue { 21 | objc_setAssociatedObject(self, 22 | &AssociatedKeys.padding, 23 | newValue as UIEdgeInsets?, 24 | objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 25 | } 26 | } 27 | } 28 | 29 | override open func draw(_ rect: CGRect) { 30 | if let insets = padding { 31 | self.drawText(in: rect.inset(by: insets)) 32 | } else { 33 | self.drawText(in: rect) 34 | } 35 | } 36 | 37 | override open var intrinsicContentSize: CGSize { 38 | guard let text = self.text else { 39 | return super.intrinsicContentSize 40 | } 41 | 42 | var contentSize = super.intrinsicContentSize 43 | var textWidth: CGFloat = frame.size.width 44 | var insetsHeight: CGFloat = 0.0 45 | var insetsWidth: CGFloat = 0.0 46 | 47 | if let insets = padding { 48 | insetsWidth += insets.left + insets.right 49 | insetsHeight += insets.top + insets.bottom 50 | textWidth -= insetsWidth 51 | } 52 | 53 | let newSize = text.boundingRect(with: CGSize(width: textWidth, height: CGFloat.greatestFiniteMagnitude), 54 | options: NSStringDrawingOptions.usesLineFragmentOrigin, 55 | attributes: [NSAttributedString.Key.font: self.font!], context: nil) 56 | 57 | contentSize.height = ceil(newSize.size.height) + insetsHeight 58 | contentSize.width = ceil(newSize.size.width) + insetsWidth 59 | 60 | return contentSize 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs/docsets/Marindeck.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | window.jazzy = {'docset': false} 6 | if (typeof window.dash != 'undefined') { 7 | document.documentElement.className += ' dash' 8 | window.jazzy.docset = true 9 | } 10 | if (navigator.userAgent.match(/xcode/i)) { 11 | document.documentElement.className += ' xcode' 12 | window.jazzy.docset = true 13 | } 14 | 15 | function toggleItem($link, $content) { 16 | var animationDuration = 300; 17 | $link.toggleClass('token-open'); 18 | $content.slideToggle(animationDuration); 19 | } 20 | 21 | function itemLinkToContent($link) { 22 | return $link.parent().parent().next(); 23 | } 24 | 25 | // On doc load + hash-change, open any targetted item 26 | function openCurrentItemIfClosed() { 27 | if (window.jazzy.docset) { 28 | return; 29 | } 30 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 31 | $content = itemLinkToContent($link); 32 | if ($content.is(':hidden')) { 33 | toggleItem($link, $content); 34 | } 35 | } 36 | 37 | $(openCurrentItemIfClosed); 38 | $(window).on('hashchange', openCurrentItemIfClosed); 39 | 40 | // On item link ('token') click, toggle its discussion 41 | $('.token').on('click', function(event) { 42 | if (window.jazzy.docset) { 43 | return; 44 | } 45 | var $link = $(this); 46 | toggleItem($link, itemLinkToContent($link)); 47 | 48 | // Keeps the document from jumping to the hash. 49 | var href = $link.attr('href'); 50 | if (history.pushState) { 51 | history.pushState({}, '', href); 52 | } else { 53 | location.hash = href; 54 | } 55 | event.preventDefault(); 56 | }); 57 | 58 | // Clicks on links to the current, closed, item need to open the item 59 | $("a:not('.token')").on('click', function() { 60 | if (location == this.href) { 61 | openCurrentItemIfClosed(); 62 | } 63 | }); 64 | 65 | // KaTeX rendering 66 | if ("katex" in window) { 67 | $($('.math').each( (_, element) => { 68 | katex.render(element.textContent, element, { 69 | displayMode: $(element).hasClass('m-block'), 70 | throwOnError: false, 71 | trust: true 72 | }); 73 | })) 74 | } 75 | -------------------------------------------------------------------------------- /README-ja.md: -------------------------------------------------------------------------------- 1 | # MarinDeck for iOS 2 | 3 | [![Twitter Follow](https://img.shields.io/twitter/follow/vitomcharm?style=flat-square)](https://twitter.com/intent/follow?screen_name=vitomcharm) 4 | [![Discord](https://img.shields.io/badge/Discord-join-blue)](https://discord.gg/JKsqaxcnCW) 5 | [![Official](https://img.shields.io/badge/Official%20Site-Visit-blue)](https://hisubway.online/marindeck) 6 | ![version](https://img.shields.io/badge/version-Alpha-red) 7 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FRiniaOkyama%2FMarinDeck4iOS.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FRiniaOkyama%2FMarinDeck4iOS?ref=badge_shield) 8 | [![Build Status](https://app.bitrise.io/app/731d5ad5a90fa3b9/status.svg?token=pwBzWcUl57XRSwEovqqIPA&branch=master)](https://app.bitrise.io/app/731d5ad5a90fa3b9) 9 | 10 | 11 | ![MarinDeckheader](https://user-images.githubusercontent.com/66313777/123541317-d1c2fc80-d77e-11eb-97f9-7733bfb23d44.png) 12 | 13 | ![MarinDeck4iOSSct](https://user-images.githubusercontent.com/54408846/207113913-55dc4285-2107-41cb-81c5-1c19c4373578.png) 14 | 15 | 16 | [日本語 / [English](README.md)] 17 | 18 | ## 必要条件 19 | 20 | * iOS14.1+ 21 | * Xcode14 22 | 23 | ## セットアップ 24 | 25 | ``` 26 | $ brew install mint 27 | $ mint bootstrap 28 | 29 | $ mint run xcodegen generate 30 | $ xed . 31 | ``` 32 | 33 | 34 | 35 | ## 貢献 36 | 37 | 貢献お待ちしております✨ 38 | - [New issue](https://github.com/RiniaOkyama/MarinDeck4iOS/issues/new) 39 | - [New Pull Request](https://github.com/RiniaOkyama/MarinDeck4iOS/compare) 40 | 41 | 42 | ## Support 43 | 44 | iOSアプリについてサポートが必要な場合や質問がある場合は Discordチャンネル([#苹果](https://discord.gg/JKsqaxcnCW))でサポートを受けることができます。バグを発見した場合は、Githubに新しいissueを作成するか、Discordチャンネル([#苹果](https://discord.gg/JKsqaxcnCW))までお願いします。 45 | 46 | このリポジトリはiOSアプリのみを扱っています。Android版とは別なので注意してください。 47 | 48 | ## LICENSE 49 | いつか 50 | 51 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FRiniaOkyama%2FMarinDeck4iOS.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FRiniaOkyama%2FMarinDeck4iOS?ref=badge_large) 52 | 53 | ## Stats 54 | 55 | ![Alt](https://repobeats.axiom.co/api/embed/8d044c1cd4748a131128ebc168fdf720962a5f95.svg "Repobeats analytics image") 56 | -------------------------------------------------------------------------------- /docs/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | $(function(){ 6 | var $typeahead = $('[data-typeahead]'); 7 | var $form = $typeahead.parents('form'); 8 | var searchURL = $form.attr('action'); 9 | 10 | function displayTemplate(result) { 11 | return result.name; 12 | } 13 | 14 | function suggestionTemplate(result) { 15 | var t = '
'; 16 | t += '' + result.name + ''; 17 | if (result.parent_name) { 18 | t += '' + result.parent_name + ''; 19 | } 20 | t += '
'; 21 | return t; 22 | } 23 | 24 | $typeahead.one('focus', function() { 25 | $form.addClass('loading'); 26 | 27 | $.getJSON(searchURL).then(function(searchData) { 28 | const searchIndex = lunr(function() { 29 | this.ref('url'); 30 | this.field('name'); 31 | this.field('abstract'); 32 | for (const [url, doc] of Object.entries(searchData)) { 33 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 34 | } 35 | }); 36 | 37 | $typeahead.typeahead( 38 | { 39 | highlight: true, 40 | minLength: 3, 41 | autoselect: true 42 | }, 43 | { 44 | limit: 10, 45 | display: displayTemplate, 46 | templates: { suggestion: suggestionTemplate }, 47 | source: function(query, sync) { 48 | const lcSearch = query.toLowerCase(); 49 | const results = searchIndex.query(function(q) { 50 | q.term(lcSearch, { boost: 100 }); 51 | q.term(lcSearch, { 52 | boost: 10, 53 | wildcard: lunr.Query.wildcard.TRAILING 54 | }); 55 | }).map(function(result) { 56 | var doc = searchData[result.ref]; 57 | doc.url = result.ref; 58 | return doc; 59 | }); 60 | sync(results); 61 | } 62 | } 63 | ); 64 | $form.removeClass('loading'); 65 | $typeahead.trigger('focus'); 66 | }); 67 | }); 68 | 69 | var baseURL = searchURL.slice(0, -"search.json".length); 70 | 71 | $typeahead.on('typeahead:select', function(e, result) { 72 | window.location = baseURL + result.url; 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /docs/docsets/Marindeck.docset/Contents/Resources/Documents/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | $(function(){ 6 | var $typeahead = $('[data-typeahead]'); 7 | var $form = $typeahead.parents('form'); 8 | var searchURL = $form.attr('action'); 9 | 10 | function displayTemplate(result) { 11 | return result.name; 12 | } 13 | 14 | function suggestionTemplate(result) { 15 | var t = '
'; 16 | t += '' + result.name + ''; 17 | if (result.parent_name) { 18 | t += '' + result.parent_name + ''; 19 | } 20 | t += '
'; 21 | return t; 22 | } 23 | 24 | $typeahead.one('focus', function() { 25 | $form.addClass('loading'); 26 | 27 | $.getJSON(searchURL).then(function(searchData) { 28 | const searchIndex = lunr(function() { 29 | this.ref('url'); 30 | this.field('name'); 31 | this.field('abstract'); 32 | for (const [url, doc] of Object.entries(searchData)) { 33 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 34 | } 35 | }); 36 | 37 | $typeahead.typeahead( 38 | { 39 | highlight: true, 40 | minLength: 3, 41 | autoselect: true 42 | }, 43 | { 44 | limit: 10, 45 | display: displayTemplate, 46 | templates: { suggestion: suggestionTemplate }, 47 | source: function(query, sync) { 48 | const lcSearch = query.toLowerCase(); 49 | const results = searchIndex.query(function(q) { 50 | q.term(lcSearch, { boost: 100 }); 51 | q.term(lcSearch, { 52 | boost: 10, 53 | wildcard: lunr.Query.wildcard.TRAILING 54 | }); 55 | }).map(function(result) { 56 | var doc = searchData[result.ref]; 57 | doc.url = result.ref; 58 | return doc; 59 | }); 60 | sync(results); 61 | } 62 | } 63 | ); 64 | $form.removeClass('loading'); 65 | $typeahead.trigger('focus'); 66 | }); 67 | }); 68 | 69 | var baseURL = searchURL.slice(0, -"search.json".length); 70 | 71 | $typeahead.on('typeahead:select', function(e, result) { 72 | window.location = baseURL + result.url; 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /Marindeck/Extensions/ViewController+biometrics.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by a on 2022/01/26. 3 | // 4 | 5 | import Foundation 6 | import UIKit 7 | import LocalAuthentication 8 | 9 | extension ViewController { 10 | // 生体認証 11 | func checkBiometrics() { 12 | let isUseBiometrics = UserDefaults.standard.bool(forKey: UserDefaultsKey.isUseBiometrics) 13 | if isUseBiometrics { 14 | if canUseBiometrics() { 15 | isMainDeckViewLock = true 16 | mainDeckView.isHidden = true 17 | let context = LAContext() 18 | let reason = "ロックを解除" 19 | let backBlackView = UIView(frame: view.bounds) 20 | backBlackView.backgroundColor = .black 21 | view.addSubview(backBlackView) 22 | context.evaluatePolicy(.deviceOwnerAuthentication, 23 | localizedReason: reason) { (success, evaluateError) in 24 | if success { 25 | DispatchQueue.main.async { [unowned self] in 26 | self.isMainDeckViewLock = false 27 | self.mainDeckView.isHidden = false 28 | self.tweetFloatingBtn.isHidden = false 29 | UIView.animate(withDuration: 0.3, animations: { 30 | backBlackView.alpha = 0 31 | }, completion: { _ in 32 | backBlackView.removeFromSuperview() 33 | }) 34 | } 35 | } else { 36 | DispatchQueue.main.async { 37 | let errorLabel = UILabel(frame: backBlackView.bounds) 38 | errorLabel.textAlignment = .center 39 | errorLabel.text = "認証に失敗しました。" 40 | errorLabel.textColor = .white 41 | backBlackView.addSubview(errorLabel) 42 | } 43 | guard let error = evaluateError as NSError? else { 44 | print("Error") 45 | return 46 | } 47 | print("\(error.code): \(error.localizedDescription)") 48 | } 49 | } 50 | } else { 51 | // 生体認証をオンにしているが、許可されていない。 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Marindeck/View/Other/DraftViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DraftViewController.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/11/28. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct DraftView: View { 11 | @Environment(\.presentationMode) var presentationMode 12 | 13 | typealias selectedCompletion = (_ index: Int) -> Void 14 | let selected: selectedCompletion? 15 | let drafts: [Draft] 16 | 17 | init(selected: selectedCompletion? = nil, drafts: [Draft]) { 18 | self.selected = selected 19 | self.drafts = drafts 20 | } 21 | 22 | var body: some View { 23 | VStack { 24 | if drafts.isEmpty { 25 | Text("下書きがありません。\n\nネイティブのツイート画面から\n下書き保存ができます。") 26 | .multilineTextAlignment(.center) 27 | } else { 28 | List { 29 | ForEach(0 ..< drafts.count) { index in 30 | if #available(iOS 15.0, *) { 31 | Text(drafts[index].text) 32 | // 開発環境がiOS14.5なのでデバッグできない。 33 | // .swipeActions(edge: .trailing) { 34 | // Button(role: .destructive) { 35 | // print("delete action.") 36 | // } label: { 37 | // Image(systemName: "trash.fill") 38 | // } 39 | // } 40 | .onTapGesture { 41 | presentationMode.wrappedValue.dismiss() 42 | selected?(index) 43 | } 44 | } else { 45 | Text(drafts[index].text) 46 | .onTapGesture { 47 | presentationMode.wrappedValue.dismiss() 48 | selected?(index) 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | struct DraftView_Previews: PreviewProvider { 59 | static var previews: some View { 60 | DraftView(selected: nil, drafts: []) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Marindeck/View/Core/ViewController+ImagePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController+ImagePicker.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/05. 6 | // 7 | import UIKit 8 | import Loaf 9 | 10 | extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { 11 | 12 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { 13 | print("\(info)") 14 | if let image = info[.originalImage] as? UIImage { 15 | guard let url = info[.imageURL] as? URL else { return } 16 | let pex = url.pathExtension 17 | 18 | var base64imgString = "" 19 | if pex == "png" { 20 | base64imgString = image.pngData()?.base64EncodedString(options: []) ?? "" 21 | } else if pex == "jpg" || pex == "jpeg" { 22 | base64imgString = image.jpegData(compressionQuality: 0.7)?.base64EncodedString(options: []) ?? "" 23 | } 24 | 25 | if base64imgString == "" { 26 | // TODO: L10n 27 | Loaf("画像が読み込めませんでした。", state: .error, location: .top, sender: self).show() 28 | return 29 | } 30 | 31 | print(pex) 32 | print(url.lastPathComponent) 33 | print(base64imgString.count) 34 | UIPasteboard.general.string = base64imgString 35 | webView.evaluateJavaScript("window.MarinDeckInputs.addTweetImage(\"data:image/\(pex);base64,\(base64imgString)\", \"image/\(pex)\", \"\(url.lastPathComponent)\")") { _, error in 36 | print("photoselected : ", error ?? "成功") 37 | } 38 | dismiss(animated: true, completion: nil) 39 | } 40 | } 41 | 42 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) { 43 | 44 | if let image = info[UIImagePickerController.InfoKey.originalImage.rawValue] as? UIImage { 45 | guard let base64img = image.pngData()?.base64EncodedString(options: []) else { 46 | return 47 | } 48 | webView.evaluateJavaScript("window.MarinDeckInputs.addTweetImage(\"data:image/png;base64,\(base64img)\", \"image/png\", \"test.png\")") { _, error in 49 | print("photoselected : ", error ?? "成功") 50 | } 51 | } else { 52 | print("Error") 53 | } 54 | 55 | dismiss(animated: true, completion: nil) 56 | } 57 | 58 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 59 | dismiss(animated: true, completion: nil) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Binding/src/inputs.ts: -------------------------------------------------------------------------------- 1 | import { jQuery } from "../MarinDeckObject/src/objects/jQuery" 2 | 3 | 4 | export interface MarinDeckInputsInterface { 5 | addTweetImage(base64: string, type: string, name: string): void 6 | touchPointTweetLike(x: number, y: number): void 7 | postTweet(text: string, reply_to_id, key): void 8 | updateSchedule(Y: number, M: number, D: number, h: number, m: number): void 9 | } 10 | 11 | /** tweet */ 12 | // const getClient = (key = null) => window.TD.controller.clients.getClient(key || window.TD.storage.accountController.getDefault().privateState.key) || window.TD.controller.clients.getPreferredClient('twitter') 13 | 14 | // const getAllAccounts = () => { 15 | // const accounts = [] 16 | // window.TD.storage.accountController.getAll().forEach((x) => { 17 | // if (x.managed) accounts.push({ 18 | // key: x.privateState.key, 19 | // name: x.state.name, 20 | // userId: x.state.userId, 21 | // username: x.state.username, 22 | // profileImageURL: x.state.profileImageURL, 23 | // }) 24 | // }) 25 | // return accounts 26 | // } 27 | 28 | // window.TD.storage.accountController.getAll() 29 | // .filter(({managed}) => managed) 30 | // .map(({state: {name: fullname, username, userId, profileImageURL}}) => ({fullname, username, userId, profileImageURL})) 31 | 32 | 33 | export class MarinDeckInputs implements MarinDeckInputsInterface { 34 | 35 | addTweetImage(base64: string, type: string, name: string) { 36 | var bin = atob(base64.replace(/^.*,/, '')); // decode base64 37 | var buffer = new Uint8Array(bin.length); // to binary data 38 | for (var i = 0; i < bin.length; i++) { 39 | buffer[i] = bin.charCodeAt(i); 40 | } 41 | const imgFile = new File([buffer.buffer], name, {type: type}); 42 | 43 | jQuery(document).trigger("uiFilesAdded", {files: [imgFile]}); 44 | } 45 | 46 | touchPointTweetLike(x: number, y: number) { 47 | const element = document.elementFromPoint(x, y)?.closest(".tweet")?.getElementsByClassName("tweet-action")[2] 48 | if (element instanceof HTMLElement) { 49 | element.click() 50 | } 51 | } 52 | 53 | postTweet(text: string) { 54 | window.MD.TwitterAPI.update({ 55 | status: text, 56 | from: window.TD.storage.accountController.getDefault().getUsername() 57 | }) 58 | // getClient(key).update(text, reply_to_id, null, null, null, resolve, reject) 59 | } 60 | 61 | updateSchedule(Y: number, M: number, D: number, h: number, m: number): void { 62 | jQuery('.js-docked-compose').parent().trigger('uiComposeScheduleDate', {date: new Date(Y, M-1, D, h, m, 0, 0)}); 63 | } 64 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MarinDeck for iOS 2 | 3 | [![Twitter Follow](https://img.shields.io/twitter/follow/vitomcharm?style=flat-square)](https://twitter.com/intent/follow?screen_name=vitomcharm) 4 | [![Discord](https://img.shields.io/badge/Discord-join-blue)](https://discord.gg/JKsqaxcnCW) 5 | [![Official](https://img.shields.io/badge/Official%20Site-Visit-blue)](https://hisubway.online/marindeck) 6 | ![version](https://img.shields.io/badge/version-Alpha-red) 7 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FRiniaOkyama%2FMarinDeck4iOS.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FRiniaOkyama%2FMarinDeck4iOS?ref=badge_shield) 8 | [![Build Status](https://app.bitrise.io/app/731d5ad5a90fa3b9/status.svg?token=pwBzWcUl57XRSwEovqqIPA&branch=master)](https://app.bitrise.io/app/731d5ad5a90fa3b9) 9 | 10 | 11 | ![MarinDeckheader](https://user-images.githubusercontent.com/66313777/123541317-d1c2fc80-d77e-11eb-97f9-7733bfb23d44.png) 12 | 13 | ![MarinDeck4iOSSct](https://user-images.githubusercontent.com/54408846/207113913-55dc4285-2107-41cb-81c5-1c19c4373578.png) 14 | 15 | Check out [hisubway.online/marindeck](https://hisubway.online/marindeck/) and follow us on [twitter.com/HiSubway](https://twitter.com/HiSubway) and [twitter.com/vitomcharm](https://twitter.com/vitomcharm) 16 | 17 | [[日本語](README-ja.md) / English] 18 | 19 | ## Requirements 20 | 21 | * iOS14.1+ 22 | * Xcode14 23 | 24 | ## Installation 25 | 26 | ``` 27 | $ brew install mint 28 | $ mint bootstrap 29 | 30 | $ mint run xcodegen generate 31 | $ xed . 32 | ``` 33 | 34 | 35 | 36 | ## Contribute 37 | 38 | If you want to contribute to MarinDeck4iOS, you are very welcome 39 | - [New issue](https://github.com/RiniaOkyama/MarinDeck4iOS/issues/new) 40 | - [New Pull Request](https://github.com/RiniaOkyama/MarinDeck4iOS/compare) 41 | 42 | 43 | ## Support 44 | 45 | 46 | If you need help or have questions about the iOS app, you can get support on our Discord channel ([#苹果](https://discord.gg/JKsqaxcnCW)). If you find a bug, please create a new issue on Github or send it to our Discord channel ([#苹果](https://discord.gg/JKsqaxcnCW)). 47 | 48 | Please note that this repository only deals with iOS apps; it is separate from the Android version. 49 | 50 | ## LICENSE 51 | someday... 52 | 53 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FRiniaOkyama%2FMarinDeck4iOS.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FRiniaOkyama%2FMarinDeck4iOS?ref=badge_large) 54 | 55 | ## Stats 56 | 57 | ![Alt](https://repobeats.axiom.co/api/embed/8d044c1cd4748a131128ebc168fdf720962a5f95.svg "Repobeats analytics image") 58 | -------------------------------------------------------------------------------- /Marindeck/Application/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Marindecker 4 | // 5 | // Created by Rinia on 2021/01/12. 6 | // 7 | 8 | import UIKit 9 | import class SwiftUI.UIHostingController 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func scene(_ scene: UIScene, 16 | willConnectTo session: UISceneSession, 17 | options connectionOptions: UIScene.ConnectionOptions) { 18 | 19 | guard let scene = (scene as? UIWindowScene) else { return } 20 | 21 | window = UIWindow(windowScene: scene) 22 | 23 | if UserDefaults.standard.bool(forKey: .appDebugMode) { 24 | let view = AppDebugView(vc: nil) 25 | let vc = UIHostingController(rootView: view) 26 | window?.rootViewController = vc 27 | (window?.rootViewController as! UIHostingController).rootView.vc = window?.rootViewController 28 | } else { 29 | let view = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as! ViewController 30 | window?.rootViewController = view 31 | } 32 | 33 | window?.makeKeyAndVisible() 34 | } 35 | 36 | func sceneDidDisconnect(_ scene: UIScene) { 37 | // Called as the scene is being released by the system. 38 | // This occurs shortly after the scene enters the background, or when its session is discarded. 39 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 40 | // The scene may re-connect later, as its session was not necessarily discarded 41 | // (see `application:didDiscardSceneSessions` instead). 42 | } 43 | 44 | func sceneDidBecomeActive(_ scene: UIScene) { 45 | // Called when the scene has moved from an inactive state to an active state. 46 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 47 | } 48 | 49 | func sceneWillResignActive(_ scene: UIScene) { 50 | // Called when the scene will move from an active state to an inactive state. 51 | // This may occur due to temporary interruptions (ex. an incoming phone call). 52 | } 53 | 54 | func sceneWillEnterForeground(_ scene: UIScene) { 55 | // Called as the scene transitions from the background to the foreground. 56 | // Use this method to undo the changes made on entering the background. 57 | } 58 | 59 | func sceneDidEnterBackground(_ scene: UIScene) { 60 | // Called as the scene transitions from the foreground to the background. 61 | // Use this method to save data, release shared resources, and store enough scene-specific state information 62 | // to restore the scene back to its current state. 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /Marindeck/View/Core/ViewController+WKNavigationDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController+WKNavigationDelegate.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/05. 6 | // 7 | import WebKit 8 | import SafariServices 9 | 10 | extension ViewController: WKNavigationDelegate { 11 | // MARK: - 読み込み設定(リクエスト前) 12 | func webView(_ webView: WKWebView, 13 | decidePolicyFor navigationAction: WKNavigationAction, 14 | decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 15 | let url = navigationAction.request.url 16 | guard let host = url?.host else { 17 | decisionHandler(.cancel) 18 | return 19 | } 20 | 21 | if ((url?.absoluteString.contains("twitter.com/i/cards")) ?? false) || 22 | (url?.absoluteString.contains("youtube.com/embed") ?? false) { 23 | decisionHandler(.cancel) 24 | return 25 | } 26 | 27 | if host == "tweetdeck.twitter.com" { 28 | decisionHandler(.allow) 29 | // }else if host.hasPrefix("t.co") { 30 | // decisionHandler(.cancel) 31 | } else if host == "mobile.twitter.com" { 32 | let vc = LoginViewController() 33 | vc.delegate = self 34 | let nvc = UINavigationController(rootViewController: vc) 35 | present(nvc, animated: true, completion: nil) 36 | decisionHandler(.cancel) 37 | } else { 38 | let safariVC = SFSafariViewController(url: url!) 39 | present(safariVC, animated: true, completion: nil) 40 | 41 | decisionHandler(.cancel) 42 | } 43 | } 44 | 45 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 46 | webView.loadJsFile(forResource: "moduleraid") 47 | // loadJsFile(forResource: "marindeck-css") 48 | // webView.loadJsFile(forResource: "msecdeck.bundle") 49 | webView.loadJsFile(forResource: "marindeck") 50 | webView.loadCSSFile(forResource: "marindeck") 51 | 52 | let cjss = try! dbQueue.read { db in 53 | try CustomJS.fetchAll(db) 54 | } 55 | .filter { $0.isLoad } 56 | .sorted(by: { $0.loadIndex < $1.loadIndex }) 57 | for item in cjss { 58 | webView.inject(js: item.js) 59 | } 60 | 61 | let csss = try! dbQueue.read { db in 62 | try CustomCSS.fetchAll(db) 63 | } 64 | .filter { $0.isLoad } 65 | .sorted(by: { $0.loadIndex < $1.loadIndex }) 66 | for item in csss { 67 | webView.inject(css: item.css) 68 | } 69 | 70 | let theme = fetchTheme() 71 | webView.inject(js: theme.js) 72 | 73 | let rjs = RemoteJS.shared 74 | rjs.update { [weak self] () in 75 | self?.webView.inject(js: rjs.getJs(id: .navigationTab) ?? "") 76 | } 77 | 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /Marindeck/View/Menu/MenuItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuItemView.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/05/06. 6 | // 7 | 8 | import UIKit 9 | 10 | @IBDesignable 11 | final class MenuItemView: UIView { 12 | 13 | @IBInspectable var iconImage: UIImage? { 14 | didSet { 15 | iconView.image = iconImage 16 | } 17 | // get { return iconView.image } 18 | // set { iconView.image = newValue } 19 | } 20 | 21 | @IBInspectable var title: String = "" { 22 | didSet { 23 | titleLabel.text = title 24 | } 25 | } 26 | 27 | public lazy var iconView: UIImageView = { 28 | let imgView = UIImageView() 29 | imgView.frame.size.height = self.frame.height 30 | imgView.frame.size.width = 22 31 | imgView.frame.origin.x = 12 32 | imgView.contentMode = .scaleAspectFit 33 | imgView.image = iconImage 34 | imgView.tintColor = .labelColor 35 | return imgView 36 | }() 37 | 38 | public lazy var titleLabel: UILabel = { 39 | let label = UILabel() 40 | label.frame.size.height = self.frame.height 41 | label.frame.size.width = self.frame.width - (36 + 12) 42 | label.frame.origin.x = 36 + 12 43 | label.textColor = .labelColor 44 | 45 | return label 46 | }() 47 | 48 | override init(frame: CGRect) { 49 | super.init(frame: frame) 50 | setupViews() 51 | } 52 | 53 | override func awakeFromNib() { 54 | super.awakeFromNib() 55 | setupViews() 56 | } 57 | 58 | override func prepareForInterfaceBuilder() { 59 | super.prepareForInterfaceBuilder() 60 | setupViews() 61 | // setNeedsDisplay() 62 | } 63 | 64 | required init?(coder aDecoder: NSCoder) { 65 | super.init(coder: aDecoder) 66 | } 67 | 68 | func setupViews() { 69 | self.addSubview(iconView) 70 | self.addSubview(titleLabel) 71 | 72 | self.layer.cornerRadius = 12 73 | self.backgroundColor = .clear 74 | } 75 | 76 | public func setTapEvent(action: Selector, target: Any) { 77 | let tapGestureRecognizer = UITapGestureRecognizer(target: target, action: action) 78 | self.addGestureRecognizer(tapGestureRecognizer) 79 | } 80 | 81 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 82 | self.backgroundColor = UIColor.black.withAlphaComponent(0.3) 83 | } 84 | 85 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 86 | UIView.animate(withDuration: 0.2, animations: { 87 | self.backgroundColor = .clear 88 | }) 89 | } 90 | 91 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 92 | UIView.animate(withDuration: 0.2, animations: { 93 | self.backgroundColor = .clear 94 | }) 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /Marindeck/View/Core/ViewController+MenuView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController+MenuView.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/11. 6 | // 7 | 8 | import Foundation 9 | import class UIKit.UIView 10 | import class UIKit.UIColor 11 | 12 | protocol MenuAction { 13 | func closeMenu() 14 | func openMenu() 15 | } 16 | 17 | extension ViewController: MenuDelegate { 18 | func reload() { 19 | webView.reload() 20 | closeMenu() 21 | } 22 | 23 | func openProfile() { 24 | closeMenu() 25 | webView.evaluateJavaScript("document.querySelector(\"body > div.application.js-app.is-condensed > header > div > div.js-account-summary > a > div\").click()") { _, error in 26 | print("openProfile : ", error ?? "成功") 27 | } 28 | } 29 | 30 | func openColumnAdd() { 31 | closeMenu() 32 | webView.evaluateJavaScript("document.querySelector(\".js-header-add-column\").click()") { _, error in 33 | print(#function, error ?? "成功") 34 | } 35 | } 36 | 37 | func openTdSettings() { 38 | closeMenu() 39 | td.actions.openTDSettings() 40 | } 41 | } 42 | 43 | extension ViewController: MenuAction { 44 | // Menuを閉じる 45 | func closeMenu() { 46 | isMenuOpen = false 47 | menuView.translatesAutoresizingMaskIntoConstraints = false 48 | mainDeckView.translatesAutoresizingMaskIntoConstraints = false 49 | mainDeckBlurView.isUserInteractionEnabled = false 50 | UIView.animate(withDuration: 0.2, animations: { 51 | self.mainDeckBlurView.backgroundColor = .none 52 | 53 | self.mainDeckBlurView.frame.origin.x = 0 54 | self.mainDeckView.frame.origin.x = 0 55 | self.bottomBackView.frame.origin.x = 0 56 | self.topBackView.frame.origin.x = 0 57 | 58 | self.menuView.frame.origin.x = -self.menuView.frame.width 59 | }) 60 | } 61 | 62 | // Menuを開く 63 | func openMenu() { 64 | isMenuOpen = true 65 | menuView.translatesAutoresizingMaskIntoConstraints = true 66 | mainDeckView.translatesAutoresizingMaskIntoConstraints = true 67 | mainDeckBlurView.isUserInteractionEnabled = true 68 | 69 | td.account.getAccount { [weak self] account in 70 | self?.menuVC.setUserIcon(url: account.profileImageUrl ?? "") 71 | self?.menuVC.setUserNameID(name: account.name ?? "", id: account.userId ?? "") 72 | } 73 | 74 | UIView.animate(withDuration: 0.3, animations: { 75 | self.menuView.frame.origin.x = 0 76 | self.mainDeckBlurView.frame.origin.x = self.menuView.frame.width 77 | self.mainDeckView.frame.origin.x = self.menuView.frame.width 78 | 79 | UIView.animate(withDuration: 0.2, animations: { 80 | self.mainDeckBlurView.backgroundColor = UIColor.black.withAlphaComponent(0.5) 81 | }) 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /project.yml: -------------------------------------------------------------------------------- 1 | name: Marindeck 2 | 3 | packages: 4 | GRDB: 5 | url: https://github.com/groue/GRDB.swift 6 | exactVersion: 5.12.0 7 | MarkdownView: 8 | url: https://github.com/hirossan4049/MarkdownView 9 | revision: 753be391e6d3640161ca21f49a57c306aaebaf3a 10 | GiphyUISDK: 11 | url: https://github.com/Giphy/giphy-ios-sdk 12 | exactVersion: 2.1.16 13 | Loaf: 14 | url: https://github.com/schmidyy/Loaf 15 | exactVersion: 0.6.0 16 | SwiftyStoreKit: 17 | url: https://github.com/bizz84/SwiftyStoreKit 18 | exactVersion: 0.16.4 19 | 20 | 21 | 22 | options: 23 | #bundleIdPrefix: marindeck 24 | postGenCommand: rbenv exec pod install 25 | developmentLanguage: en 26 | 27 | settingGroups: 28 | MarindeckBaseSettings: 29 | SWIFT_OBJC_BRIDGING_HEADER: Marindeck/Marindeck-Bridging-Header.h 30 | 31 | settings: 32 | base: 33 | DEVELOPMENT_TEAM: 726VR75V6L 34 | MARKETING_VERSION: 0.2.2 35 | CURRENT_PROJECT_VERSION: 0.2.2 36 | config: 37 | debug: 38 | DEBUG_INFORMATION_FORMAT: "dwarf-with-dsym" 39 | 40 | # schemes: 41 | # Debug: 42 | # build: 43 | # targets: 44 | # MarinDeck: all 45 | # MarinDeckTests: [test] 46 | # 47 | # Release: 48 | # build: 49 | # targets: 50 | # MarinDeck: all 51 | # MarinDeckTests: [test] 52 | # 53 | # run: 54 | # config: Release 55 | # test: 56 | # config: Release 57 | # profile: 58 | # config: Release 59 | # analyze: 60 | # config: Release 61 | # archive: 62 | # config: Release 63 | 64 | 65 | 66 | targets: 67 | Marindeck: 68 | type: application 69 | platform: iOS 70 | deploymentTarget: "15" 71 | sources: Marindeck 72 | scheme: {} 73 | settings: 74 | base: 75 | DEVELOPMENT_TEAM: 726VR75V6L 76 | PRODUCT_BUNDLE_IDENTIFIER: marindeck 77 | DISPLAY_APPNAME: MarinDeck 78 | dependencies: 79 | - package: GRDB 80 | - package: MarkdownView 81 | - package: GiphyUISDK 82 | - package: Loaf 83 | - package: SwiftyStoreKit 84 | preBuildScripts: 85 | - path: ./scripts/swiftlint.sh 86 | name: Run SwiftLint 87 | - path: ./scripts/vite.sh 88 | name: Build Vite 89 | 90 | Marindeck-dev: 91 | type: application 92 | platform: iOS 93 | deploymentTarget: "15" 94 | sources: Marindeck 95 | scheme: {} 96 | settings: 97 | base: 98 | DEVELOPMENT_TEAM: 726VR75V6L 99 | PRODUCT_BUNDLE_IDENTIFIER: marindeck.dev 100 | DISPLAY_APPNAME: MD-dev 101 | dependencies: 102 | - package: GRDB 103 | - package: MarkdownView 104 | - package: GiphyUISDK 105 | - package: Loaf 106 | - package: SwiftyStoreKit 107 | -------------------------------------------------------------------------------- /Marindeck/View/Settings/Icon/IconSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IconSettings.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/12/31. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct Icon: Hashable { 11 | let iconName: String 12 | let iconTitle: String 13 | let iconFlag: String? 14 | } 15 | 16 | struct IconSettingsListItem: View { 17 | let icon: Icon 18 | let isSelected: Bool 19 | 20 | var body: some View { 21 | HStack { 22 | Image(icon.iconName) 23 | .resizable() 24 | .frame(width: 48, height: 48) 25 | .cornerRadius(8) 26 | Text(icon.iconTitle) 27 | .frame(maxWidth: .infinity, alignment: .leading) 28 | .foregroundColor(Color(UIColor.labelColor)) 29 | if isSelected { 30 | Image(systemName: "checkmark.circle.fill") 31 | .frame(width: 12.0, height: 12.0) 32 | .foregroundColor(Color(UIColor.labelColor)) 33 | } 34 | } 35 | .frame(height: 54) 36 | } 37 | } 38 | 39 | struct IconSettingsView: View { 40 | @Environment(\.presentationMode) var presentationMode 41 | 42 | init() { 43 | UITableView.appearance().backgroundColor = .secondaryBackgroundColor 44 | } 45 | 46 | // @State private var icons = ["DefaultIcon": "白","BlackIcon": "黒","RainbowIcon": "ゲーミング"] 47 | let icons: [Icon] = [ 48 | .init(iconName: "DefaultIcon", iconTitle: "白", iconFlag: nil), 49 | .init(iconName: "BlackIcon", iconTitle: "黒", iconFlag: "BlackIcon"), 50 | .init(iconName: "RainbowIcon", iconTitle: "ゲーミング", iconFlag: "RainbowIcon") 51 | ] 52 | 53 | @State private var alternateIconName = UIApplication.shared.alternateIconName 54 | 55 | var body: some View { 56 | ZStack { 57 | // TODO: NavigatonViewのタイトルを設定しても表示されない 58 | // NavigationView { 59 | List { 60 | ForEach(icons, id: \.self) { icon in 61 | IconSettingsListItem(icon: icon, isSelected: alternateIconName == icon.iconFlag ) 62 | .contentShape(Rectangle()) 63 | .onTapGesture { 64 | UIApplication.shared.setAlternateIconName(icon.iconFlag, completionHandler: nil) 65 | updateIconName() 66 | } 67 | .listRowBackground(Color(UIColor.secondaryBackgroundColor)) 68 | } 69 | } 70 | .onAppear { 71 | updateIconName() 72 | } 73 | // } 74 | // .navigationBarTitle(Text("Users")) 75 | } 76 | } 77 | 78 | func updateIconName() { 79 | alternateIconName = UIApplication.shared.alternateIconName 80 | } 81 | } 82 | 83 | struct IconSettingsView_Previews: PreviewProvider { 84 | static var previews: some View { 85 | IconSettingsView() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Marindeck/Application/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Marindecker 4 | // 5 | // Created by Rinia on 2021/01/12. 6 | // 7 | 8 | import UIKit 9 | import Keys 10 | import GiphyUISDK 11 | import SwiftyStoreKit 12 | 13 | @main 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | func application(_ application: UIApplication, 17 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | 20 | // let keys = MarindeckKeys() 21 | // DeployGateSDK 22 | // .sharedInstance() 23 | // .launchApplication(withAuthor: keys.deploygateUsername, key: keys.deploygateSdkApiKey) 24 | UserDefaults.standard.register(defaults: [.marginSafeArea: true, 25 | .appDebugMode: false]) 26 | 27 | Database.shared.setup() 28 | Giphy.configure(apiKey: MarindeckKeys().giphyApiKey) 29 | UIApplication.shared.isIdleTimerDisabled = UserDefaults.standard.bool(forKey: .noSleep) 30 | 31 | #if DEBUG 32 | _ = Test() 33 | #endif 34 | 35 | SwiftyStoreKit.completeTransactions(atomically: true) { purchases in 36 | for purchase in purchases { 37 | switch purchase.transaction.transactionState { 38 | case .purchased, .restored: 39 | if purchase.needsFinishTransaction { 40 | // Deliver content from server, then: 41 | SwiftyStoreKit.finishTransaction(purchase.transaction) 42 | } 43 | // Unlock content 44 | case .failed, .purchasing, .deferred: 45 | break // do nothing 46 | @unknown default: 47 | break 48 | } 49 | } 50 | } 51 | 52 | return true 53 | } 54 | 55 | // MARK: UISceneSession Lifecycle 56 | 57 | func application(_ application: UIApplication, 58 | configurationForConnecting connectingSceneSession: UISceneSession, 59 | options: UIScene.ConnectionOptions) -> UISceneConfiguration { 60 | // Called when a new scene session is being created. 61 | // Use this method to select a configuration to create the new scene with. 62 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 63 | } 64 | 65 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 66 | // Called when the user discards a scene session. 67 | // If any sessions were discarded while the application was not running, 68 | // this will be called shortly after application:didFinishLaunchingWithOptions. 69 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Marindeck/View/Settings/Theme/ThemeDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThemeDetailViewController.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/05/09. 6 | // 7 | 8 | import UIKit 9 | 10 | class ThemeDetailViewController: UIViewController { 11 | @IBOutlet weak var iconView: UIImageView! 12 | @IBOutlet weak var titleLabel: UILabel! 13 | @IBOutlet weak var userLabel: UILabel! 14 | @IBOutlet weak var descriptionTextView: UITextView! 15 | 16 | @IBOutlet weak var applyBtn: UIButton! 17 | @IBOutlet weak var previewBtn: UIButton! 18 | 19 | public var theme: Theme? { 20 | didSet { 21 | titleLabel?.text = theme?.title 22 | descriptionTextView?.text = theme?.description 23 | // iconView.image = theme?.icon 24 | userLabel?.text = "by \(theme?.user ?? "不明")" 25 | } 26 | } 27 | 28 | public var viewController: ViewController! 29 | 30 | private var isApplied = false 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | self.title = theme?.title 36 | applyBtn.layer.cornerRadius = 6 37 | previewBtn.layer.cornerRadius = 6 38 | 39 | iconView.clipsToBounds = true 40 | iconView.layer.cornerRadius = iconView.frame.width / 2 41 | iconView.image = UIImage(named: theme?.icon ?? "") ?? Asset.marindeckLogo.image 42 | titleLabel?.text = theme?.title 43 | descriptionTextView?.text = theme?.description 44 | // iconView.image = theme?.icon 45 | userLabel?.text = theme?.user 46 | 47 | reload() 48 | 49 | setSwipeBack() 50 | } 51 | 52 | func reload() { 53 | view.backgroundColor = .secondaryBackgroundColor 54 | navigationController?.navigationBar.tintColor = .labelColor 55 | navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.labelColor] 56 | 57 | titleLabel.textColor = .labelColor 58 | userLabel.textColor = .subLabelColor 59 | descriptionTextView.textColor = .subLabelColor 60 | 61 | if let themeID = UserDefaults.standard.string(forKey: UserDefaultsKey.themeID) { 62 | if themeID == theme?.id { 63 | isApplied = true 64 | } 65 | } 66 | 67 | if isApplied { 68 | applyBtn.backgroundColor = .backgroundColor 69 | applyBtn.setTitle("適用済", for: .normal) 70 | applyBtn.setTitleColor(.subLabelColor, for: .normal) 71 | 72 | } else { 73 | applyBtn.backgroundColor = .systemBlue 74 | applyBtn.setTitle("適用", for: .normal) 75 | } 76 | } 77 | 78 | @IBAction func preview() { 79 | // UserDefaults.standard.setValue("0", forKey: UserDefaultsKey.themeID) 80 | // reload() 81 | } 82 | 83 | @IBAction func apply() { 84 | if isApplied { return } 85 | UserDefaults.standard.set( theme?.id, forKey: .themeID) 86 | viewController.webView.reload() 87 | reload() 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /Marindeck/Strings/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | // MARK: Menu 2 | "menu.profile.title" = "プロフィール"; 3 | "menu.reload.title" = "リロード"; 4 | "menu.addColumn.title" = "カラムを追加"; 5 | 6 | // MARK: ContextMenu(Image) 7 | "contextMenu.tweetImage.title" = "画像をツイート"; 8 | "contextMenu.like.title" = "いいね"; 9 | "contextMenu.saveImage.title" = "画像を保存"; 10 | "contextMenu.saved.title" = "保存しました"; 11 | 12 | // MARK: OnBoarding 13 | "onBoarding.startMarinDeck.title" = "MarinDeckをはじめる"; 14 | 15 | // MARK: Settings 16 | "settings.navigation.title" = "設定"; 17 | "settings.general.header.title" = "一般"; 18 | "settings.customize.header.title" = "カスタマイズ"; 19 | "settings.appinfo.header.title" = "アプリについて"; 20 | "settings.donate.header.title" = "寄付"; 21 | "settings.logout.header.title" = "ログアウト"; 22 | 23 | "settings.nativePreview.cell.title" = "ネイティブのプレビューを使用"; 24 | "settings.biometrics.cell.title" = "生体認証"; 25 | "settings.tweetButtonBehavior.cell.title" = "ツイートボタンの動作"; 26 | "settings.marginSafeArea.cell.title" = "SafeAreaを考慮する"; 27 | "settings.noSleep.cell.title" = "スリープさせない"; 28 | 29 | "settings.customJS.cell.title" = "カスタムJavaScript"; 30 | "settings.customCSS.cell.title" = "カスタムCSS"; 31 | "settings.theme.cell.title" = "テーマ"; 32 | "settings.customActonButtons.cell.title" = "カスタムアクションボタン"; 33 | "settings.icon.cell.title" = "アイコン"; 34 | 35 | "settings.termsOfUse.cell.title" = "利用規約"; 36 | "settings.license.cell.title" = "ライセンス"; 37 | "settings.issueEnhancement.cell.title" = "ご意見・ご要望"; 38 | "settings.developers.cell.title" = "開発者"; 39 | "settings.donate.cell.title" = "寄付"; 40 | "settings.version.cell.title" = "バージョン"; 41 | "settings.checkUpdate.cell.title" = "更新を確認"; 42 | 43 | "settings.importSettings.cell.title" = "設定をインポート"; 44 | "settings.exportSettings.cell.title" = "設定をエクスポート"; 45 | 46 | "settings.donate.cell.title" = "寄付"; 47 | 48 | "settings.logout.cell.title" = "ログアウト"; 49 | 50 | "settings.checkUpdate.checkingForUpdates.title" = "更新を確認"; 51 | "settings.checkUpdate.existUpdate.title" = "更新があります"; 52 | "settings.checkUpdate.latest.title" = "最新です"; 53 | 54 | // MARK: ActionButtons 55 | "actionButton.debug.title" = "デバッグ"; 56 | "actionButton.gif.title" = "GIF"; 57 | "actionButton.tweet.title" = "ツイート"; 58 | "actionButton.menu.title" = "メニュー"; 59 | "actionButton.draft.title" = "ドラフト"; 60 | "actionButton.settings.title" = "設定"; 61 | 62 | "actionButton.debug.description" = "デバッグモーダルを開く"; 63 | "actionButton.gif.description" = "GIFを選ぶ"; 64 | "actionButton.tweet.description" = "ツイートモーダルを開く"; 65 | "actionButton.menu.description" = "メニューを開く"; 66 | "actionButton.draft.description" = "下書きを開く"; 67 | "actionButton.settings.description" = "設定を開く"; 68 | "actionButton.description.title" = "ツイートボタンを長押ししたときに出てくるアクションボタンを設定できます。"; 69 | 70 | 71 | 72 | // MARK: Alert 73 | "alert.OK.title" = "OK"; 74 | "alert.close.title" = "閉じる"; 75 | "alert.open.title" = "開く"; 76 | "alert.update.title" = "更新"; 77 | "alert.cancel.title" = "キャンセル"; 78 | 79 | "alert.openUrl.title" = "URLを開きますか?"; 80 | "alert.logoutMessage.title" = "ログアウトしますか?"; 81 | 82 | "alert.importedSettings.title" = "設定をインポートしました。"; 83 | "alert.recommendRestartApp.title" = "アプリ再起動をおすすめします。"; 84 | -------------------------------------------------------------------------------- /Marindeck/View/ja.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "ネイティブのプレビューを使用"; ObjectID = "25W-XT-4PI"; */ 3 | "25W-XT-4PI.text" = "ネイティブのプレビューを使用"; 4 | 5 | /* Class = "UITableViewSection"; headerTitle = "ログアウト"; ObjectID = "2qv-XT-C7t"; */ 6 | "2qv-XT-C7t.headerTitle" = "ログアウト"; 7 | 8 | /* Class = "UILabel"; text = "カスタムCSS"; ObjectID = "3jh-83-S3a"; */ 9 | "3jh-83-S3a.text" = "カスタムCSS"; 10 | 11 | /* Class = "UILabel"; text = "設定をインポート"; ObjectID = "9Of-JB-4oA"; */ 12 | "9Of-JB-4oA.text" = "設定をインポート"; 13 | 14 | /* Class = "UILabel"; text = "設定をエクスポート"; ObjectID = "9s8-52-qb7"; */ 15 | "9s8-52-qb7.text" = "設定をエクスポート"; 16 | 17 | /* Class = "UITextView"; text = "デスクトップ版Discordをモチーフにしたテーマです。明るすぎず暗すぎないDiscordのダークテーマが好きな方におすすめです"; ObjectID = "CEG-80-AEU"; */ 18 | "CEG-80-AEU.text" = "デスクトップ版Discordをモチーフにしたテーマです。明るすぎず暗すぎないDiscordのダークテーマが好きな方におすすめです"; 19 | 20 | /* Class = "UILabel"; text = "0.0.0 Alpha"; ObjectID = "DkX-M1-Evn"; */ 21 | "DkX-M1-Evn.text" = "0.0.0 Alpha"; 22 | 23 | /* Class = "UILabel"; text = "利用規約"; ObjectID = "G19-zP-4LD"; */ 24 | "G19-zP-4LD.text" = "利用規約"; 25 | 26 | /* Class = "UILabel"; text = "バージョン"; ObjectID = "GQ1-eM-49f"; */ 27 | "GQ1-eM-49f.text" = "バージョン"; 28 | 29 | /* Class = "UILabel"; text = "By TweetDeck"; ObjectID = "HHq-On-ZPg"; */ 30 | "HHq-On-ZPg.text" = "By TweetDeck"; 31 | 32 | /* Class = "UILabel"; text = "デフォルト"; ObjectID = "NDC-rS-v8P"; */ 33 | "NDC-rS-v8P.text" = "デフォルト"; 34 | 35 | /* Class = "UILabel"; text = "@twitter"; ObjectID = "QZQ-Zj-807"; */ 36 | "QZQ-Zj-807.text" = "@twitter"; 37 | 38 | /* Class = "UITableViewSection"; headerTitle = "アプリについて"; ObjectID = "QcA-JT-qUC"; */ 39 | "QcA-JT-qUC.headerTitle" = "アプリについて"; 40 | 41 | /* Class = "UILabel"; text = "サポーターの皆様"; ObjectID = "Yhd-Po-OYi"; */ 42 | "Yhd-Po-OYi.text" = "サポーターの皆様"; 43 | 44 | /* Class = "UILabel"; text = "着せ替え"; ObjectID = "YkZ-WD-38a"; */ 45 | "YkZ-WD-38a.text" = "着せ替え"; 46 | 47 | /* Class = "UILabel"; text = "ライセンス情報"; ObjectID = "Z1e-0c-usZ"; */ 48 | "Z1e-0c-usZ.text" = "ライセンス情報"; 49 | 50 | /* Class = "UILabel"; text = "ログアウト"; ObjectID = "ZZp-Ll-buN"; */ 51 | "ZZp-Ll-buN.text" = "ログアウト"; 52 | 53 | /* Class = "UIButton"; normalTitle = "プレビュー"; ObjectID = "cuw-34-Vjr"; */ 54 | "cuw-34-Vjr.normalTitle" = "プレビュー"; 55 | 56 | /* Class = "UITableViewSection"; headerTitle = "カスタマイズ"; ObjectID = "d0p-CE-9K3"; */ 57 | "d0p-CE-9K3.headerTitle" = "カスタマイズ"; 58 | 59 | /* Class = "UILabel"; text = "ご意見・ご要望"; ObjectID = "eRj-KM-XGr"; */ 60 | "eRj-KM-XGr.text" = "ご意見・ご要望"; 61 | 62 | /* Class = "UILabel"; text = "TestAccount"; ObjectID = "eai-3F-GbR"; */ 63 | "eai-3F-GbR.text" = "TestAccount"; 64 | 65 | /* Class = "UILabel"; text = "寄付"; ObjectID = "gSK-oI-7Qi"; */ 66 | "gSK-oI-7Qi.text" = "寄付"; 67 | 68 | /* Class = "UILabel"; text = "カスタムJavaScript"; ObjectID = "kGa-Gz-6XG"; */ 69 | "kGa-Gz-6XG.text" = "カスタムJavaScript"; 70 | 71 | /* Class = "UIButton"; normalTitle = "適用"; ObjectID = "nKz-ke-eMH"; */ 72 | "nKz-ke-eMH.normalTitle" = "適用"; 73 | 74 | /* Class = "UITableViewSection"; headerTitle = "一般"; ObjectID = "vTI-XN-UL0"; */ 75 | "vTI-XN-UL0.headerTitle" = "一般"; 76 | 77 | /* Class = "UILabel"; text = "利用規約"; ObjectID = "x3V-SQ-2Sn"; */ 78 | "x3V-SQ-2Sn.text" = "利用規約"; 79 | -------------------------------------------------------------------------------- /Marindeck/Database/Database.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Database.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/10/30. 6 | // 7 | 8 | import Foundation 9 | import GRDB 10 | 11 | // FIXME: Modelに移動 12 | struct CustomJS: Codable, FetchableRecord, PersistableRecord { 13 | var id: Int64? 14 | var title: String 15 | var js: String 16 | var createAt: Date 17 | var updateAt: Date 18 | var loadIndex: Int32 19 | var isLoad: Bool 20 | } 21 | 22 | // FIXME: Modelに移動 23 | struct CustomCSS: Codable, FetchableRecord, PersistableRecord { 24 | var id: Int64? 25 | var title: String 26 | var css: String 27 | var createAt: Date 28 | var updateAt: Date 29 | var loadIndex: Int32 30 | var isLoad: Bool 31 | } 32 | 33 | struct Draft: Codable, FetchableRecord, PersistableRecord { 34 | var id: Int64? 35 | var text: String 36 | } 37 | 38 | class Database { 39 | static let shared = Database() 40 | 41 | public private(set) lazy var dbQueue: DatabaseQueue = { 42 | let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! 43 | print("DATABASE DIR: \(dir.absoluteString + "database.sqlite")") 44 | return try! DatabaseQueue(path: dir.absoluteString + "database.sqlite") 45 | }() 46 | 47 | func setup() { 48 | 49 | try? dbQueue.write { db in 50 | // CustomJS 51 | try db.create(table: "customjs") { t in 52 | t.autoIncrementedPrimaryKey("id") 53 | t.column("title", .text).notNull() 54 | t.column("js", .text).notNull() 55 | t.column("createAt", .date).notNull() 56 | t.column("updateAt", .date).notNull() 57 | t.column("loadIndex", .integer).notNull() 58 | t.column("isLoad", .boolean).notNull() 59 | } 60 | } 61 | try? dbQueue.write { db in 62 | // CustomCSS 63 | try db.create(table: "customcss") { t in 64 | t.autoIncrementedPrimaryKey("id") 65 | t.column("title", .text).notNull() 66 | t.column("css", .text).notNull() 67 | t.column("createAt", .date).notNull() 68 | t.column("updateAt", .date).notNull() 69 | t.column("loadIndex", .integer).notNull() 70 | t.column("isLoad", .boolean).notNull() 71 | } 72 | } 73 | try? dbQueue.write { db in 74 | // Draft 75 | try db.create(table: "draft") { t in 76 | t.autoIncrementedPrimaryKey("id") 77 | t.column("text", .text).notNull() 78 | } 79 | } 80 | 81 | try? dbQueue.write { db in 82 | // RetemoJS 83 | try db.create(table: "remotejsdata") { t in 84 | t.autoIncrementedPrimaryKey("_id") 85 | t.column("id", .text).notNull() 86 | t.column("title", .text).notNull() 87 | t.column("version", .integer).notNull() 88 | t.column("jsUrl", .text).notNull() 89 | t.column("js", .text) 90 | } 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /Marindeck/View/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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Marindeck/View/Other/ModalBrowserViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModalBrowserViewController.swift 3 | // Marindeck 4 | // 5 | // Created by a on 12/13/22. 6 | // 7 | 8 | import UIKit 9 | import WebKit 10 | 11 | class ModalBrowserViewController: UIViewController, WKUIDelegate, WKNavigationDelegate { 12 | public var url: String = "" 13 | 14 | var webView: WKWebView! 15 | lazy var dismissButton: UIButton = { 16 | let btn = UIButton() 17 | let cf = UIImage.SymbolConfiguration(pointSize: 28, weight: .medium, scale: .default) 18 | btn.setImage(UIImage(systemName: "xmark.circle.fill", withConfiguration: cf), for: .normal) 19 | btn.tintColor = .label 20 | btn.translatesAutoresizingMaskIntoConstraints = false 21 | return btn 22 | }() 23 | 24 | override func loadView() { 25 | let webConfiguration = WKWebViewConfiguration() 26 | webView = WKWebView(frame: .zero, configuration: webConfiguration) 27 | webView.uiDelegate = self 28 | webView.navigationDelegate = self 29 | view = webView 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | view.addSubview(dismissButton) 36 | 37 | NSLayoutConstraint.activate([ 38 | dismissButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 12), 39 | dismissButton.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -12), 40 | dismissButton.widthAnchor.constraint(equalToConstant: 28), 41 | dismissButton.heightAnchor.constraint(equalToConstant: 28) 42 | ]) 43 | 44 | dismissButton.addTarget(self, action: #selector(onDismiss), for: .touchUpInside) 45 | 46 | view.bringSubviewToFront(webView) 47 | 48 | let myURL = URL(string: url) 49 | let request = URLRequest(url: myURL!) 50 | 51 | let jsonString = """ 52 | [{ 53 | "trigger": { 54 | "url-filter": ".*" 55 | }, 56 | "action": { 57 | "type": "css-display-none", 58 | "selector": "#layers" 59 | } 60 | }] 61 | """ 62 | 63 | WKContentRuleListStore.default().compileContentRuleList(forIdentifier: "ContentBlockingRules", encodedContentRuleList: jsonString) { rulesList, error in 64 | if let error = error { 65 | print(error) 66 | return 67 | } 68 | guard let rulesList = rulesList else { 69 | return 70 | } 71 | let config = self.webView.configuration 72 | config.userContentController.add(rulesList) 73 | self.webView.load(request) 74 | } 75 | } 76 | 77 | @objc 78 | func onDismiss() { 79 | dismiss(animated: true, completion: nil) 80 | } 81 | 82 | func webView(_ webView: WKWebView, 83 | decidePolicyFor navigationAction: WKNavigationAction, 84 | decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 85 | decisionHandler(.allow) 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /Marindeck/Strings/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | // MARK: Menu 2 | "menu.profile.title" = "Profile"; 3 | "menu.reload.title" = "Reload"; 4 | "menu.addColumn.title" = "Add column"; 5 | 6 | // MARK: ContextMenu(Image) 7 | "contextMenu.tweetImage.title" = "Tweet image"; 8 | "contextMenu.like.title" = "Like"; 9 | "contextMenu.saveImage.title" = "Save image"; 10 | "contextMenu.saved.title" = "Saved"; 11 | 12 | // MARK: OnBoarding 13 | "onBoarding.startMarinDeck.title" = "Start MarinDeck"; 14 | 15 | // MARK: Settings 16 | "settings.navigation.title" = "Settings"; 17 | "settings.general.header.title" = "General"; 18 | "settings.customize.header.title" = "Customize"; 19 | "settings.appinfo.header.title" = "App info"; 20 | "settings.donate.header.title" = "Donate"; 21 | "settings.logout.header.title" = "Logout"; 22 | 23 | "settings.nativePreview.cell.title" = "Use native preview"; 24 | "settings.biometrics.cell.title" = "Biometrics"; 25 | "settings.tweetButtonBehavior.cell.title" = "Tweet button behavior"; 26 | "settings.marginSafeArea.cell.title" = "Margin safeArea"; 27 | "settings.noSleep.cell.title" = "No Sleep"; 28 | 29 | "settings.customJS.cell.title" = "Custom JavaScript"; 30 | "settings.customCSS.cell.title" = "Custom CSS"; 31 | "settings.theme.cell.title" = "Themes"; 32 | "settings.customActonButtons.cell.title" = "Custom action buttons"; 33 | "settings.icon.cell.title" = "Icons"; 34 | 35 | "settings.termsOfUse.cell.title" = "Team of use"; 36 | "settings.license.cell.title" = "License"; 37 | "settings.issueEnhancement.cell.title" = "issue, enhancement"; 38 | "settings.developers.cell.title" = "Developers"; 39 | "settings.donate.cell.title" = "Donate"; 40 | "settings.version.cell.title" = "Version"; 41 | "settings.checkUpdate.cell.title" = "Check update"; 42 | 43 | "settings.importSettings.cell.title" = "Import settings"; 44 | "settings.exportSettings.cell.title" = "Export settings"; 45 | 46 | "settings.donate.cell.title" = "donate"; 47 | 48 | "settings.logout.cell.title" = "logout"; 49 | 50 | "settings.checkUpdate.checkingForUpdates.title" = "Checking for updates."; 51 | "settings.checkUpdate.existUpdate.title" = "There will be an update."; 52 | "settings.checkUpdate.latest.title" = "latest."; 53 | 54 | 55 | // MARK: ActionButtons 56 | "actionButton.debug.title" = "Debug"; 57 | "actionButton.gif.title" = "GIF"; 58 | "actionButton.tweet.title" = "Tweet"; 59 | "actionButton.menu.title" = "Menu"; 60 | "actionButton.draft.title" = "Draft"; 61 | "actionButton.settings.title" = "Settings"; 62 | 63 | "actionButton.debug.description" = "Open the debug modal."; 64 | "actionButton.gif.description" = "Select GIF"; 65 | "actionButton.tweet.description" = "Open the tweet modal"; 66 | "actionButton.menu.description" = "Open the menu"; 67 | "actionButton.draft.description" = "Open a draft"; 68 | "actionButton.settings.description" = "Open Settings"; 69 | "actionButton.description.title" = "Set the action button to be displayed when you press and hold the tweet button."; 70 | 71 | // MARK: Alert 72 | "alert.OK.title" = "OK"; 73 | "alert.close.title" = "Close"; 74 | "alert.open.title" = "Open"; 75 | "alert.update.title" = "Update"; 76 | "alert.cancel.title" = "Cancel"; 77 | 78 | "alert.openUrl.title" = "Open URL?"; 79 | "alert.logoutMessage.title" = ""; 80 | 81 | "alert.importedSettings.title" = "Imported the configuration."; 82 | "alert.recommendRestartApp.title" = "We recommend restarting the application."; 83 | -------------------------------------------------------------------------------- /Marindeck/View/Common/AppDebugView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDebugView.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/03/11. 6 | // 7 | 8 | import SwiftUI 9 | import SwiftyStoreKit 10 | 11 | struct AppDebugView: View { 12 | @Environment(\.presentationMode) var presentationMode 13 | weak var vc: UIViewController? 14 | 15 | init(vc: UIViewController? = nil) { 16 | self.vc = vc 17 | } 18 | 19 | var body: some View { 20 | VStack(spacing: 32) { 21 | Button("AppDebugモードを終了", action: { 22 | UserDefaults.standard.set(false, forKey: .appDebugMode) 23 | presentationMode.wrappedValue.dismiss() 24 | }) 25 | Button("Deckを表示", action: { 26 | let vc = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as! ViewController 27 | self.vc?.present(vc, animated: false, completion: nil) 28 | }) 29 | Button("設定を表示", action: { 30 | let vc = UIStoryboard(name: "Settings", bundle: nil).instantiateInitialViewController() as! SettingsTableViewController 31 | let nvc = UINavigationController(rootViewController: vc) 32 | self.vc?.present(nvc, animated: true, completion: nil) 33 | }) 34 | Button("Twitter設定画面", action: { 35 | vc?.present(TwitterSettingsViewController(), animated: true, completion: nil) 36 | }) 37 | 38 | Button("OnBoardingを表示", action: { 39 | let onBoardingVC = OnBoardingViewController() 40 | onBoardingVC.modalPresentationStyle = .currentContext 41 | vc?.present(onBoardingVC, animated: false, completion: nil) 42 | }) 43 | 44 | Button("ネイティブTweetModal", action: { 45 | let vc = UIViewController() 46 | vc.modalPresentationStyle = .overFullScreen 47 | let view = TweetView() 48 | view.frame = vc.view.bounds 49 | view.normalTweetModalY = 40 50 | vc.view.addSubview(view) 51 | vc.view.backgroundColor = UIColor.green.withAlphaComponent(0.3) 52 | self.vc?.present(vc, animated: false, completion: nil) 53 | }) 54 | 55 | Button("Test課金") { 56 | let productIds: Set = ["300yen"] 57 | 58 | SwiftyStoreKit.retrieveProductsInfo(productIds) { result in 59 | if result.retrievedProducts.first != nil { 60 | let products = result.retrievedProducts.sorted { (firstProduct, secondProduct) -> Bool in 61 | return firstProduct.price.doubleValue < secondProduct.price.doubleValue 62 | } 63 | for product in products { 64 | print(product) 65 | } 66 | } else if result.invalidProductIDs.first != nil { 67 | print("Invalid product identifier : \(result.invalidProductIDs)") 68 | } else { 69 | print("Error : \(result.error.debugDescription)") 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | struct AppDebugView_Previews: PreviewProvider { 78 | static var previews: some View { 79 | AppDebugView() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Marindeck/View/Settings/TwitterSettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterSettingsViewController.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/11. 6 | // 7 | 8 | import UIKit 9 | import WebKit 10 | 11 | class TwitterSettingsViewController: UIViewController, WKUIDelegate, WKNavigationDelegate { 12 | public var url = "https://mobile.twitter.com/settings" 13 | 14 | var webView: WKWebView! 15 | lazy var dismissButton: UIButton = { 16 | let btn = UIButton() 17 | let cf = UIImage.SymbolConfiguration(pointSize: 28, weight: .medium, scale: .default) 18 | btn.setImage(UIImage(systemName: "xmark.circle.fill", withConfiguration: cf), for: .normal) 19 | btn.tintColor = .label 20 | btn.translatesAutoresizingMaskIntoConstraints = false 21 | return btn 22 | }() 23 | 24 | override func loadView() { 25 | let webConfiguration = WKWebViewConfiguration() 26 | webView = WKWebView(frame: .zero, configuration: webConfiguration) 27 | webView.uiDelegate = self 28 | webView.navigationDelegate = self 29 | view = webView 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | view.addSubview(dismissButton) 36 | 37 | NSLayoutConstraint.activate([ 38 | dismissButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 12), 39 | dismissButton.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -12), 40 | dismissButton.widthAnchor.constraint(equalToConstant: 28), 41 | dismissButton.heightAnchor.constraint(equalToConstant: 28) 42 | ]) 43 | 44 | dismissButton.addTarget(self, action: #selector(onDismiss), for: .touchUpInside) 45 | 46 | view.bringSubviewToFront(webView) 47 | 48 | let myURL = URL(string: url) 49 | let request = URLRequest(url: myURL!) 50 | 51 | let jsonString = """ 52 | [{ 53 | "trigger": { 54 | "url-filter": ".*" 55 | }, 56 | "action": { 57 | "type": "css-display-none", 58 | "selector": "#layers" 59 | } 60 | }] 61 | """ 62 | 63 | WKContentRuleListStore.default().compileContentRuleList(forIdentifier: "ContentBlockingRules", encodedContentRuleList: jsonString) { rulesList, error in 64 | if let error = error { 65 | print(error) 66 | return 67 | } 68 | guard let rulesList = rulesList else { 69 | return 70 | } 71 | let config = self.webView.configuration 72 | config.userContentController.add(rulesList) 73 | self.webView.load(request) 74 | } 75 | } 76 | 77 | @objc 78 | func onDismiss() { 79 | dismiss(animated: true, completion: nil) 80 | } 81 | 82 | func webView(_ webView: WKWebView, 83 | decidePolicyFor navigationAction: WKNavigationAction, 84 | decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 85 | let url = navigationAction.request.url 86 | 87 | if !(url?.path.contains("/settings") ?? true) || !(url?.host == "mobile.twitter.com") { 88 | decisionHandler(.cancel) 89 | self.dismiss(animated: true, completion: nil) 90 | } else { 91 | decisionHandler(.allow) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Marindeck/Util/RemoteJS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteJS.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/02/02. 6 | // 7 | 8 | import Alamofire 9 | import Foundation 10 | 11 | class RemoteJS { 12 | static let shared = RemoteJS() 13 | 14 | private lazy var dbQueue = Database.shared.dbQueue 15 | 16 | public private(set) var remoteJSs: [RemoteJSData]? 17 | private var savedRemoteJSs: [RemoteJSData]? 18 | 19 | func update(completion: @escaping() -> Void) { 20 | fetchDb() 21 | fetch { [weak self] remoteJSs in 22 | guard let self = self else { return } 23 | self.remoteJSs = remoteJSs 24 | 25 | for remoteJS in remoteJSs ?? [] { 26 | if !self.isLatest(latest: remoteJS) { 27 | let result = self.fetchJS(remoteJS: remoteJS) 28 | self.saveDb(remoteJS: result) 29 | } 30 | } 31 | self.fetchDb() 32 | completion() 33 | } 34 | } 35 | 36 | func getJs(id: RemoteJSDataId) -> String? { 37 | savedRemoteJSs?.filter { $0.id == id.rawValue }[safe: 0]?.js 38 | } 39 | 40 | func isLatest(id: RemoteJSDataId) -> Bool { 41 | guard let latest = remoteJSs?.filter({ $0.id == id.rawValue })[safe: 0] else { return false } 42 | return isLatest(latest: latest) 43 | } 44 | 45 | func isLatest(latest: RemoteJSData) -> Bool { 46 | guard let currentVersion = savedRemoteJSs? 47 | .filter({ $0.id == latest.id })[safe: 0]?.version else { return false } 48 | 49 | return currentVersion >= latest.version 50 | } 51 | 52 | private func fetch(completion: @escaping([RemoteJSData]?) -> Void) { 53 | AF.request(DebugSettings.remoteJsUrl).response { response in 54 | guard let data = response.data else { return } 55 | let remoteJs = try? JSONDecoder().decode([RemoteJSData].self, from: data) 56 | completion(remoteJs) 57 | } 58 | } 59 | 60 | private func fetchDb() { 61 | savedRemoteJSs = try! dbQueue.read { db in 62 | try RemoteJSData.fetchAll(db) 63 | } 64 | } 65 | 66 | private func saveDb(remoteJS: RemoteJSData) { 67 | if let rjs = savedRemoteJSs?.filter({ $0.id == remoteJS.id })[safe: 0] { 68 | var saverjs = rjs 69 | saverjs.version = remoteJS.version 70 | saverjs.jsUrl = remoteJS.jsUrl 71 | saverjs.js = remoteJS.js 72 | try! dbQueue.write { db in 73 | try saverjs.update(db) 74 | } 75 | } else { 76 | try! dbQueue.write { db in 77 | try remoteJS.insert(db) 78 | } 79 | } 80 | } 81 | 82 | private func fetchJS(remoteJS: RemoteJSData) -> RemoteJSData { 83 | print(#function) 84 | let semaphore = DispatchSemaphore(value: 0) 85 | let queue = DispatchQueue.global(qos: .utility) 86 | var result = remoteJS 87 | AF.request(remoteJS.jsUrl).response(queue: queue) { response in 88 | print(response) 89 | guard let data = response.data else { 90 | semaphore.signal() 91 | return 92 | } 93 | var tmpRemoteJS = remoteJS 94 | tmpRemoteJS.js = String(data: data, encoding: .utf8) 95 | result = tmpRemoteJS 96 | semaphore.signal() 97 | } 98 | semaphore.wait() 99 | return result 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /Marindeck/Info-dev.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ja_JP 7 | CFBundleDisplayName 8 | MarinDeck-dev 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIcons 12 | 13 | CFBundleAlternateIcons 14 | 15 | BlackIcon 16 | 17 | CFBundleIconFiles 18 | 19 | BlackIcon 20 | 21 | 22 | RainbowIcon 23 | 24 | CFBundleIconFiles 25 | 26 | RainbowIcon 27 | 28 | 29 | 30 | 31 | CFBundleIdentifier 32 | $(PRODUCT_BUNDLE_IDENTIFIER) 33 | CFBundleInfoDictionaryVersion 34 | 6.0 35 | CFBundleName 36 | $(PRODUCT_NAME) 37 | CFBundlePackageType 38 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 39 | CFBundleShortVersionString 40 | $(MARKETING_VERSION) 41 | CFBundleVersion 42 | $(CURRENT_PROJECT_VERSION) 43 | ITSAppUsesNonExemptEncryption 44 | 45 | LSApplicationCategoryType 46 | public.app-category.social-networking 47 | LSApplicationQueriesSchemes 48 | 49 | youtube 50 | 51 | LSRequiresIPhoneOS 52 | 53 | NSAppTransportSecurity 54 | 55 | NSAllowsArbitraryLoads 56 | 57 | 58 | NSCameraUsageDescription 59 | 画像を投稿するためにカメラを使用します。 60 | NSFaceIDUsageDescription 61 | アプリのロックに使用します。 62 | NSPhotoLibraryAddUsageDescription 63 | 投稿された画像を保存します。 64 | UIApplicationSceneManifest 65 | 66 | UIApplicationSupportsMultipleScenes 67 | 68 | UISceneConfigurations 69 | 70 | UIWindowSceneSessionRoleApplication 71 | 72 | 73 | UISceneConfigurationName 74 | Default Configuration 75 | UISceneDelegateClassName 76 | $(PRODUCT_MODULE_NAME).SceneDelegate 77 | UISceneStoryboardFile 78 | Main 79 | 80 | 81 | 82 | 83 | UIApplicationSupportsIndirectInputEvents 84 | 85 | UIFileSharingEnabled 86 | 87 | UILaunchStoryboardName 88 | LaunchScreen 89 | UIMainStoryboardFile 90 | Main 91 | UIRequiredDeviceCapabilities 92 | 93 | armv7 94 | 95 | UISupportedInterfaceOrientations 96 | 97 | UIInterfaceOrientationPortrait 98 | UIInterfaceOrientationLandscapeLeft 99 | UIInterfaceOrientationLandscapeRight 100 | 101 | UISupportedInterfaceOrientations~ipad 102 | 103 | UIInterfaceOrientationPortrait 104 | UIInterfaceOrientationPortraitUpsideDown 105 | UIInterfaceOrientationLandscapeLeft 106 | UIInterfaceOrientationLandscapeRight 107 | 108 | UIViewControllerBasedStatusBarAppearance 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /Marindeck/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ja_JP 7 | CFBundleDisplayName 8 | $(DISPLAY_APPNAME) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIcons 12 | 13 | CFBundleAlternateIcons 14 | 15 | BlackIcon 16 | 17 | CFBundleIconFiles 18 | 19 | BlackIcon 20 | 21 | 22 | RainbowIcon 23 | 24 | CFBundleIconFiles 25 | 26 | RainbowIcon 27 | 28 | 29 | 30 | 31 | CFBundleIdentifier 32 | $(PRODUCT_BUNDLE_IDENTIFIER) 33 | CFBundleInfoDictionaryVersion 34 | 6.0 35 | CFBundleName 36 | $(PRODUCT_NAME) 37 | CFBundlePackageType 38 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 39 | CFBundleShortVersionString 40 | $(MARKETING_VERSION) 41 | CFBundleVersion 42 | $(CURRENT_PROJECT_VERSION) 43 | ITSAppUsesNonExemptEncryption 44 | 45 | LSApplicationCategoryType 46 | public.app-category.social-networking 47 | LSApplicationQueriesSchemes 48 | 49 | youtube 50 | twitter 51 | 52 | LSRequiresIPhoneOS 53 | 54 | NSAppTransportSecurity 55 | 56 | NSAllowsArbitraryLoads 57 | 58 | 59 | NSCameraUsageDescription 60 | 画像を投稿するためにカメラを使用します。 61 | NSFaceIDUsageDescription 62 | アプリのロックに使用します。 63 | NSPhotoLibraryAddUsageDescription 64 | 投稿された画像を保存します。 65 | UIApplicationSceneManifest 66 | 67 | UIApplicationSupportsMultipleScenes 68 | 69 | UISceneConfigurations 70 | 71 | UIWindowSceneSessionRoleApplication 72 | 73 | 74 | UISceneConfigurationName 75 | Default Configuration 76 | UISceneDelegateClassName 77 | $(PRODUCT_MODULE_NAME).SceneDelegate 78 | UISceneStoryboardFile 79 | Main 80 | 81 | 82 | 83 | 84 | UIApplicationSupportsIndirectInputEvents 85 | 86 | UIFileSharingEnabled 87 | 88 | UILaunchStoryboardName 89 | LaunchScreen 90 | UIMainStoryboardFile 91 | Main 92 | UIRequiredDeviceCapabilities 93 | 94 | armv7 95 | 96 | UISupportedInterfaceOrientations 97 | 98 | UIInterfaceOrientationPortrait 99 | UIInterfaceOrientationLandscapeLeft 100 | UIInterfaceOrientationLandscapeRight 101 | 102 | UISupportedInterfaceOrientations~ipad 103 | 104 | UIInterfaceOrientationPortrait 105 | UIInterfaceOrientationPortraitUpsideDown 106 | UIInterfaceOrientationLandscapeLeft 107 | UIInterfaceOrientationLandscapeRight 108 | 109 | UIViewControllerBasedStatusBarAppearance 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /Marindeck/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "57.png", 47 | "idiom" : "iphone", 48 | "scale" : "1x", 49 | "size" : "57x57" 50 | }, 51 | { 52 | "filename" : "114.png", 53 | "idiom" : "iphone", 54 | "scale" : "2x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "120.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "60x60" 62 | }, 63 | { 64 | "filename" : "180.png", 65 | "idiom" : "iphone", 66 | "scale" : "3x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "filename" : "20.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "20x20" 74 | }, 75 | { 76 | "filename" : "40.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "20x20" 80 | }, 81 | { 82 | "filename" : "29.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "29x29" 86 | }, 87 | { 88 | "filename" : "58.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "29x29" 92 | }, 93 | { 94 | "filename" : "40.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "40x40" 98 | }, 99 | { 100 | "filename" : "80.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "40x40" 104 | }, 105 | { 106 | "filename" : "50.png", 107 | "idiom" : "ipad", 108 | "scale" : "1x", 109 | "size" : "50x50" 110 | }, 111 | { 112 | "filename" : "100.png", 113 | "idiom" : "ipad", 114 | "scale" : "2x", 115 | "size" : "50x50" 116 | }, 117 | { 118 | "filename" : "72.png", 119 | "idiom" : "ipad", 120 | "scale" : "1x", 121 | "size" : "72x72" 122 | }, 123 | { 124 | "filename" : "144.png", 125 | "idiom" : "ipad", 126 | "scale" : "2x", 127 | "size" : "72x72" 128 | }, 129 | { 130 | "filename" : "76.png", 131 | "idiom" : "ipad", 132 | "scale" : "1x", 133 | "size" : "76x76" 134 | }, 135 | { 136 | "filename" : "152.png", 137 | "idiom" : "ipad", 138 | "scale" : "2x", 139 | "size" : "76x76" 140 | }, 141 | { 142 | "filename" : "167.png", 143 | "idiom" : "ipad", 144 | "scale" : "2x", 145 | "size" : "83.5x83.5" 146 | }, 147 | { 148 | "filename" : "1024.png", 149 | "idiom" : "ios-marketing", 150 | "scale" : "1x", 151 | "size" : "1024x1024" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Marindeck/Model/TD/TD+Actions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TD+Actions.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/01/25. 6 | // 7 | import Foundation 8 | 9 | extension TD.ActionsController { 10 | // ツイート画面のTextViewにフォーカスする 11 | func focusTweetTextArea() { 12 | webView?.evaluateJavaScript("document.querySelector(\".js-compose-text\").focus()") { _, error in 13 | print("focusTweetTextArea : ", error ?? "成功") 14 | } 15 | } 16 | 17 | // 座標の位置にあるツイートをいいね 18 | func positionTweetLike(x: Int, y: Int) { 19 | webView?.evaluateJavaScript("window.MarinDeckInputs.touchPointTweetLike(\(x), \(y))", completionHandler: { _, error in 20 | print("touchPointTweetLike : ", error ?? "成功") 21 | }) 22 | } 23 | 24 | // カラムスクロールの制御(正常に動作しません。) 25 | func isColumnScroll(_ bool: Bool) { 26 | let isScroll = bool ? "on" : "off" 27 | webView?.evaluateJavaScript("columnScroll.\(isScroll)()") { _, error in 28 | print("webViewLog : ", error ?? "成功") 29 | } 30 | } 31 | 32 | // ツイート 33 | func tweet(text: String) { 34 | let replaceDict = ["\\": "\\\\", "\"": "\\\"", "\'": "\\\'"] 35 | let txt = replaceDict.reduce(text) { $0.replacingOccurrences(of: $1.key, with: $1.value) } 36 | webView?.evaluateJavaScript("window.MarinDeckInputs.postTweet('\(txt)')") { _, error in 37 | print("tweet : ", error ?? "成功") 38 | } 39 | } 40 | 41 | // FIXME: あとから出てきたheaderに適用されない。 42 | // top-margin 43 | func setStatusBarSpace(height: Int) { 44 | let headerHeight = height + 50 45 | webView?.evaluateJavaScript(""" 46 | document.querySelectorAll(".column-header").forEach(function(item) { 47 | item.style.height = "\(headerHeight)px" 48 | item.style.maxHeight = "\(headerHeight)px" 49 | item.style.paddingTop = "\(height)px" 50 | }) 51 | """) { _, error in 52 | print(#function, error ?? "成功") 53 | } 54 | webView?.evaluateJavaScript(""" 55 | document.querySelectorAll(".js-detail-header").forEach(function(item) { 56 | item.style.height = "\(headerHeight)px" 57 | item.style.maxHeight = "\(headerHeight)px" 58 | item.style.paddingTop = "\(height)px" 59 | }) 60 | """) { _, error in 61 | print(#function, error ?? "成功") 62 | } 63 | } 64 | 65 | // MARK: Blob 66 | func setBlob(url: String, base64: String, mimeType: String) { 67 | webView?.evaluateJavaScript("MD4iOS.Blob.set(\(url), \(base64), \(mimeType)") 68 | } 69 | 70 | func setSchedule(date: Date) { 71 | let calendar = Calendar.current 72 | let Y = calendar.component(.year, from: date) 73 | let M = calendar.component(.month, from: date) 74 | let D = calendar.component(.day, from: date) 75 | let h = calendar.component(.hour, from: date) 76 | let m = calendar.component(.minute, from: date) 77 | 78 | webView?.evaluateJavaScript("window.MarinDeckInputs.updateSchedule(\(Y), \(M), \(D), \(h), \(m))") { _, error in 79 | print(#function, error ?? "成功") 80 | } 81 | } 82 | 83 | func openTDSettings() { 84 | webView?.evaluateJavaScript("document.querySelector(\".js-app-settings\").click();document.querySelector(\"[data-action='globalSettings']\").click()") 85 | } 86 | 87 | func send(uuid: String, value: Any?) { 88 | webView?.evaluateJavaScript("window.MD.Native.send({uuid: '\(uuid)', value: \(value ?? "null")})") 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /*! Jazzy - https://github.com/realm/jazzy 2 | * Copyright Realm Inc. 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | /* Credit to https://gist.github.com/wataru420/2048287 */ 6 | .highlight .c { 7 | color: #999988; 8 | font-style: italic; } 9 | 10 | .highlight .err { 11 | color: #a61717; 12 | background-color: #e3d2d2; } 13 | 14 | .highlight .k { 15 | color: #000000; 16 | font-weight: bold; } 17 | 18 | .highlight .o { 19 | color: #000000; 20 | font-weight: bold; } 21 | 22 | .highlight .cm { 23 | color: #999988; 24 | font-style: italic; } 25 | 26 | .highlight .cp { 27 | color: #999999; 28 | font-weight: bold; } 29 | 30 | .highlight .c1 { 31 | color: #999988; 32 | font-style: italic; } 33 | 34 | .highlight .cs { 35 | color: #999999; 36 | font-weight: bold; 37 | font-style: italic; } 38 | 39 | .highlight .gd { 40 | color: #000000; 41 | background-color: #ffdddd; } 42 | 43 | .highlight .gd .x { 44 | color: #000000; 45 | background-color: #ffaaaa; } 46 | 47 | .highlight .ge { 48 | color: #000000; 49 | font-style: italic; } 50 | 51 | .highlight .gr { 52 | color: #aa0000; } 53 | 54 | .highlight .gh { 55 | color: #999999; } 56 | 57 | .highlight .gi { 58 | color: #000000; 59 | background-color: #ddffdd; } 60 | 61 | .highlight .gi .x { 62 | color: #000000; 63 | background-color: #aaffaa; } 64 | 65 | .highlight .go { 66 | color: #888888; } 67 | 68 | .highlight .gp { 69 | color: #555555; } 70 | 71 | .highlight .gs { 72 | font-weight: bold; } 73 | 74 | .highlight .gu { 75 | color: #aaaaaa; } 76 | 77 | .highlight .gt { 78 | color: #aa0000; } 79 | 80 | .highlight .kc { 81 | color: #000000; 82 | font-weight: bold; } 83 | 84 | .highlight .kd { 85 | color: #000000; 86 | font-weight: bold; } 87 | 88 | .highlight .kp { 89 | color: #000000; 90 | font-weight: bold; } 91 | 92 | .highlight .kr { 93 | color: #000000; 94 | font-weight: bold; } 95 | 96 | .highlight .kt { 97 | color: #445588; } 98 | 99 | .highlight .m { 100 | color: #009999; } 101 | 102 | .highlight .s { 103 | color: #d14; } 104 | 105 | .highlight .na { 106 | color: #008080; } 107 | 108 | .highlight .nb { 109 | color: #0086B3; } 110 | 111 | .highlight .nc { 112 | color: #445588; 113 | font-weight: bold; } 114 | 115 | .highlight .no { 116 | color: #008080; } 117 | 118 | .highlight .ni { 119 | color: #800080; } 120 | 121 | .highlight .ne { 122 | color: #990000; 123 | font-weight: bold; } 124 | 125 | .highlight .nf { 126 | color: #990000; } 127 | 128 | .highlight .nn { 129 | color: #555555; } 130 | 131 | .highlight .nt { 132 | color: #000080; } 133 | 134 | .highlight .nv { 135 | color: #008080; } 136 | 137 | .highlight .ow { 138 | color: #000000; 139 | font-weight: bold; } 140 | 141 | .highlight .w { 142 | color: #bbbbbb; } 143 | 144 | .highlight .mf { 145 | color: #009999; } 146 | 147 | .highlight .mh { 148 | color: #009999; } 149 | 150 | .highlight .mi { 151 | color: #009999; } 152 | 153 | .highlight .mo { 154 | color: #009999; } 155 | 156 | .highlight .sb { 157 | color: #d14; } 158 | 159 | .highlight .sc { 160 | color: #d14; } 161 | 162 | .highlight .sd { 163 | color: #d14; } 164 | 165 | .highlight .s2 { 166 | color: #d14; } 167 | 168 | .highlight .se { 169 | color: #d14; } 170 | 171 | .highlight .sh { 172 | color: #d14; } 173 | 174 | .highlight .si { 175 | color: #d14; } 176 | 177 | .highlight .sx { 178 | color: #d14; } 179 | 180 | .highlight .sr { 181 | color: #009926; } 182 | 183 | .highlight .s1 { 184 | color: #d14; } 185 | 186 | .highlight .ss { 187 | color: #990073; } 188 | 189 | .highlight .bp { 190 | color: #999999; } 191 | 192 | .highlight .vc { 193 | color: #008080; } 194 | 195 | .highlight .vg { 196 | color: #008080; } 197 | 198 | .highlight .vi { 199 | color: #008080; } 200 | 201 | .highlight .il { 202 | color: #009999; } 203 | -------------------------------------------------------------------------------- /docs/docsets/Marindeck.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /*! Jazzy - https://github.com/realm/jazzy 2 | * Copyright Realm Inc. 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | /* Credit to https://gist.github.com/wataru420/2048287 */ 6 | .highlight .c { 7 | color: #999988; 8 | font-style: italic; } 9 | 10 | .highlight .err { 11 | color: #a61717; 12 | background-color: #e3d2d2; } 13 | 14 | .highlight .k { 15 | color: #000000; 16 | font-weight: bold; } 17 | 18 | .highlight .o { 19 | color: #000000; 20 | font-weight: bold; } 21 | 22 | .highlight .cm { 23 | color: #999988; 24 | font-style: italic; } 25 | 26 | .highlight .cp { 27 | color: #999999; 28 | font-weight: bold; } 29 | 30 | .highlight .c1 { 31 | color: #999988; 32 | font-style: italic; } 33 | 34 | .highlight .cs { 35 | color: #999999; 36 | font-weight: bold; 37 | font-style: italic; } 38 | 39 | .highlight .gd { 40 | color: #000000; 41 | background-color: #ffdddd; } 42 | 43 | .highlight .gd .x { 44 | color: #000000; 45 | background-color: #ffaaaa; } 46 | 47 | .highlight .ge { 48 | color: #000000; 49 | font-style: italic; } 50 | 51 | .highlight .gr { 52 | color: #aa0000; } 53 | 54 | .highlight .gh { 55 | color: #999999; } 56 | 57 | .highlight .gi { 58 | color: #000000; 59 | background-color: #ddffdd; } 60 | 61 | .highlight .gi .x { 62 | color: #000000; 63 | background-color: #aaffaa; } 64 | 65 | .highlight .go { 66 | color: #888888; } 67 | 68 | .highlight .gp { 69 | color: #555555; } 70 | 71 | .highlight .gs { 72 | font-weight: bold; } 73 | 74 | .highlight .gu { 75 | color: #aaaaaa; } 76 | 77 | .highlight .gt { 78 | color: #aa0000; } 79 | 80 | .highlight .kc { 81 | color: #000000; 82 | font-weight: bold; } 83 | 84 | .highlight .kd { 85 | color: #000000; 86 | font-weight: bold; } 87 | 88 | .highlight .kp { 89 | color: #000000; 90 | font-weight: bold; } 91 | 92 | .highlight .kr { 93 | color: #000000; 94 | font-weight: bold; } 95 | 96 | .highlight .kt { 97 | color: #445588; } 98 | 99 | .highlight .m { 100 | color: #009999; } 101 | 102 | .highlight .s { 103 | color: #d14; } 104 | 105 | .highlight .na { 106 | color: #008080; } 107 | 108 | .highlight .nb { 109 | color: #0086B3; } 110 | 111 | .highlight .nc { 112 | color: #445588; 113 | font-weight: bold; } 114 | 115 | .highlight .no { 116 | color: #008080; } 117 | 118 | .highlight .ni { 119 | color: #800080; } 120 | 121 | .highlight .ne { 122 | color: #990000; 123 | font-weight: bold; } 124 | 125 | .highlight .nf { 126 | color: #990000; } 127 | 128 | .highlight .nn { 129 | color: #555555; } 130 | 131 | .highlight .nt { 132 | color: #000080; } 133 | 134 | .highlight .nv { 135 | color: #008080; } 136 | 137 | .highlight .ow { 138 | color: #000000; 139 | font-weight: bold; } 140 | 141 | .highlight .w { 142 | color: #bbbbbb; } 143 | 144 | .highlight .mf { 145 | color: #009999; } 146 | 147 | .highlight .mh { 148 | color: #009999; } 149 | 150 | .highlight .mi { 151 | color: #009999; } 152 | 153 | .highlight .mo { 154 | color: #009999; } 155 | 156 | .highlight .sb { 157 | color: #d14; } 158 | 159 | .highlight .sc { 160 | color: #d14; } 161 | 162 | .highlight .sd { 163 | color: #d14; } 164 | 165 | .highlight .s2 { 166 | color: #d14; } 167 | 168 | .highlight .se { 169 | color: #d14; } 170 | 171 | .highlight .sh { 172 | color: #d14; } 173 | 174 | .highlight .si { 175 | color: #d14; } 176 | 177 | .highlight .sx { 178 | color: #d14; } 179 | 180 | .highlight .sr { 181 | color: #009926; } 182 | 183 | .highlight .s1 { 184 | color: #d14; } 185 | 186 | .highlight .ss { 187 | color: #990073; } 188 | 189 | .highlight .bp { 190 | color: #999999; } 191 | 192 | .highlight .vc { 193 | color: #008080; } 194 | 195 | .highlight .vg { 196 | color: #008080; } 197 | 198 | .highlight .vi { 199 | color: #008080; } 200 | 201 | .highlight .il { 202 | color: #009999; } 203 | -------------------------------------------------------------------------------- /Marindeck/View/Settings/CustomCSS/EditCustomCSSViewController.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Marindeck/Model/Handlers/General.swift: -------------------------------------------------------------------------------- 1 | // 2 | // General.swift 3 | // Marindeck 4 | // 5 | // Created by a on 2022/01/29. 6 | // 7 | 8 | import Foundation 9 | 10 | struct AnyCodingKeys: CodingKey { 11 | var stringValue: String 12 | var intValue: Int? 13 | 14 | init?(stringValue: String) { self.stringValue = stringValue } 15 | 16 | init?(intValue: Int) { 17 | self.stringValue = String(intValue) 18 | self.intValue = intValue 19 | } 20 | } 21 | 22 | extension KeyedDecodingContainer { 23 | func decode(_ type: [Any].Type, forKey key: K) throws -> [Any] { 24 | var container = try self.nestedUnkeyedContainer(forKey: key) 25 | return try container.decode(type) 26 | } 27 | 28 | func decodeIfPresent(_ type: [Any].Type, forKey key: K) throws -> [Any]? { 29 | guard contains(key) else { return .none } 30 | return try decode(type, forKey: key) 31 | } 32 | 33 | func decode(_ type: [String: Any].Type, forKey key: K) throws -> [String: Any] { 34 | let container = try nestedContainer(keyedBy: AnyCodingKeys.self, forKey: key) 35 | return try container.decode(type) 36 | } 37 | 38 | func decodeIfPresent(_ type: [String: Any].Type, forKey key: K) throws -> [String: Any]? { 39 | guard contains(key) else { return .none } 40 | return try decode(type, forKey: key) 41 | } 42 | 43 | func decode(_ type: [String: Any].Type) throws -> [String: Any] { 44 | var dictionary = [String: Any]() 45 | 46 | allKeys.forEach { key in 47 | if let value = try? decode(Bool.self, forKey: key) { 48 | dictionary[key.stringValue] = value 49 | } else if let value = try? decode(String.self, forKey: key) { 50 | dictionary[key.stringValue] = value 51 | } else if let value = try? decode(Int64.self, forKey: key) { 52 | dictionary[key.stringValue] = value 53 | } else if let value = try? decode(Double.self, forKey: key) { 54 | dictionary[key.stringValue] = value 55 | } else if let value = try? decode([String: Any].self, forKey: key) { 56 | dictionary[key.stringValue] = value 57 | } else if let value = try? decode([Any].self, forKey: key) { 58 | dictionary[key.stringValue] = value 59 | } 60 | } 61 | 62 | return dictionary 63 | } 64 | } 65 | 66 | extension UnkeyedDecodingContainer { 67 | mutating func decode(_ type: [Any].Type) throws -> [Any] { 68 | var array = [Any]() 69 | 70 | while isAtEnd == false { 71 | if let value = try? decode(Bool.self) { 72 | array.append(value) 73 | } else if let value = try? decode(String.self) { 74 | array.append(value) 75 | } else if let value = try? decode(Int64.self) { 76 | array.append(value) 77 | } else if let value = try? decode(Double.self) { 78 | array.append(value) 79 | } else if let value = try? decode([String: Any].self) { 80 | array.append(value) 81 | } else if let value = try? decode([Any].self) { 82 | array.append(value) 83 | } 84 | } 85 | 86 | return array 87 | } 88 | 89 | mutating func decode(_ type: [String: Any].Type) throws -> [String: Any] { 90 | let nestedContainer = try self.nestedContainer(keyedBy: AnyCodingKeys.self) 91 | return try nestedContainer.decode(type) 92 | } 93 | } 94 | 95 | struct General: Decodable { 96 | let type: JSCallbackFlag 97 | let body: Body 98 | 99 | struct Body: Decodable { 100 | let body: [String: Any] 101 | 102 | public init(from decoder: Decoder) throws { 103 | let container = try decoder.container(keyedBy: AnyCodingKeys.self) 104 | body = try container.decode([String: Any].self) 105 | } 106 | } 107 | 108 | // struct FetchImage: Decodable { 109 | // let url: String 110 | // } 111 | } 112 | -------------------------------------------------------------------------------- /MarinDeckExtension/MarinDeckExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MarinDeckExtension.swift 3 | // MarinDeckExtension 4 | // 5 | // Created by Rinia on 2021/04/15. 6 | // 7 | 8 | import WidgetKit 9 | import SwiftUI 10 | import Intents 11 | 12 | struct Provider: IntentTimelineProvider { 13 | func placeholder(in context: Context) -> SimpleEntry { 14 | SimpleEntry(date: Date(), configuration: ConfigurationIntent()) 15 | } 16 | 17 | func getSnapshot(for configuration: ConfigurationIntent, 18 | in context: Context, 19 | completion: @escaping (SimpleEntry) -> Void) { 20 | let entry = SimpleEntry(date: Date(), configuration: configuration) 21 | completion(entry) 22 | } 23 | 24 | func getTimeline(for configuration: ConfigurationIntent, 25 | in context: Context, 26 | completion: @escaping (Timeline) -> Void) { 27 | var entries: [SimpleEntry] = [] 28 | 29 | // Generate a timeline consisting of five entries an hour apart, starting from the current date. 30 | let currentDate = Date() 31 | for hourOffset in 0 ..< 5 { 32 | let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! 33 | let entry = SimpleEntry(date: entryDate, configuration: configuration) 34 | entries.append(entry) 35 | } 36 | 37 | let timeline = Timeline(entries: entries, policy: .atEnd) 38 | completion(timeline) 39 | } 40 | } 41 | 42 | struct SimpleEntry: TimelineEntry { 43 | let date: Date 44 | let configuration: ConfigurationIntent 45 | } 46 | 47 | struct TrendView: View { 48 | @Binding var title: String 49 | @Binding var ranking: String 50 | 51 | var body: some View { 52 | HStack { 53 | // ZStack { 54 | // Circle() 55 | // .fill(Color.clear) 56 | // .frame(width: 30, height: 30) 57 | // Text(ranking) 58 | // .frame(width: 30, height: 30, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) 59 | // } 60 | 61 | Text(title) 62 | .foregroundColor(.white) 63 | .fontWeight(.semibold) 64 | .font(/*@START_MENU_TOKEN@*/.title3/*@END_MENU_TOKEN@*/) 65 | 66 | } 67 | } 68 | } 69 | 70 | struct MarinDeckExtensionEntryView: View { 71 | var entry: Provider.Entry 72 | 73 | @State var title1st: String = "#ゲットしよう15種のお菓子" 74 | @State var title2nd: String = "#本当に人間ですか" 75 | @State var title3rd: String = "御園座初日" 76 | 77 | var body: some View { 78 | ZStack { 79 | Color(red: 0.08, green: 0.12, blue: 0.16) 80 | .edgesIgnoringSafeArea(.all) 81 | 82 | HStack { 83 | VStack(alignment: .leading, spacing: 15.0) { 84 | TrendView(title: $title1st, ranking: $title3rd) 85 | TrendView(title: $title2nd, ranking: $title3rd) 86 | TrendView(title: $title3rd, ranking: $title3rd) 87 | 88 | } 89 | .padding() 90 | } 91 | .padding(10) 92 | } 93 | } 94 | 95 | } 96 | 97 | @main 98 | struct MarinDeckExtension: Widget { 99 | let kind: String = "MarinDeckExtension" 100 | 101 | var body: some WidgetConfiguration { 102 | IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in 103 | MarinDeckExtensionEntryView(entry: entry) 104 | } 105 | .configurationDisplayName("Twitter trend") 106 | .description("Twitterのトレンドが表示されます。") 107 | } 108 | } 109 | 110 | struct MarinDeckExtension_Previews: PreviewProvider { 111 | static var previews: some View { 112 | MarinDeckExtensionEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent())) 113 | .previewContext(WidgetPreviewContext(family: .systemMedium)) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Marindeck/View/Settings/CustomJS/EditCustomJSViewController.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Marindeck/View/Login/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewController.swift 3 | // Marindeck 4 | // 5 | // Created by Rinia on 2021/11/07. 6 | // 7 | 8 | import UIKit 9 | import WebKit 10 | 11 | protocol LoginViewControllerOutput { 12 | func logined() 13 | } 14 | 15 | class LoginViewController: UIViewController, WKUIDelegate, WKNavigationDelegate { 16 | public var delegate: LoginViewControllerOutput? 17 | public var url = "https://mobile.twitter.com/login?hide_message=true&redirect_after_login=https://tweetdeck.twitter.com" 18 | 19 | var webView: WKWebView! 20 | 21 | override func loadView() { 22 | let webConfiguration = WKWebViewConfiguration() 23 | webView = WKWebView(frame: .zero, configuration: webConfiguration) 24 | webView.uiDelegate = self 25 | webView.navigationDelegate = self 26 | // webView.customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1" 27 | view = webView 28 | } 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | isModalInPresentation = true 34 | 35 | navigationController?.navigationBar.barTintColor = .label 36 | navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) 37 | navigationController?.navigationBar.shadowImage = UIImage() 38 | navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "xmark.circle.fill"), 39 | style: .plain, 40 | target: self, 41 | action: #selector(self.onDismiss)) 42 | navigationController?.navigationBar.tintColor = .label 43 | 44 | let myURL = URL(string: url) 45 | let request = URLRequest(url: myURL!) 46 | 47 | let jsonString = """ 48 | [{ 49 | "trigger": { 50 | "url-filter": ".*" 51 | }, 52 | "action": { 53 | "type": "css-display-none", 54 | "selector": "div[aria-label=閉じる]" 55 | }, 56 | "action": { 57 | "type": "css-display-none", 58 | "selector": "div[role=progressbar]" 59 | } 60 | }] 61 | """ 62 | 63 | WKContentRuleListStore.default() 64 | .compileContentRuleList(forIdentifier: "ContentBlockingRules", 65 | encodedContentRuleList: jsonString) { rulesList, error in 66 | if let error = error { 67 | print(error) 68 | return 69 | } 70 | guard let rulesList = rulesList else { 71 | return 72 | } 73 | let config = self.webView.configuration 74 | config.userContentController.add(rulesList) 75 | self.webView.load(request) 76 | } 77 | } 78 | 79 | @objc 80 | func onDismiss() { 81 | dismiss(animated: true, completion: nil) 82 | } 83 | 84 | func webView(_ webView: WKWebView, 85 | decidePolicyFor navigationAction: WKNavigationAction, 86 | decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 87 | let url = navigationAction.request.url 88 | guard let host = url?.host else { 89 | decisionHandler(.cancel) 90 | return 91 | } 92 | 93 | if host == "tweetdeck.twitter.com" || url?.lastPathComponent == "home" { 94 | decisionHandler(.cancel) 95 | dismiss(animated: true, completion: nil) 96 | delegate?.logined() 97 | return 98 | } 99 | 100 | decisionHandler(.allow) 101 | } 102 | 103 | } 104 | --------------------------------------------------------------------------------