|
@@ -39,6 +39,20 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
39
|
static let findJobsCTAHighlight = NSColor(srgbRed: 54 / 255, green: 110 / 255, blue: 198 / 255, alpha: 1)
|
39
|
static let findJobsCTAHighlight = NSColor(srgbRed: 54 / 255, green: 110 / 255, blue: 198 / 255, alpha: 1)
|
|
40
|
}
|
40
|
}
|
|
41
|
|
41
|
|
|
|
|
42
|
+ /// Multiline `NSTextField` often ignores `alignment` for wrapped runs; explicit paragraph alignment matches the title.
|
|
|
|
43
|
+ private static func jobListingDescriptionAttributedString(_ plain: String) -> NSAttributedString {
|
|
|
|
44
|
+ let paragraph = NSMutableParagraphStyle()
|
|
|
|
45
|
+ paragraph.alignment = .left
|
|
|
|
46
|
+ paragraph.lineBreakMode = .byWordWrapping
|
|
|
|
47
|
+ paragraph.baseWritingDirection = .leftToRight
|
|
|
|
48
|
+ let font = NSFont.systemFont(ofSize: 13, weight: .regular)
|
|
|
|
49
|
+ return NSAttributedString(string: plain, attributes: [
|
|
|
|
50
|
+ .font: font,
|
|
|
|
51
|
+ .foregroundColor: Theme.secondaryText,
|
|
|
|
52
|
+ .paragraphStyle: paragraph
|
|
|
|
53
|
+ ])
|
|
|
|
54
|
+ }
|
|
|
|
55
|
+
|
|
42
|
private let contentStack = NSStackView()
|
56
|
private let contentStack = NSStackView()
|
|
43
|
private let chromeContainer = NSView()
|
57
|
private let chromeContainer = NSView()
|
|
44
|
private let sidebar = NSStackView()
|
58
|
private let sidebar = NSStackView()
|
|
@@ -270,16 +284,20 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
270
|
|
284
|
|
|
271
|
private func updateDescriptionColumnWidths(in stack: NSStackView, containerWidth: CGFloat) {
|
285
|
private func updateDescriptionColumnWidths(in stack: NSStackView, containerWidth: CGFloat) {
|
|
272
|
guard containerWidth > 1 else { return }
|
286
|
guard containerWidth > 1 else { return }
|
|
273
|
- let buttonStripReserve: CGFloat = 200
|
|
|
|
274
|
- let fallbackTextColumn = max(1, containerWidth - 32 - buttonStripReserve)
|
|
|
|
|
|
287
|
+ // Matches `contentColumn` insets on the card (16 leading + 16 trailing). The description spans the full row below the buttons, so never subtract a “button strip” here — a too-narrow `preferredMaxLayoutWidth` inside a wider field makes wrapped `NSTextField` text lay out like a trailing-aligned band after resizes.
|
|
|
|
288
|
+ let contentHorizontalInset: CGFloat = 32
|
|
275
|
var didChange = false
|
289
|
var didChange = false
|
|
276
|
for card in stack.arrangedSubviews {
|
290
|
for card in stack.arrangedSubviews {
|
|
277
|
guard let desc = card.viewWithTag(502) as? NSTextField else { continue }
|
291
|
guard let desc = card.viewWithTag(502) as? NSTextField else { continue }
|
|
|
|
292
|
+ let cardWidth = card.bounds.width > 1 ? card.bounds.width : containerWidth
|
|
|
|
293
|
+ let fallbackColumn = max(1, cardWidth - contentHorizontalInset)
|
|
278
|
let columnWidth: CGFloat
|
294
|
let columnWidth: CGFloat
|
|
279
|
- if let column = desc.superview, column.bounds.width > 1 {
|
|
|
|
|
|
295
|
+ if desc.bounds.width > 1 {
|
|
|
|
296
|
+ columnWidth = desc.bounds.width
|
|
|
|
297
|
+ } else if let column = desc.superview, column.bounds.width > 1 {
|
|
280
|
columnWidth = column.bounds.width
|
298
|
columnWidth = column.bounds.width
|
|
281
|
} else {
|
299
|
} else {
|
|
282
|
- columnWidth = fallbackTextColumn
|
|
|
|
|
|
300
|
+ columnWidth = fallbackColumn
|
|
283
|
}
|
301
|
}
|
|
284
|
if abs(desc.preferredMaxLayoutWidth - columnWidth) > 0.5 {
|
302
|
if abs(desc.preferredMaxLayoutWidth - columnWidth) > 0.5 {
|
|
285
|
desc.preferredMaxLayoutWidth = columnWidth
|
303
|
desc.preferredMaxLayoutWidth = columnWidth
|
|
@@ -369,6 +387,16 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
369
|
descriptionField.font = .systemFont(ofSize: 13, weight: .regular)
|
387
|
descriptionField.font = .systemFont(ofSize: 13, weight: .regular)
|
|
370
|
descriptionField.textColor = Theme.secondaryText
|
388
|
descriptionField.textColor = Theme.secondaryText
|
|
371
|
descriptionField.maximumNumberOfLines = 0
|
389
|
descriptionField.maximumNumberOfLines = 0
|
|
|
|
390
|
+ descriptionField.alignment = .left
|
|
|
|
391
|
+ descriptionField.lineBreakMode = .byWordWrapping
|
|
|
|
392
|
+ descriptionField.baseWritingDirection = .leftToRight
|
|
|
|
393
|
+ descriptionField.attributedStringValue = Self.jobListingDescriptionAttributedString(job.description)
|
|
|
|
394
|
+ if let cell = descriptionField.cell as? NSTextFieldCell {
|
|
|
|
395
|
+ cell.alignment = .left
|
|
|
|
396
|
+ cell.wraps = true
|
|
|
|
397
|
+ }
|
|
|
|
398
|
+ descriptionField.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
|
399
|
+ descriptionField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
372
|
descriptionField.tag = 502
|
400
|
descriptionField.tag = 502
|
|
373
|
descriptionField.translatesAutoresizingMaskIntoConstraints = false
|
401
|
descriptionField.translatesAutoresizingMaskIntoConstraints = false
|
|
374
|
|
402
|
|
|
@@ -460,7 +488,9 @@ final class DashboardView: NSView, NSTextFieldDelegate {
|
|
460
|
savedButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 72),
|
488
|
savedButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 72),
|
|
461
|
savedButton.heightAnchor.constraint(equalToConstant: 28),
|
489
|
savedButton.heightAnchor.constraint(equalToConstant: 28),
|
|
462
|
dismissButton.widthAnchor.constraint(equalToConstant: 28),
|
490
|
dismissButton.widthAnchor.constraint(equalToConstant: 28),
|
|
463
|
- dismissButton.heightAnchor.constraint(equalToConstant: 28)
|
|
|
|
|
|
491
|
+ dismissButton.heightAnchor.constraint(equalToConstant: 28),
|
|
|
|
492
|
+
|
|
|
|
493
|
+ descriptionField.widthAnchor.constraint(equalTo: contentColumn.widthAnchor)
|
|
464
|
])
|
494
|
])
|
|
465
|
|
495
|
|
|
466
|
return card
|
496
|
return card
|