220 lines
7.2 KiB
Objective-C
220 lines
7.2 KiB
Objective-C
//
|
|
// WRBookmark.m
|
|
// WeRead (微信读书)
|
|
// Reverse-engineered implementation reconstruction
|
|
//
|
|
// Detailed pseudo-code based on binary analysis (many NSString ivars,
|
|
// WRBook, WRMPReview, NSArray, NSDictionary, NSMutableSet) and
|
|
// contextual knowledge of WeRead's bookmark/annotation system.
|
|
//
|
|
|
|
#import "WRBookmark.h"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Color mapping
|
|
// ---------------------------------------------------------------------------
|
|
static NSDictionary<NSString *, UIColor *> *sColorMap = nil;
|
|
|
|
@implementation WRBookmark
|
|
|
|
#pragma mark - Class Initialization
|
|
|
|
+ (void)initialize
|
|
{
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
sColorMap = @{
|
|
@"yellow" : [UIColor colorWithRed:1.0 green:0.9 blue:0.3 alpha:0.4],
|
|
@"blue" : [UIColor colorWithRed:0.3 green:0.6 blue:1.0 alpha:0.4],
|
|
@"red" : [UIColor colorWithRed:1.0 green:0.3 blue:0.3 alpha:0.4],
|
|
@"green" : [UIColor colorWithRed:0.3 green:0.9 blue:0.4 alpha:0.4],
|
|
@"purple" : [UIColor colorWithRed:0.7 green:0.3 blue:0.9 alpha:0.4],
|
|
};
|
|
});
|
|
}
|
|
|
|
#pragma mark - Factory Methods
|
|
|
|
+ (instancetype)bookmarkWithBookId:(NSString *)bookId
|
|
chapterUid:(NSString *)chapterUid
|
|
offset:(NSInteger)offset
|
|
text:(NSString *)text
|
|
type:(WRBookmarkType)type
|
|
{
|
|
WRBookmark *bm = [[WRBookmark alloc] init];
|
|
bm.bookId = bookId;
|
|
bm.chapterUid = chapterUid;
|
|
bm.chapterOffset = offset;
|
|
bm.markText = text;
|
|
bm.type = type;
|
|
bm.createTime = [[NSDate date] timeIntervalSince1970];
|
|
bm.updateTime = bm.createTime;
|
|
bm.isSynced = NO;
|
|
|
|
// Generate a unique bookmark ID
|
|
bm.bookmarkId = [[NSUUID UUID] UUIDString];
|
|
|
|
return bm;
|
|
}
|
|
|
|
+ (instancetype)highlightWithBookId:(NSString *)bookId
|
|
chapterUid:(NSString *)chapterUid
|
|
startPos:(NSInteger)startPos
|
|
endPos:(NSInteger)endPos
|
|
text:(NSString *)text
|
|
colorStyle:(NSString *)colorStyle
|
|
{
|
|
WRBookmark *bm = [self bookmarkWithBookId:bookId
|
|
chapterUid:chapterUid
|
|
offset:startPos
|
|
text:text
|
|
type:WRBookmarkTypeHighlight];
|
|
bm.startPos = startPos;
|
|
bm.endPos = endPos;
|
|
bm.rangeLength = endPos - startPos;
|
|
bm.colorStyle = colorStyle ?: @"yellow";
|
|
|
|
return bm;
|
|
}
|
|
|
|
#pragma mark - Serialization
|
|
|
|
- (NSDictionary *)toDictionary
|
|
{
|
|
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
|
|
|
if (_bookmarkId) dict[@"bookmarkId"] = _bookmarkId;
|
|
if (_bookId) dict[@"bookId"] = _bookId;
|
|
if (_chapterUid) dict[@"chapterUid"] = _chapterUid;
|
|
dict[@"chapterOffset"] = @(_chapterOffset);
|
|
dict[@"chapterIndex"] = @(_chapterIndex);
|
|
if (_markText) dict[@"markText"] = _markText;
|
|
if (_noteContent) dict[@"noteContent"] = _noteContent;
|
|
dict[@"type"] = @(_type);
|
|
if (_colorStyle) dict[@"colorStyle"] = _colorStyle;
|
|
dict[@"startPos"] = @(_startPos);
|
|
dict[@"endPos"] = @(_endPos);
|
|
dict[@"rangeLength"] = @(_rangeLength);
|
|
if (_anchorId) dict[@"anchorId"] = _anchorId;
|
|
if (_rangeKey) dict[@"rangeKey"] = _rangeKey;
|
|
dict[@"createTime"] = @(_createTime);
|
|
dict[@"updateTime"] = @(_updateTime);
|
|
dict[@"isSynced"] = @(_isSynced);
|
|
if (_syncKey) dict[@"syncKey"] = _syncKey;
|
|
if (_rangeInfo) dict[@"rangeInfo"] = _rangeInfo;
|
|
if (_extraMetadata) dict[@"extraMetadata"] = _extraMetadata;
|
|
|
|
if (_tags.count > 0) {
|
|
dict[@"tags"] = [_tags allObjects];
|
|
}
|
|
|
|
return [dict copy];
|
|
}
|
|
|
|
+ (nullable instancetype)fromDictionary:(NSDictionary *)dict
|
|
{
|
|
if (!dict) return nil;
|
|
|
|
WRBookmark *bm = [[WRBookmark alloc] init];
|
|
|
|
bm.bookmarkId = dict[@"bookmarkId"];
|
|
bm.bookId = dict[@"bookId"];
|
|
bm.chapterUid = dict[@"chapterUid"];
|
|
bm.chapterOffset = [dict[@"chapterOffset"] integerValue];
|
|
bm.chapterIndex = [dict[@"chapterIndex"] integerValue];
|
|
bm.markText = dict[@"markText"];
|
|
bm.noteContent = dict[@"noteContent"];
|
|
bm.type = [dict[@"type"] integerValue];
|
|
bm.colorStyle = dict[@"colorStyle"];
|
|
bm.startPos = [dict[@"startPos"] integerValue];
|
|
bm.endPos = [dict[@"endPos"] integerValue];
|
|
bm.rangeLength = [dict[@"rangeLength"] integerValue];
|
|
bm.anchorId = dict[@"anchorId"];
|
|
bm.rangeKey = dict[@"rangeKey"];
|
|
bm.createTime = [dict[@"createTime"] doubleValue];
|
|
bm.updateTime = [dict[@"updateTime"] doubleValue];
|
|
bm.isSynced = [dict[@"isSynced"] boolValue];
|
|
bm.syncKey = dict[@"syncKey"];
|
|
bm.rangeInfo = dict[@"rangeInfo"];
|
|
bm.extraMetadata = dict[@"extraMetadata"];
|
|
|
|
NSArray *tags = dict[@"tags"];
|
|
if (tags) {
|
|
bm.tags = [NSMutableSet setWithArray:tags];
|
|
}
|
|
|
|
return bm;
|
|
}
|
|
|
|
- (nullable NSData *)toJSONData
|
|
{
|
|
NSDictionary *dict = [self toDictionary];
|
|
return [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil];
|
|
}
|
|
|
|
+ (nullable instancetype)fromJSONData:(NSData *)data
|
|
{
|
|
if (!data) return nil;
|
|
|
|
NSError *error = nil;
|
|
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
|
|
options:0
|
|
error:&error];
|
|
if (!dict || ![dict isKindOfClass:[NSDictionary class]]) return nil;
|
|
|
|
return [self fromDictionary:dict];
|
|
}
|
|
|
|
#pragma mark - Display
|
|
|
|
- (NSString *)displaySummary
|
|
{
|
|
switch (_type) {
|
|
case WRBookmarkTypeHighlight:
|
|
return [NSString stringWithFormat:@"[Highlight] %@", _markText ?: @""];
|
|
case WRBookmarkTypeUnderline:
|
|
return [NSString stringWithFormat:@"[Underline] %@", _markText ?: @""];
|
|
case WRBookmarkTypeMark:
|
|
return [NSString stringWithFormat:@"[Bookmark] Chapter %@", _chapterUid ?: @""];
|
|
case WRBookmarkTypeNote:
|
|
return [NSString stringWithFormat:@"[Note] %@", _noteContent ?: _markText ?: @""];
|
|
case WRBookmarkTypePencil:
|
|
return @"[Pencil Note]";
|
|
default:
|
|
return _markText ?: @"";
|
|
}
|
|
}
|
|
|
|
- (UIColor *)highlightUIColor
|
|
{
|
|
if (!_colorStyle) {
|
|
return sColorMap[@"yellow"] ?: [UIColor yellowColor];
|
|
}
|
|
return sColorMap[_colorStyle] ?: [UIColor yellowColor];
|
|
}
|
|
|
|
#pragma mark - NSObject
|
|
|
|
- (NSString *)description
|
|
{
|
|
return [NSString stringWithFormat:@"<WRBookmark: %@ type=%ld book=%@ ch=%@ '%@'>",
|
|
_bookmarkId, (long)_type, _bookId, _chapterUid,
|
|
[_markText substringToIndex:MIN(30, _markText.length)]];
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)object
|
|
{
|
|
if (self == object) return YES;
|
|
if (![object isKindOfClass:[WRBookmark class]]) return NO;
|
|
|
|
WRBookmark *other = (WRBookmark *)object;
|
|
return [self.bookmarkId isEqualToString:other.bookmarkId];
|
|
}
|
|
|
|
- (NSUInteger)hash
|
|
{
|
|
return self.bookmarkId.hash;
|
|
}
|
|
|
|
@end
|