'datetime', 'views' => 'integer', 'comments_count' => 'integer', 'stick' => 'boolean', 'visible' => 'boolean', 'close_comment' => 'boolean', 'ai_suggestions' => 'array', 'legacy_attachments' => 'array', 'cover_generated_at' => 'datetime', ]; } public function hasCover(): bool { return $this->cover_status === 'ready' && filled($this->cover_path); } public function coverUrl(): ?string { return app(ArticleCoverService::class)->publicUrl($this); } protected function contentFormat(): Attribute { return Attribute::make( get: fn (?string $value): string => ContentFormat::normalize($value, ContentFormat::HTML), set: fn (?string $value): string => ContentFormat::normalize($value, ContentFormat::HTML), ); } public function renderedHtml(): string { return $this->rendered()['html']; } /** * @return array{html: string, toc: list} */ public function rendered(): array { return app(ContentRenderer::class)->render( (string) $this->content, $this->content_format, $this->id, ); } public function isMarkdown(): bool { return $this->content_format === ContentFormat::MARKDOWN; } public function category(): BelongsTo { return $this->belongsTo(Category::class); } public function user(): BelongsTo { return $this->belongsTo(User::class); } public function comments(): HasMany { return $this->hasMany(Comment::class); } public function tags(): BelongsToMany { return $this->belongsToMany(Tag::class); } public function attachments(): HasMany { return $this->hasMany(Attachment::class); } public function scopeVisible(Builder $query): Builder { return $query->where('visible', true); } public function scopePublished(Builder $query): Builder { return $query ->whereNotNull('published_at') ->where('published_at', '<=', now()); } }