|
|
@@ -1,4 +1,5 @@
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
#import <Cocoa/Cocoa.h>
|
|
|
|
|
|
|
|
#import <AVKit/AVKit.h>
|
|
|
|
|
|
|
|
|
|
|
|
static NSString *TCMSString(id value) {
|
|
|
|
static NSString *TCMSString(id value) {
|
|
|
|
if ([value isKindOfClass:[NSString class]]) {
|
|
|
|
if ([value isKindOfClass:[NSString class]]) {
|
|
|
@@ -37,6 +38,28 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
return normalized;
|
|
|
|
return normalized;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static NSString *TCMSFormatBytes(id value) {
|
|
|
|
|
|
|
|
long long bytes = 0;
|
|
|
|
|
|
|
|
if ([value respondsToSelector:@selector(longLongValue)]) {
|
|
|
|
|
|
|
|
bytes = [value longLongValue];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bytes <= 0) {
|
|
|
|
|
|
|
|
return @"";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSArray *units = @[@"B", @"KB", @"MB", @"GB", @"TB"];
|
|
|
|
|
|
|
|
double amount = (double)bytes;
|
|
|
|
|
|
|
|
NSUInteger unitIndex = 0;
|
|
|
|
|
|
|
|
while (amount >= 1024.0 && unitIndex + 1 < units.count) {
|
|
|
|
|
|
|
|
amount /= 1024.0;
|
|
|
|
|
|
|
|
unitIndex++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return unitIndex == 0
|
|
|
|
|
|
|
|
? [NSString stringWithFormat:@"%lld %@", bytes, units[unitIndex]]
|
|
|
|
|
|
|
|
: [NSString stringWithFormat:@"%.1f %@", amount, units[unitIndex]];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@interface TCMSMarkdownTextView : NSTextView
|
|
|
|
@interface TCMSMarkdownTextView : NSTextView
|
|
|
|
@end
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
@@ -53,6 +76,7 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
@property (nonatomic, copy) NSString *baseURLString;
|
|
|
|
@property (nonatomic, copy) NSString *baseURLString;
|
|
|
|
@property (nonatomic, copy) NSString *username;
|
|
|
|
@property (nonatomic, copy) NSString *username;
|
|
|
|
@property (nonatomic, copy) NSString *password;
|
|
|
|
@property (nonatomic, copy) NSString *password;
|
|
|
|
|
|
|
|
- (NSURL *)absoluteURLForPath:(NSString *)path;
|
|
|
|
- (void)requestAction:(NSString *)action query:(NSDictionary *)query method:(NSString *)method body:(NSDictionary *)body completion:(void (^)(NSDictionary *, NSError *))completion;
|
|
|
|
- (void)requestAction:(NSString *)action query:(NSDictionary *)query method:(NSString *)method body:(NSDictionary *)body completion:(void (^)(NSDictionary *, NSError *))completion;
|
|
|
|
- (void)uploadFile:(NSURL *)fileURL completion:(void (^)(NSDictionary *, NSError *))completion;
|
|
|
|
- (void)uploadFile:(NSURL *)fileURL completion:(void (^)(NSDictionary *, NSError *))completion;
|
|
|
|
@end
|
|
|
|
@end
|
|
|
@@ -70,6 +94,34 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
return [base URLByAppendingPathComponent:@"api.php"];
|
|
|
|
return [base URLByAppendingPathComponent:@"api.php"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (NSURL *)absoluteURLForPath:(NSString *)path {
|
|
|
|
|
|
|
|
NSString *rawPath = TCMSString(path);
|
|
|
|
|
|
|
|
if (rawPath.length == 0) {
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSURL *directURL = [NSURL URLWithString:rawPath];
|
|
|
|
|
|
|
|
if (directURL.scheme.length > 0) {
|
|
|
|
|
|
|
|
return directURL;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSURL *base = [NSURL URLWithString:self.baseURLString ?: @""];
|
|
|
|
|
|
|
|
if (!base) {
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ([rawPath hasPrefix:@"/"]) {
|
|
|
|
|
|
|
|
NSURLComponents *components = [NSURLComponents componentsWithURL:base resolvingAgainstBaseURL:NO];
|
|
|
|
|
|
|
|
components.path = rawPath;
|
|
|
|
|
|
|
|
components.query = nil;
|
|
|
|
|
|
|
|
components.fragment = nil;
|
|
|
|
|
|
|
|
return components.URL;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSURL *siteBase = [[base.lastPathComponent lowercaseString] isEqualToString:@"api.php"] ? [base URLByDeletingLastPathComponent] : base;
|
|
|
|
|
|
|
|
return [siteBase URLByAppendingPathComponent:rawPath];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSURL *)URLForAction:(NSString *)action query:(NSDictionary *)query {
|
|
|
|
- (NSURL *)URLForAction:(NSString *)action query:(NSDictionary *)query {
|
|
|
|
NSURL *endpoint = [self endpointURL];
|
|
|
|
NSURL *endpoint = [self endpointURL];
|
|
|
|
if (!endpoint) {
|
|
|
|
if (!endpoint) {
|
|
|
@@ -212,6 +264,13 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
@property (nonatomic, strong) NSButton *autoCorrectButton;
|
|
|
|
@property (nonatomic, strong) NSButton *autoCorrectButton;
|
|
|
|
@property (nonatomic, strong) NSTextView *markdownView;
|
|
|
|
@property (nonatomic, strong) NSTextView *markdownView;
|
|
|
|
@property (nonatomic, strong) NSScrollView *editorScroll;
|
|
|
|
@property (nonatomic, strong) NSScrollView *editorScroll;
|
|
|
|
|
|
|
|
@property (nonatomic, strong) NSView *mediaPreviewContainer;
|
|
|
|
|
|
|
|
@property (nonatomic, strong) NSTextField *mediaPreviewLabel;
|
|
|
|
|
|
|
|
@property (nonatomic, strong) NSTextField *mediaDetailLabel;
|
|
|
|
|
|
|
|
@property (nonatomic, strong) NSImageView *mediaImageView;
|
|
|
|
|
|
|
|
@property (nonatomic, strong) AVPlayerView *mediaPlayerView;
|
|
|
|
|
|
|
|
@property (nonatomic, strong) NSButton *openMediaButton;
|
|
|
|
|
|
|
|
@property (nonatomic, copy) NSString *mediaPreviewURLString;
|
|
|
|
@property (nonatomic, strong) NSPopUpButton *commentStatusPopup;
|
|
|
|
@property (nonatomic, strong) NSPopUpButton *commentStatusPopup;
|
|
|
|
@property (nonatomic, strong) NSButton *previousPageButton;
|
|
|
|
@property (nonatomic, strong) NSButton *previousPageButton;
|
|
|
|
@property (nonatomic, strong) NSButton *nextPageButton;
|
|
|
|
@property (nonatomic, strong) NSButton *nextPageButton;
|
|
|
@@ -302,7 +361,7 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable
|
|
|
|
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable
|
|
|
|
backing:NSBackingStoreBuffered
|
|
|
|
backing:NSBackingStoreBuffered
|
|
|
|
defer:NO];
|
|
|
|
defer:NO];
|
|
|
|
self.window.title = @"TCMS Mac Client";
|
|
|
|
self.window.title = @"TCMS";
|
|
|
|
self.window.minSize = NSMakeSize(1040, 680);
|
|
|
|
self.window.minSize = NSMakeSize(1040, 680);
|
|
|
|
self.window.delegate = self;
|
|
|
|
self.window.delegate = self;
|
|
|
|
self.window.releasedWhenClosed = NO;
|
|
|
|
self.window.releasedWhenClosed = NO;
|
|
|
@@ -457,6 +516,14 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.editorScroll = nil;
|
|
|
|
self.editorScroll = nil;
|
|
|
|
self.markdownView = nil;
|
|
|
|
self.markdownView = nil;
|
|
|
|
|
|
|
|
[self.mediaPlayerView.player pause];
|
|
|
|
|
|
|
|
self.mediaPreviewContainer = nil;
|
|
|
|
|
|
|
|
self.mediaPreviewLabel = nil;
|
|
|
|
|
|
|
|
self.mediaDetailLabel = nil;
|
|
|
|
|
|
|
|
self.mediaImageView = nil;
|
|
|
|
|
|
|
|
self.mediaPlayerView = nil;
|
|
|
|
|
|
|
|
self.openMediaButton = nil;
|
|
|
|
|
|
|
|
self.mediaPreviewURLString = nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)showContentSection:(NSString *)section {
|
|
|
|
- (void)showContentSection:(NSString *)section {
|
|
|
@@ -580,11 +647,82 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
- (void)showMediaSection {
|
|
|
|
- (void)showMediaSection {
|
|
|
|
self.section = @"media";
|
|
|
|
self.section = @"media";
|
|
|
|
[self clearWorkspace];
|
|
|
|
[self clearWorkspace];
|
|
|
|
|
|
|
|
self.contentListPage = 1;
|
|
|
|
[self.workspace addSubview:[self label:@"Media" frame:NSMakeRect(20, 690, 180, 28) font:[NSFont boldSystemFontOfSize:24]]];
|
|
|
|
[self.workspace addSubview:[self label:@"Media" frame:NSMakeRect(20, 690, 180, 28) font:[NSFont boldSystemFontOfSize:24]]];
|
|
|
|
[self addButton:@"Refresh" frame:NSMakeRect(210, 690, 90, 30) action:@selector(refreshCurrentSection)];
|
|
|
|
[self addButton:@"Refresh" frame:NSMakeRect(210, 690, 90, 30) action:@selector(refreshCurrentSection)];
|
|
|
|
[self addButton:@"Upload" frame:NSMakeRect(310, 690, 90, 30) action:@selector(uploadMedia)];
|
|
|
|
[self addButton:@"Upload" frame:NSMakeRect(310, 690, 90, 30) action:@selector(uploadMedia)];
|
|
|
|
NSScrollView *mediaScroll = [self buildTableWithFrame:NSMakeRect(20, 20, self.workspace.bounds.size.width - 40, 650) columns:@[@"name", @"mime", @"url"]];
|
|
|
|
self.openMediaButton = [self addButton:@"Open" frame:NSMakeRect(410, 690, 80, 30) action:@selector(openSelectedMedia:)];
|
|
|
|
mediaScroll.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
|
|
self.openMediaButton.enabled = NO;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSScrollView *mediaScroll = [self buildTableWithFrame:NSMakeRect(20, 56, 420, 614) columns:@[@"name", @"mime", @"size"]];
|
|
|
|
|
|
|
|
mediaScroll.autoresizingMask = NSViewHeightSizable | NSViewMaxXMargin;
|
|
|
|
|
|
|
|
self.tableView.target = self;
|
|
|
|
|
|
|
|
self.tableView.doubleAction = @selector(openSelectedMedia:);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.previousPageButton = [NSButton buttonWithTitle:@"Previous" target:self action:@selector(previousContentPage)];
|
|
|
|
|
|
|
|
self.previousPageButton.frame = NSMakeRect(20, 20, 78, 28);
|
|
|
|
|
|
|
|
self.previousPageButton.bezelStyle = NSBezelStyleRounded;
|
|
|
|
|
|
|
|
self.previousPageButton.autoresizingMask = NSViewMaxYMargin;
|
|
|
|
|
|
|
|
[self.workspace addSubview:self.previousPageButton];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.pageInfoLabel = [self label:@"1 / 1" frame:NSMakeRect(104, 24, 66, 20) font:[NSFont systemFontOfSize:12]];
|
|
|
|
|
|
|
|
self.pageInfoLabel.alignment = NSTextAlignmentCenter;
|
|
|
|
|
|
|
|
self.pageInfoLabel.autoresizingMask = NSViewMaxYMargin;
|
|
|
|
|
|
|
|
[self.workspace addSubview:self.pageInfoLabel];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.nextPageButton = [NSButton buttonWithTitle:@"Next" target:self action:@selector(nextContentPage)];
|
|
|
|
|
|
|
|
self.nextPageButton.frame = NSMakeRect(176, 20, 54, 28);
|
|
|
|
|
|
|
|
self.nextPageButton.bezelStyle = NSBezelStyleRounded;
|
|
|
|
|
|
|
|
self.nextPageButton.autoresizingMask = NSViewMaxYMargin;
|
|
|
|
|
|
|
|
[self.workspace addSubview:self.nextPageButton];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.perPagePopup = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(236, 20, 64, 28)];
|
|
|
|
|
|
|
|
self.perPagePopup.autoresizingMask = NSViewMaxYMargin;
|
|
|
|
|
|
|
|
self.perPagePopup.toolTip = @"Items per page";
|
|
|
|
|
|
|
|
self.perPagePopup.target = self;
|
|
|
|
|
|
|
|
self.perPagePopup.action = @selector(contentPerPageChanged:);
|
|
|
|
|
|
|
|
[self.perPagePopup addItemsWithTitles:@[@"10", @"25", @"50", @"100"]];
|
|
|
|
|
|
|
|
NSString *perPageTitle = [NSString stringWithFormat:@"%ld", (long)self.contentListPerPage];
|
|
|
|
|
|
|
|
if ([self.perPagePopup itemWithTitle:perPageTitle]) {
|
|
|
|
|
|
|
|
[self.perPagePopup selectItemWithTitle:perPageTitle];
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
self.contentListPerPage = 25;
|
|
|
|
|
|
|
|
[self.perPagePopup selectItemWithTitle:@"25"];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
[self.workspace addSubview:self.perPagePopup];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CGFloat previewLeft = 460;
|
|
|
|
|
|
|
|
CGFloat previewWidth = self.workspace.bounds.size.width - previewLeft - 20;
|
|
|
|
|
|
|
|
self.mediaPreviewContainer = [[NSView alloc] initWithFrame:NSMakeRect(previewLeft, 20, previewWidth, 650)];
|
|
|
|
|
|
|
|
self.mediaPreviewContainer.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
|
|
|
|
|
|
self.mediaPreviewContainer.wantsLayer = YES;
|
|
|
|
|
|
|
|
self.mediaPreviewContainer.layer.backgroundColor = [NSColor colorWithCalibratedWhite:0.11 alpha:1].CGColor;
|
|
|
|
|
|
|
|
[self.workspace addSubview:self.mediaPreviewContainer];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.mediaPreviewLabel = [self label:@"No media selected" frame:NSMakeRect(16, 612, previewWidth - 32, 24) font:[NSFont boldSystemFontOfSize:15]];
|
|
|
|
|
|
|
|
self.mediaPreviewLabel.autoresizingMask = NSViewWidthSizable | NSViewMinYMargin;
|
|
|
|
|
|
|
|
self.mediaPreviewLabel.textColor = [NSColor colorWithCalibratedWhite:0.92 alpha:1];
|
|
|
|
|
|
|
|
[self.mediaPreviewContainer addSubview:self.mediaPreviewLabel];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.mediaDetailLabel = [self label:@"" frame:NSMakeRect(16, 584, previewWidth - 32, 20) font:[NSFont systemFontOfSize:12]];
|
|
|
|
|
|
|
|
self.mediaDetailLabel.autoresizingMask = NSViewWidthSizable | NSViewMinYMargin;
|
|
|
|
|
|
|
|
self.mediaDetailLabel.textColor = [NSColor colorWithCalibratedWhite:0.72 alpha:1];
|
|
|
|
|
|
|
|
[self.mediaPreviewContainer addSubview:self.mediaDetailLabel];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSRect previewFrame = NSMakeRect(16, 16, previewWidth - 32, 552);
|
|
|
|
|
|
|
|
self.mediaImageView = [[NSImageView alloc] initWithFrame:previewFrame];
|
|
|
|
|
|
|
|
self.mediaImageView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
|
|
|
|
|
|
self.mediaImageView.imageScaling = NSImageScaleProportionallyUpOrDown;
|
|
|
|
|
|
|
|
self.mediaImageView.hidden = YES;
|
|
|
|
|
|
|
|
[self.mediaPreviewContainer addSubview:self.mediaImageView];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.mediaPlayerView = [[AVPlayerView alloc] initWithFrame:previewFrame];
|
|
|
|
|
|
|
|
self.mediaPlayerView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
|
|
|
|
|
|
self.mediaPlayerView.controlsStyle = AVPlayerViewControlsStyleDefault;
|
|
|
|
|
|
|
|
self.mediaPlayerView.hidden = YES;
|
|
|
|
|
|
|
|
[self.mediaPreviewContainer addSubview:self.mediaPlayerView];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[self resetMediaPreview];
|
|
|
|
[self refreshMedia];
|
|
|
|
[self refreshMedia];
|
|
|
|
[self refreshWindowLayout];
|
|
|
|
[self refreshWindowLayout];
|
|
|
|
}
|
|
|
|
}
|
|
|
@@ -694,12 +832,15 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
self.previousPageButton.enabled = total > 0 && self.contentListPage > 1;
|
|
|
|
self.previousPageButton.enabled = total > 0 && self.contentListPage > 1;
|
|
|
|
self.nextPageButton.enabled = total > 0 && self.contentListPage < totalPages;
|
|
|
|
self.nextPageButton.enabled = total > 0 && self.contentListPage < totalPages;
|
|
|
|
self.perPagePopup.enabled = total > 0;
|
|
|
|
self.perPagePopup.enabled = total > 0;
|
|
|
|
|
|
|
|
if ([self.section isEqualToString:@"media"]) {
|
|
|
|
|
|
|
|
[self resetMediaPreview];
|
|
|
|
|
|
|
|
}
|
|
|
|
[self refreshWindowLayout];
|
|
|
|
[self refreshWindowLayout];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSString *)contentListStatusMessageForType:(NSString *)type {
|
|
|
|
- (NSString *)contentListStatusMessageForType:(NSString *)type {
|
|
|
|
NSInteger total = self.allTableItems.count;
|
|
|
|
NSInteger total = self.allTableItems.count;
|
|
|
|
NSString *noun = [type isEqualToString:@"page"] ? @"pages" : @"posts";
|
|
|
|
NSString *noun = [type isEqualToString:@"media"] ? @"media items" : ([type isEqualToString:@"page"] ? @"pages" : @"posts");
|
|
|
|
if (total == 0) {
|
|
|
|
if (total == 0) {
|
|
|
|
return [NSString stringWithFormat:@"No %@ found.", noun];
|
|
|
|
return [NSString stringWithFormat:@"No %@ found.", noun];
|
|
|
|
}
|
|
|
|
}
|
|
|
@@ -709,13 +850,20 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
return [NSString stringWithFormat:@"Showing %ld-%ld of %ld %@.", (long)start, (long)end, (long)total, noun];
|
|
|
|
return [NSString stringWithFormat:@"Showing %ld-%ld of %ld %@.", (long)start, (long)end, (long)total, noun];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (NSString *)currentListStatusMessage {
|
|
|
|
|
|
|
|
if ([self.section isEqualToString:@"media"]) {
|
|
|
|
|
|
|
|
return [self contentListStatusMessageForType:@"media"];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return [self contentListStatusMessageForType:[self.section isEqualToString:@"pages"] ? @"page" : @"post"];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)previousContentPage {
|
|
|
|
- (void)previousContentPage {
|
|
|
|
if (self.contentListPage <= 1) {
|
|
|
|
if (self.contentListPage <= 1) {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.contentListPage -= 1;
|
|
|
|
self.contentListPage -= 1;
|
|
|
|
[self applyContentListPagination];
|
|
|
|
[self applyContentListPagination];
|
|
|
|
[self setStatus:[self contentListStatusMessageForType:[self.section isEqualToString:@"pages"] ? @"page" : @"post"]];
|
|
|
|
[self setStatus:[self currentListStatusMessage]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)nextContentPage {
|
|
|
|
- (void)nextContentPage {
|
|
|
@@ -725,7 +873,7 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.contentListPage += 1;
|
|
|
|
self.contentListPage += 1;
|
|
|
|
[self applyContentListPagination];
|
|
|
|
[self applyContentListPagination];
|
|
|
|
[self setStatus:[self contentListStatusMessageForType:[self.section isEqualToString:@"pages"] ? @"page" : @"post"]];
|
|
|
|
[self setStatus:[self currentListStatusMessage]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)contentPerPageChanged:(id)sender {
|
|
|
|
- (void)contentPerPageChanged:(id)sender {
|
|
|
@@ -734,7 +882,7 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
self.contentListPage = 1;
|
|
|
|
self.contentListPage = 1;
|
|
|
|
[NSUserDefaults.standardUserDefaults setInteger:selected forKey:@"contentListPerPage"];
|
|
|
|
[NSUserDefaults.standardUserDefaults setInteger:selected forKey:@"contentListPerPage"];
|
|
|
|
[self applyContentListPagination];
|
|
|
|
[self applyContentListPagination];
|
|
|
|
[self setStatus:[self contentListStatusMessageForType:[self.section isEqualToString:@"pages"] ? @"page" : @"post"]];
|
|
|
|
[self setStatus:[self currentListStatusMessage]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
|
|
|
|
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
|
|
|
@@ -753,7 +901,7 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
field.lineBreakMode = NSLineBreakByTruncatingTail;
|
|
|
|
field.lineBreakMode = NSLineBreakByTruncatingTail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NSDictionary *item = self.tableItems[row];
|
|
|
|
NSDictionary *item = self.tableItems[row];
|
|
|
|
field.stringValue = TCMSString(item[tableColumn.identifier]);
|
|
|
|
field.stringValue = [tableColumn.identifier isEqualToString:@"size"] ? TCMSFormatBytes(item[@"size"]) : TCMSString(item[tableColumn.identifier]);
|
|
|
|
return field;
|
|
|
|
return field;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -765,6 +913,8 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
NSDictionary *item = self.tableItems[row];
|
|
|
|
NSDictionary *item = self.tableItems[row];
|
|
|
|
if ([self.section isEqualToString:@"posts"] || [self.section isEqualToString:@"pages"]) {
|
|
|
|
if ([self.section isEqualToString:@"posts"] || [self.section isEqualToString:@"pages"]) {
|
|
|
|
[self loadContentSlug:TCMSString(item[@"slug"])];
|
|
|
|
[self loadContentSlug:TCMSString(item[@"slug"])];
|
|
|
|
|
|
|
|
} else if ([self.section isEqualToString:@"media"]) {
|
|
|
|
|
|
|
|
[self previewMediaItem:item];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -1080,9 +1230,10 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
[self setStatus:error.localizedDescription];
|
|
|
|
[self setStatus:error.localizedDescription];
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.tableItems = [NSMutableArray arrayWithArray:json[@"items"] ?: @[]];
|
|
|
|
NSArray *items = [json[@"items"] isKindOfClass:[NSArray class]] ? json[@"items"] : @[];
|
|
|
|
[self.tableView reloadData];
|
|
|
|
self.allTableItems = [NSMutableArray arrayWithArray:items];
|
|
|
|
[self setStatus:@"Media refreshed."];
|
|
|
|
[self applyContentListPagination];
|
|
|
|
|
|
|
|
[self setStatus:[self currentListStatusMessage]];
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@@ -1104,6 +1255,111 @@ static NSString *TCMSMarkdownCompareString(NSString *value) {
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (NSURL *)absoluteURLForMediaItem:(NSDictionary *)item {
|
|
|
|
|
|
|
|
NSString *url = TCMSString(item[@"url"]);
|
|
|
|
|
|
|
|
if (url.length == 0) {
|
|
|
|
|
|
|
|
url = TCMSString(item[@"path"]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return [self.client absoluteURLForPath:url];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (NSString *)mediaDetailForItem:(NSDictionary *)item {
|
|
|
|
|
|
|
|
NSMutableArray *parts = [NSMutableArray array];
|
|
|
|
|
|
|
|
NSString *mime = TCMSString(item[@"mime"]);
|
|
|
|
|
|
|
|
NSString *size = TCMSFormatBytes(item[@"size"]);
|
|
|
|
|
|
|
|
NSString *path = TCMSString(item[@"path"]);
|
|
|
|
|
|
|
|
if (mime.length > 0) {
|
|
|
|
|
|
|
|
[parts addObject:mime];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (size.length > 0) {
|
|
|
|
|
|
|
|
[parts addObject:size];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (path.length > 0) {
|
|
|
|
|
|
|
|
[parts addObject:path];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return [parts componentsJoinedByString:@" "];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)resetMediaPreview {
|
|
|
|
|
|
|
|
[self.mediaPlayerView.player pause];
|
|
|
|
|
|
|
|
self.mediaPlayerView.player = nil;
|
|
|
|
|
|
|
|
self.mediaPlayerView.hidden = YES;
|
|
|
|
|
|
|
|
self.mediaImageView.image = nil;
|
|
|
|
|
|
|
|
self.mediaImageView.hidden = YES;
|
|
|
|
|
|
|
|
self.mediaPreviewURLString = nil;
|
|
|
|
|
|
|
|
self.openMediaButton.enabled = NO;
|
|
|
|
|
|
|
|
self.mediaPreviewLabel.stringValue = @"No media selected";
|
|
|
|
|
|
|
|
self.mediaDetailLabel.stringValue = @"";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)previewMediaItem:(NSDictionary *)item {
|
|
|
|
|
|
|
|
NSURL *url = [self absoluteURLForMediaItem:item];
|
|
|
|
|
|
|
|
if (!url) {
|
|
|
|
|
|
|
|
[self resetMediaPreview];
|
|
|
|
|
|
|
|
[self setStatus:@"That media item does not have a usable URL."];
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSString *mime = [TCMSString(item[@"mime"]) lowercaseString];
|
|
|
|
|
|
|
|
NSString *urlString = url.absoluteString ?: @"";
|
|
|
|
|
|
|
|
self.mediaPreviewURLString = urlString;
|
|
|
|
|
|
|
|
self.openMediaButton.enabled = YES;
|
|
|
|
|
|
|
|
self.mediaPreviewLabel.stringValue = TCMSString(item[@"name"]).length ? TCMSString(item[@"name"]) : url.lastPathComponent;
|
|
|
|
|
|
|
|
self.mediaDetailLabel.stringValue = [self mediaDetailForItem:item];
|
|
|
|
|
|
|
|
self.mediaImageView.image = nil;
|
|
|
|
|
|
|
|
self.mediaImageView.hidden = YES;
|
|
|
|
|
|
|
|
[self.mediaPlayerView.player pause];
|
|
|
|
|
|
|
|
self.mediaPlayerView.player = nil;
|
|
|
|
|
|
|
|
self.mediaPlayerView.hidden = YES;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ([mime hasPrefix:@"image/"]) {
|
|
|
|
|
|
|
|
self.mediaImageView.hidden = NO;
|
|
|
|
|
|
|
|
[self setStatus:@"Loading image preview."];
|
|
|
|
|
|
|
|
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
|
|
|
|
|
|
|
NSImage *image = data.length > 0 ? [[NSImage alloc] initWithData:data] : nil;
|
|
|
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
|
|
|
if (![self.mediaPreviewURLString isEqualToString:urlString]) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (image) {
|
|
|
|
|
|
|
|
self.mediaImageView.image = image;
|
|
|
|
|
|
|
|
[self setStatus:@"Image preview ready."];
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
self.mediaImageView.hidden = YES;
|
|
|
|
|
|
|
|
[self setStatus:error.localizedDescription ?: @"Unable to load image preview."];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}] resume];
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ([mime hasPrefix:@"video/"] || [mime hasPrefix:@"audio/"]) {
|
|
|
|
|
|
|
|
AVPlayer *player = [AVPlayer playerWithURL:url];
|
|
|
|
|
|
|
|
self.mediaPlayerView.player = player;
|
|
|
|
|
|
|
|
self.mediaPlayerView.hidden = NO;
|
|
|
|
|
|
|
|
[self setStatus:[mime hasPrefix:@"audio/"] ? @"Audio ready to play." : @"Video ready to play."];
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[self setStatus:@"Preview unavailable for this file type. Use Open to view it."];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)openSelectedMedia:(id)sender {
|
|
|
|
|
|
|
|
NSDictionary *item = [self selectedTableItem];
|
|
|
|
|
|
|
|
if (!item) {
|
|
|
|
|
|
|
|
[self setStatus:@"Select a media item first."];
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSURL *url = [self absoluteURLForMediaItem:item];
|
|
|
|
|
|
|
|
if (!url) {
|
|
|
|
|
|
|
|
[self setStatus:@"That media item does not have a usable URL."];
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[NSWorkspace.sharedWorkspace openURL:url];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)refreshComments {
|
|
|
|
- (void)refreshComments {
|
|
|
|
NSString *status = self.commentStatusPopup.titleOfSelectedItem ?: @"pending";
|
|
|
|
NSString *status = self.commentStatusPopup.titleOfSelectedItem ?: @"pending";
|
|
|
|
NSDictionary *query = [status isEqualToString:@"all"] ? @{} : @{@"status": status};
|
|
|
|
NSDictionary *query = [status isEqualToString:@"all"] ? @{} : @{@"status": status};
|
|
|
|