|
|
@@ -97,17 +97,40 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
}
|
|
|
|
|
|
/// Multiline `NSTextField` often ignores `alignment` for wrapped runs; explicit paragraph alignment matches the title.
|
|
|
- private static func jobListingDescriptionAttributedString(_ plain: String) -> NSAttributedString {
|
|
|
- let paragraph = NSMutableParagraphStyle()
|
|
|
- paragraph.alignment = .left
|
|
|
- paragraph.lineBreakMode = .byWordWrapping
|
|
|
- paragraph.baseWritingDirection = .leftToRight
|
|
|
- let font = NSFont.systemFont(ofSize: 13, weight: .regular)
|
|
|
- return NSAttributedString(string: plain, attributes: [
|
|
|
- .font: font,
|
|
|
- .foregroundColor: Theme.secondaryText,
|
|
|
- .paragraphStyle: paragraph
|
|
|
- ])
|
|
|
+ private static func jobListingDescriptionAttributedString(
|
|
|
+ _ plain: String,
|
|
|
+ forceLeftToRight: Bool = false
|
|
|
+ ) -> NSAttributedString {
|
|
|
+ if forceLeftToRight {
|
|
|
+ let paragraph = NSMutableParagraphStyle()
|
|
|
+ paragraph.alignment = .left
|
|
|
+ paragraph.lineBreakMode = .byWordWrapping
|
|
|
+ paragraph.baseWritingDirection = .leftToRight
|
|
|
+ return NSAttributedString(string: plain, attributes: [
|
|
|
+ .font: NSFont.systemFont(ofSize: 13, weight: .regular),
|
|
|
+ .foregroundColor: Theme.secondaryText,
|
|
|
+ .paragraphStyle: paragraph
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ return AppLayoutDirection.attributedString(
|
|
|
+ plain,
|
|
|
+ font: NSFont.systemFont(ofSize: 13, weight: .regular),
|
|
|
+ color: Theme.secondaryText
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ private static func chatBubbleAttributedString(_ text: String, color: NSColor, font: NSFont) -> NSAttributedString {
|
|
|
+ AppLayoutDirection.attributedString(text, font: font, color: color)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func searchFieldPlaceholder() -> NSAttributedString {
|
|
|
+ NSAttributedString(
|
|
|
+ string: L("Ask for roles, skills, salary, or job descriptions..."),
|
|
|
+ attributes: AppLayoutDirection.placeholderAttributes(
|
|
|
+ font: .systemFont(ofSize: 14, weight: .regular),
|
|
|
+ color: Theme.secondaryText
|
|
|
+ )
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
/// Horizontal row for sidebar + main; plain view + constraints keep both panels top/bottom aligned (stack view height alignment was inconsistent).
|
|
|
@@ -271,13 +294,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
savedJobsPageTitleLabel.stringValue = L("Saved Jobs")
|
|
|
nonHomeSubtitleLabel.stringValue = L("This area is not available in the preview build. Use Home to search jobs.")
|
|
|
|
|
|
- jobKeywordsField.placeholderAttributedString = NSAttributedString(
|
|
|
- string: L("Ask for roles, skills, salary, or job descriptions..."),
|
|
|
- attributes: [
|
|
|
- .foregroundColor: Theme.secondaryText,
|
|
|
- .font: NSFont.systemFont(ofSize: 14, weight: .regular)
|
|
|
- ]
|
|
|
- )
|
|
|
+ jobKeywordsField.placeholderAttributedString = searchFieldPlaceholder()
|
|
|
jobSearchIcon.setAccessibilityLabel(L("Ask AI"))
|
|
|
findJobsButton.setAccessibilityLabel(L("Send"))
|
|
|
|
|
|
@@ -295,6 +312,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
rebuildFeatureShortcutCards()
|
|
|
trailingLoadMoreJobsButton?.title = L("Show more jobs")
|
|
|
refreshWelcomeChatMessageForCurrentLanguage()
|
|
|
+ applyHomeLayoutDirection()
|
|
|
AppMenuLocalizer.apply()
|
|
|
}
|
|
|
|
|
|
@@ -424,13 +442,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
searchCard.layer?.backgroundColor = (searchHovering ? Theme.neutralHoverFill : Theme.cardBackground).cgColor
|
|
|
searchCard.layer?.borderColor = (searchHovering ? Theme.searchBarBorderHover : Theme.searchBarBorder).cgColor
|
|
|
jobKeywordsField.textColor = Theme.primaryText
|
|
|
- jobKeywordsField.placeholderAttributedString = NSAttributedString(
|
|
|
- string: L("Ask for roles, skills, salary, or job descriptions..."),
|
|
|
- attributes: [
|
|
|
- .foregroundColor: Theme.secondaryText,
|
|
|
- .font: NSFont.systemFont(ofSize: 14, weight: .regular)
|
|
|
- ]
|
|
|
- )
|
|
|
+ jobKeywordsField.placeholderAttributedString = searchFieldPlaceholder()
|
|
|
jobSearchIcon.contentTintColor = Theme.brandBlue
|
|
|
let ctaHovering = findJobsButton.isHovering
|
|
|
findJobsCTAPill.layer?.backgroundColor = (ctaHovering ? Theme.brandBlueHover : Theme.brandBlue).cgColor
|
|
|
@@ -457,11 +469,55 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
configureSidebar()
|
|
|
reloadSavedJobsListings()
|
|
|
rebuildChatUI()
|
|
|
+ applyHomeLayoutDirection()
|
|
|
applyProSubscriptionToSidebar()
|
|
|
updateFreeJobSearchQuotaLabel()
|
|
|
needsLayout = true
|
|
|
}
|
|
|
|
|
|
+ /// Home search, chat, and shell chrome follow the in-app language; CV/profile subtrees stay LTR.
|
|
|
+ private func applyHomeLayoutDirection() {
|
|
|
+ let direction = AppLayoutDirection.interface
|
|
|
+
|
|
|
+ userInterfaceLayoutDirection = direction
|
|
|
+ panelsRow.userInterfaceLayoutDirection = direction
|
|
|
+ chromeContainer.userInterfaceLayoutDirection = direction
|
|
|
+ sidebar.userInterfaceLayoutDirection = direction
|
|
|
+ mainOverlay.userInterfaceLayoutDirection = direction
|
|
|
+ searchBarColumn.userInterfaceLayoutDirection = direction
|
|
|
+ searchCard.userInterfaceLayoutDirection = direction
|
|
|
+ chatScrollView.userInterfaceLayoutDirection = direction
|
|
|
+ chatDocumentView.userInterfaceLayoutDirection = direction
|
|
|
+ chatStack.userInterfaceLayoutDirection = direction
|
|
|
+ featureCardsRow.userInterfaceLayoutDirection = direction
|
|
|
+ welcomeHeroHost.userInterfaceLayoutDirection = direction
|
|
|
+
|
|
|
+ AppLayoutDirection.configureTextField(jobKeywordsField)
|
|
|
+ jobKeywordsField.placeholderAttributedString = searchFieldPlaceholder()
|
|
|
+
|
|
|
+ greetingLabel.baseWritingDirection = AppLayoutDirection.naturalWritingDirection
|
|
|
+ subtitleLabel.baseWritingDirection = AppLayoutDirection.naturalWritingDirection
|
|
|
+
|
|
|
+ for card in featureCardsRow.arrangedSubviews {
|
|
|
+ (card as? FeatureShortcutCardView)?.applyLayoutDirection()
|
|
|
+ }
|
|
|
+
|
|
|
+ refreshChatBubbleTextDirection()
|
|
|
+ nonHomeHost.userInterfaceLayoutDirection = .leftToRight
|
|
|
+ }
|
|
|
+
|
|
|
+ private func refreshChatBubbleTextDirection() {
|
|
|
+ func walk(_ view: NSView) {
|
|
|
+ if let label = view as? ChatBubbleLabel {
|
|
|
+ let color = label.textColor ?? Theme.primaryText
|
|
|
+ let font = label.font ?? .systemFont(ofSize: 13.5, weight: .regular)
|
|
|
+ label.attributedStringValue = Self.chatBubbleAttributedString(label.stringValue, color: color, font: font)
|
|
|
+ }
|
|
|
+ for sub in view.subviews { walk(sub) }
|
|
|
+ }
|
|
|
+ walk(chatStack)
|
|
|
+ }
|
|
|
+
|
|
|
private func refreshSettingsPageAppearance(in root: NSView) {
|
|
|
for view in root.subviewsRecursive() {
|
|
|
guard let rawID = view.identifier?.rawValue else { continue }
|
|
|
@@ -728,6 +784,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
])
|
|
|
registerSubscriptionObserverOnce()
|
|
|
refreshLocalizedStrings()
|
|
|
+ applyHomeLayoutDirection()
|
|
|
}
|
|
|
|
|
|
private func registerSubscriptionObserverOnce() {
|
|
|
@@ -863,6 +920,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
target: self,
|
|
|
action: spec.action
|
|
|
)
|
|
|
+ card.applyLayoutDirection()
|
|
|
featureCardsRow.addArrangedSubview(card)
|
|
|
}
|
|
|
}
|
|
|
@@ -1057,7 +1115,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
titleField.textColor = Theme.brandBlue
|
|
|
titleField.maximumNumberOfLines = 2
|
|
|
titleField.lineBreakMode = .byWordWrapping
|
|
|
- titleField.alignment = .left
|
|
|
+ titleField.alignment = context == .homeSearchResults ? AppLayoutDirection.naturalTextAlignment : .left
|
|
|
titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
titleField.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
@@ -1085,11 +1143,16 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
descriptionField.textColor = Theme.secondaryText
|
|
|
descriptionField.maximumNumberOfLines = 2
|
|
|
descriptionField.lineBreakMode = .byWordWrapping
|
|
|
- descriptionField.alignment = .left
|
|
|
- descriptionField.baseWritingDirection = .leftToRight
|
|
|
- descriptionField.attributedStringValue = Self.jobListingDescriptionAttributedString(job.description)
|
|
|
+ descriptionField.alignment = context == .homeSearchResults ? AppLayoutDirection.naturalTextAlignment : .left
|
|
|
+ descriptionField.baseWritingDirection = context == .homeSearchResults
|
|
|
+ ? AppLayoutDirection.naturalWritingDirection
|
|
|
+ : .leftToRight
|
|
|
+ descriptionField.attributedStringValue = Self.jobListingDescriptionAttributedString(
|
|
|
+ job.description,
|
|
|
+ forceLeftToRight: context == .savedJobsPage
|
|
|
+ )
|
|
|
if let cell = descriptionField.cell as? NSTextFieldCell {
|
|
|
- cell.alignment = .left
|
|
|
+ cell.alignment = descriptionField.alignment
|
|
|
cell.wraps = true
|
|
|
}
|
|
|
descriptionField.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
@@ -1451,10 +1514,10 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
field.delegate = self
|
|
|
field.placeholderAttributedString = NSAttributedString(
|
|
|
string: placeholder,
|
|
|
- attributes: [
|
|
|
- .foregroundColor: Theme.secondaryText,
|
|
|
- .font: NSFont.systemFont(ofSize: 14, weight: .regular)
|
|
|
- ]
|
|
|
+ attributes: AppLayoutDirection.placeholderAttributes(
|
|
|
+ font: .systemFont(ofSize: 14, weight: .regular),
|
|
|
+ color: Theme.secondaryText
|
|
|
+ )
|
|
|
)
|
|
|
field.cell?.usesSingleLineMode = true
|
|
|
field.cell?.wraps = false
|
|
|
@@ -1469,6 +1532,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
jobSearchIcon.contentTintColor = Theme.brandBlue
|
|
|
|
|
|
configureField(jobKeywordsField, placeholder: L("Ask for roles, skills, salary, or job descriptions..."))
|
|
|
+ AppLayoutDirection.configureTextField(jobKeywordsField)
|
|
|
|
|
|
let ctaHeight: CGFloat = 42
|
|
|
let ctaCorner = ctaHeight / 2
|
|
|
@@ -2706,7 +2770,7 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
let nameLabel = NSTextField(labelWithString: AppMarketingLinks.appDisplayName)
|
|
|
nameLabel.font = .systemFont(ofSize: 11, weight: .semibold)
|
|
|
nameLabel.textColor = Theme.secondaryText
|
|
|
- nameLabel.alignment = .left
|
|
|
+ nameLabel.alignment = AppLayoutDirection.naturalTextAlignment
|
|
|
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
let bubble = makeChatBubbleContainer(text: text, isUser: false)
|
|
|
@@ -2733,7 +2797,6 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
|
|
|
host.addSubview(avatar)
|
|
|
host.addSubview(column)
|
|
|
- host.userInterfaceLayoutDirection = .leftToRight
|
|
|
NSLayoutConstraint.activate([
|
|
|
avatar.leadingAnchor.constraint(equalTo: host.leadingAnchor),
|
|
|
avatar.topAnchor.constraint(equalTo: host.topAnchor),
|
|
|
@@ -2770,7 +2833,13 @@ final class DashboardView: NSView, NSTextFieldDelegate, NSSharingServicePickerDe
|
|
|
label.textColor = isUser ? .white : Theme.primaryText
|
|
|
label.maximumNumberOfLines = 0
|
|
|
label.lineBreakMode = .byWordWrapping
|
|
|
- label.alignment = .left
|
|
|
+ label.alignment = AppLayoutDirection.naturalTextAlignment
|
|
|
+ label.baseWritingDirection = AppLayoutDirection.naturalWritingDirection
|
|
|
+ label.attributedStringValue = Self.chatBubbleAttributedString(
|
|
|
+ text,
|
|
|
+ color: label.textColor ?? Theme.primaryText,
|
|
|
+ font: label.font ?? .systemFont(ofSize: 13.5, weight: .regular)
|
|
|
+ )
|
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
|
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
@@ -3925,6 +3994,27 @@ private final class FeatureShortcutCardView: NSView {
|
|
|
subtitleField?.textColor = AppDashboardTheme.featureSecondaryText
|
|
|
chevronView?.contentTintColor = accent
|
|
|
updateSelectionAppearance()
|
|
|
+ applyLayoutDirection()
|
|
|
+ }
|
|
|
+
|
|
|
+ func applyLayoutDirection() {
|
|
|
+ let rtl = AppLayoutDirection.isRightToLeft
|
|
|
+ userInterfaceLayoutDirection = AppLayoutDirection.interface
|
|
|
+ let align: NSTextAlignment = rtl ? .right : .left
|
|
|
+ titleField?.alignment = align
|
|
|
+ subtitleField?.alignment = align
|
|
|
+ if let cell = titleField?.cell as? NSTextFieldCell {
|
|
|
+ cell.alignment = align
|
|
|
+ cell.baseWritingDirection = AppLayoutDirection.naturalWritingDirection
|
|
|
+ }
|
|
|
+ if let cell = subtitleField?.cell as? NSTextFieldCell {
|
|
|
+ cell.alignment = align
|
|
|
+ cell.baseWritingDirection = AppLayoutDirection.naturalWritingDirection
|
|
|
+ }
|
|
|
+ chevronView?.image = NSImage(
|
|
|
+ systemSymbolName: rtl ? "arrow.left" : "arrow.right",
|
|
|
+ accessibilityDescription: nil
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
override func viewDidChangeEffectiveAppearance() {
|