feat: Initial clean release of CHM
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled

这个提交包含在:
chen
2026-11-04 11:29:39 +08:00
当前提交 301194963b
共有 8663 个文件被更改,包括 1962691 次插入 和 0 次删除

查看文件

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Share View Controller-->
<scene sceneID="ceB-am-kn3">
<objects>
<viewController id="j1y-V4-xli" customClass="ShareViewController" sceneMemberID="viewController">
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" style="medium" translatesAutoresizingMaskIntoConstraints="NO" id="xea-QC-Aqw">
<rect key="frame" x="197" y="443" width="20" height="20"/>
</activityIndicatorView>
</subviews>
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="xea-QC-Aqw" firstAttribute="centerX" secondItem="1Xd-am-t49" secondAttribute="centerX" id="Al3-qb-mVX"/>
<constraint firstItem="xea-QC-Aqw" firstAttribute="centerY" secondItem="1Xd-am-t49" secondAttribute="centerY" id="k5g-vk-GHv"/>
</constraints>
</view>
<connections>
<outlet property="spinner" destination="xea-QC-Aqw" id="qxC-bS-d1N"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="139" y="101"/>
</scene>
</scenes>
</document>

查看文件

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleDisplayName</key>
<string>ShareExtension</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationDictionaryVersion</key>
<integer>2</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>10</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>10</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
<key>RCTNewArchEnabled</key>
<true/>
</dict>
</plist>

查看文件

@@ -0,0 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "JoplinCommonShareExtension"
spec.version = "1.0.0"
spec.summary = "Common Share Extension code for Joplin."
spec.description = "Common Share Extension for Joplin"
spec.homepage = "https://github.com/laurent22/joplin"
spec.license = { :type => "AGPL-3.0-or-later" }
spec.author = { "Duncan Cunningham" => "duncanc4@gmail.com" }
spec.platform = :ios, "9.0"
spec.source = { :path => "." }
spec.source_files = "Source/Common/**/*.{h,m}"
end

查看文件

@@ -0,0 +1,14 @@
Pod::Spec.new do |spec|
spec.name = "JoplinRNShareExtension"
spec.version = "1.0.0"
spec.summary = "React Native module for Joplin to access the data from the share extension."
spec.description = "React Native Share Extension module for Joplin"
spec.homepage = "https://github.com/laurent22/joplin"
spec.license = { :type => "AGPL-3.0-or-later" }
spec.author = { "Duncan Cunningham" => "duncanc4@gmail.com" }
spec.platform = :ios, "9.0"
spec.source = { :path => "." }
spec.source_files = "Source/RNShareExtension/**/*.{h,m}"
spec.dependency "React"
spec.dependency "JoplinCommonShareExtension"
end

查看文件

@@ -0,0 +1,29 @@
# Share Extension Creation Instructions
## Creating the Target
2. In Xcode select File->New->Target
3. Then search for Share Extension
4. Select Share Extension and click Next
5. In Product Name enter ShareExtension
6. Leave the rest of the options and click Finish
7. If the Activate "ShareExtension" scheme popup comes, click Activate
## Configuring the Share Extension
1. Delete *ShareViewController.h* and *ShareViewController.m* from the project. (Select files and right click, then Delete)
2. On the confirmation popup, select Move to Trash
3. Now add *ShareViewController.h* and *ShareViewController.m* from *ShareExtension/Source/ShareExtension*. This can be done by right clicking on the *ShareExtension* folder in Xcode and selecting Add Files to "Joplin". Double check that the ShareExtension is checked for Add to targets and click Add.
4. Switch over to git and reset the changes done to *ShareExtension/Base.lproj/Maininterface.storyboard* and *ShareExtension/Info.plist*, as Xcode generated new versions of these files and overwrote ours.
5. Now select the ShareExtension Target and go to Signing & Capabilities
6. Click the + Capability and search for App Groups and add it
7. Back in git reset *ShareExtension/ShareExtension.entitlements* as Xcode just overwrote it. Back in Xcode you should see the app group set
8. Now switch to General, just left of Signing & Capabilities
9. Under Deployment Info, change the iOS version to match the Joplin target version, which is 9.0
## Configuring Joplin
1. We need to perform the same steps to add the App Group as done for the Share Extension, the only difference is the entitlements files is *Joplin.entitlements*
## Final Steps
1. Run pod install from the ios directory to install the new pods
# Other Information
## App Group
If the app group name needs to be changed for some reason, it needs to be changed for both the Share Extension and Joplin. Also *ShareExtensionGroupIdentifier* in *ShareExtension/Source/Common/ShareExtensionConstants.m* needs to be changed to match the new app group.

查看文件

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.net.cozic.joplin</string>
</array>
</dict>
</plist>

查看文件

@@ -0,0 +1,24 @@
//
// ShareData.h
// ShareExtension
//
// Created by Duncan Cunningham on 2/6/21.
// Copyright © 2021 joplinapp.org. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface ShareData : NSObject
@property (nonatomic, strong) NSString* title;
@property (nonatomic, strong) NSString* text;
@property (nonatomic, strong) NSArray<NSDictionary*>* resources;
- (id)initWithDictionary:(NSDictionary*)dictionary;
- (NSDictionary*)encodeToDictionary;
+ (NSDictionary*)resourceDictionaryForURL:(NSString*)url withName:(NSString*)name andMimeType:(NSString*)mimeType;
+ (NSString*)resourceURLFromDictionary:(NSDictionary*)dictionary;
@end

查看文件

@@ -0,0 +1,53 @@
//
// ShareData.m
// ShareExtension
//
// Created by Duncan Cunningham on 2/6/21.
// Copyright © 2021 joplinapp.org. All rights reserved.
//
#import "ShareData.h"
static NSString* const ShareDataTitleKey = @"title";
static NSString* const ShareDataTextKey = @"text";
static NSString* const ShareDataResourcesKey = @"resources";
static NSString* const ShareDataResourceUriKey = @"uri";
static NSString* const ShareDataResourceNameKey = @"name";
static NSString* const ShareDataResourceMimeTypeKey = @"mimeType";
@implementation ShareData
- (id)initWithDictionary:(NSDictionary*)dictionary {
self = [super init];
if (self != nil) {
self.title = [dictionary objectForKey:ShareDataTitleKey];
self.text = [dictionary objectForKey:ShareDataTextKey];
self.resources = [dictionary objectForKey:ShareDataResourcesKey];
}
return self;
}
- (NSDictionary*)encodeToDictionary {
NSString* title = (self.title == nil) ? @"" : self.title;
NSString* text = (self.text == nil) ? @"" : self.text;
NSArray* resources = (self.resources == nil) ? @[] : self.resources;
return @{ShareDataTitleKey: title,
ShareDataTextKey: text,
ShareDataResourcesKey: resources};
}
+ (NSDictionary*)resourceDictionaryForURL:(NSString*)url withName:(NSString*)name andMimeType:(NSString*)mimeType {
return @{ShareDataResourceUriKey: url,
ShareDataResourceNameKey: name,
ShareDataResourceMimeTypeKey: mimeType};
}
+ (NSString*)resourceURLFromDictionary:(NSDictionary*)dictionary {
return [dictionary objectForKey:ShareDataResourceUriKey];
}
@end

查看文件

@@ -0,0 +1,16 @@
//
// ShareExtensionConstants.h
// Joplin
//
// Created by Duncan Cunningham on 2/6/21.
// Copyright © 2021 joplinapp.org. All rights reserved.
//
#ifndef ShareExtensionConstants_h
#define ShareExtensionConstants_h
extern NSString* const ShareExtensionGroupIdentifier;
extern NSString* const ShareExtensionShareURL;
extern NSString* const ShareExtensionShareDataFilename;
#endif /* ShareExtensionConstants_h */

查看文件

@@ -0,0 +1,13 @@
//
// ShareExtensionConstants.m
// Joplin
//
// Created by Duncan Cunningham on 2/6/21.
// Copyright © 2021 joplinapp.org. All rights reserved.
//
#import <Foundation/Foundation.h>
NSString* const ShareExtensionGroupIdentifier = @"group.net.cozic.joplin";
NSString* const ShareExtensionShareURL = @"joplin://shareData";
NSString* const ShareExtensionShareDataFilename = @"shareData";

查看文件

@@ -0,0 +1,67 @@
//
// ShareExtension.m
// Joplin
//
// Created by Duncan Cunningham on 2/6/21.
// Copyright © 2021 joplinapp.org. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import "ShareData.h"
#import "ShareExtensionConstants.h"
@interface ShareExtension : NSObject <RCTBridgeModule>
@property (nonatomic, strong) ShareData* shareData;
@end
@implementation ShareExtension
RCT_EXPORT_MODULE();
RCT_REMAP_METHOD(data, resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
NSDictionary* dictionary = [self decodeShareDataFromURL:self.shareDataURL];
self.shareData = [[ShareData alloc] initWithDictionary:dictionary];
resolve(dictionary);
}
RCT_EXPORT_METHOD(close) {
if (self.shareData.resources != nil) {
for (NSDictionary* resource in self.shareData.resources) {
NSString* uri = [ShareData resourceURLFromDictionary:resource];
if (uri != nil && [NSFileManager.defaultManager fileExistsAtPath:uri]) {
[NSFileManager.defaultManager removeItemAtPath:uri error:nil];
}
}
}
[NSFileManager.defaultManager removeItemAtPath:[[self shareDataURL] path] error:nil];
}
+ (BOOL)requiresMainQueueSetup {
return YES;
}
- (NSDictionary*)constantsToExport {
return @{@"SHARE_EXTENSION_SHARE_URL": ShareExtensionShareURL};
}
- (NSDictionary*)decodeShareDataFromURL:(NSURL*)url {
NSData* data = [NSData dataWithContentsOfFile:[url path]];
if (data != nil) {
return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
} else {
return nil;
}
}
- (NSURL*)shareDataURL {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSURL* sharedContainerURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:ShareExtensionGroupIdentifier];
return [sharedContainerURL URLByAppendingPathComponent:ShareExtensionShareDataFilename];
}
@end

查看文件

@@ -0,0 +1,13 @@
//
// ShareViewController.h
// ShareExtension
//
// Created by Duncan Cunningham on 12/28/20.
// Copyright © 2020 joplinapp.org. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface ShareViewController : UIViewController
@end

查看文件

@@ -0,0 +1,151 @@
//
// ShareViewController.m
// ShareExtension
//
// Created by Duncan Cunningham on 12/28/20.
// Copyright © 2020 joplinapp.org. All rights reserved.
//
#import "ShareViewController.h"
#import "ShareData.h"
#import "ShareExtensionConstants.h"
#import <MobileCoreServices/MobileCoreServices.h>
@interface ShareViewController ()
@property (nonatomic, strong) NSURL* sharedContainerURL;
@property (nonatomic, weak) IBOutlet UIActivityIndicatorView *spinner;
@end
@implementation ShareViewController
- (NSURL*)sharedContainerURL {
if (_sharedContainerURL == nil) {
NSFileManager* fileManager = [NSFileManager defaultManager];
_sharedContainerURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:ShareExtensionGroupIdentifier];
}
return _sharedContainerURL;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.spinner startAnimating];
[self extractDataFromContext:self.extensionContext withCallback:^(ShareData* shareData) {
[self serializeShareDataToSharedContainer:shareData];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){
[self.spinner stopAnimating];
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];
[self launchMainApp];
});
}];
}
- (void)extractDataFromContext:(NSExtensionContext *)context withCallback:(void(^)(ShareData* shareData))callback {
__block ShareData* shareData = [[ShareData alloc] init];
NSMutableArray* resources = [[NSMutableArray alloc] init];
NSExtensionItem* item = [context.inputItems firstObject];
NSArray* attachments = item.attachments;
__block NSUInteger index = 0;
[attachments enumerateObjectsUsingBlock:^(NSItemProvider* provider, NSUInteger idx, BOOL* stop) {
[provider.registeredTypeIdentifiers enumerateObjectsUsingBlock:^(NSString* identifier, NSUInteger idx, BOOL* stop) {
[provider loadItemForTypeIdentifier:identifier options:nil completionHandler:^(id<NSSecureCoding> item, NSError* error) {
index += 1;
// is an URL - Can be a path or Web URL
if ([(NSObject*)item isKindOfClass:[NSURL class]]) {
NSURL* url = (NSURL*)item;
if ([[url pathExtension] isEqualToString:@""] || [url.scheme containsString:@"http"]) {
shareData.text = [url absoluteString];
} else {
NSURL* copiedURL = [self copyURLToSharedContainer:url];
NSDictionary* resource = [self resourceDictionaryForMediaURL:copiedURL];
[resources addObject:resource];
}
// is a String
} else if ([(NSObject*)item isKindOfClass:[NSString class]]) {
shareData.text = (NSString*)item;
// is an Image
} else if ([(NSObject*)item isKindOfClass:[UIImage class]]) {
UIImage* sharedImage = (UIImage*)item;
NSURL* path = [self copyImageToSharedContainer:sharedImage];
NSDictionary* resource = [self resourceDictionaryForMediaURL:path];
[resources addObject:resource];
}
if (index == [attachments count]) {
shareData.resources = resources;
callback(shareData);
}
}];
// We'll only use the first provider
*stop = YES;
}];
}];
}
- (NSURL*)copyImageToSharedContainer:(UIImage*)image {
NSString* name = [NSString stringWithFormat:@"%@%@", [NSUUID new].UUIDString, @".png"];
NSURL* path = [self.sharedContainerURL URLByAppendingPathComponent:name];
[UIImagePNGRepresentation(image) writeToFile:[path path] atomically:YES];
return path;
}
- (NSURL*)copyURLToSharedContainer:(NSURL*)url {
NSString* name = [url lastPathComponent];
NSURL* path = [self.sharedContainerURL URLByAppendingPathComponent:name];
[[NSFileManager defaultManager] copyItemAtPath:[url path] toPath:[path path] error:nil];
return path;
}
- (void)serializeShareDataToSharedContainer:(ShareData*)shareData {
NSData* data = [NSJSONSerialization dataWithJSONObject:[shareData encodeToDictionary] options:0 error:nil];
NSURL* path = [self.sharedContainerURL URLByAppendingPathComponent:ShareExtensionShareDataFilename isDirectory:NO];
[data writeToFile:[path path] options:NSDataWritingAtomic error: nil];
}
- (NSDictionary*)resourceDictionaryForMediaURL:(NSURL*)url {
NSString* name = [url lastPathComponent];
NSString* extension = [url pathExtension];
NSString* mimeType = [self mimeTypeFor:extension];
return [ShareData resourceDictionaryForURL:[url absoluteString] withName:name andMimeType:mimeType];
}
- (NSString*)mimeTypeFor:(NSString*)fileExtension{
NSString* UTI = (__bridge_transfer NSString*)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
NSString* mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
return mimeType;
}
- (void)launchMainApp {
SEL selector = NSSelectorFromString(@"openURL:");
UIResponder* responder = self;
while (responder != nil) {
if ([responder respondsToSelector:selector]) {
UIApplication *app = (UIApplication*) responder;
NSURL *url = [NSURL URLWithString:ShareExtensionShareURL];
[app openURL:url options:@{} completionHandler:^(BOOL success) {
if (success) {
NSLog(@"Opened URL successfully!");
} else {
NSLog(@"Failed to open URL.");
}
}];
break;
}
responder = responder.nextResponder;
}
}
@end