Re-implement syncLinks and processLinks via WikiLinkResolver
syncLinks parses the default-locale content; processLinks resolves each [[link]] against the current locale at render time. Link labels preserve original spelling; destination resolves to the same document in the current locale (with fallback).
This commit is contained in:
@@ -184,10 +184,66 @@ public function scopeInDirectory(Builder $query, string $directory): Builder
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync wiki-links for this document. Implementation in Task 7.
|
||||
* Extract [[wiki-links]] from the default-locale translation's content
|
||||
* and persist them via DocumentLink.
|
||||
*/
|
||||
public function syncLinks(): void
|
||||
{
|
||||
// Replaced in Task 7
|
||||
$this->outgoingLinks()->delete();
|
||||
|
||||
$translation = $this->translationFor($this->default_locale, fallback: false);
|
||||
if (!$translation || !$translation->content) {
|
||||
return;
|
||||
}
|
||||
|
||||
preg_match_all('/\[\[([^\]]+)\]\]/', $translation->content, $matches);
|
||||
if (empty($matches[1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$resolver = new \App\Services\WikiLinkResolver();
|
||||
$position = 0;
|
||||
foreach ($matches[1] as $linkTitle) {
|
||||
$linkTitle = trim($linkTitle);
|
||||
$target = $resolver->resolve($linkTitle, $this->default_locale);
|
||||
|
||||
DocumentLink::create([
|
||||
'source_document_id' => $this->id,
|
||||
'target_document_id' => $target?->id,
|
||||
'target_title' => $linkTitle,
|
||||
'position' => $position++,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert [[wiki-links]] in the current-locale rendered_html to anchor tags.
|
||||
* Link labels stay in the original language; the destination document is
|
||||
* resolved against the current locale (with fallback).
|
||||
*/
|
||||
public function processLinks(): string
|
||||
{
|
||||
$html = $this->rendered_html ?? '';
|
||||
if ($html === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$resolver = new \App\Services\WikiLinkResolver();
|
||||
$currentLocale = App::getLocale();
|
||||
|
||||
return preg_replace_callback(
|
||||
'/\[\[([^\]]+)\]\]/',
|
||||
function ($matches) use ($resolver, $currentLocale) {
|
||||
$linkText = trim($matches[1]);
|
||||
$target = $resolver->resolve($linkText, $currentLocale);
|
||||
|
||||
if ($target) {
|
||||
return '<a href="' . route('documents.show', $target->slug) . '" class="wiki-link">' . e($linkText) . '</a>';
|
||||
}
|
||||
|
||||
return '<a href="' . route('documents.create') . '?title=' . urlencode($linkText) . '" class="wiki-link wiki-link-new">' . e($linkText) . '</a>';
|
||||
},
|
||||
$html
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,4 +76,70 @@ public function test_available_locales_lists_existing_translations(): void
|
||||
sort($locales);
|
||||
$this->assertSame(['en', 'fr', 'ja'], $locales);
|
||||
}
|
||||
|
||||
public function test_sync_links_creates_outgoing_links_with_resolved_targets(): void
|
||||
{
|
||||
$target = Document::factory()->create(['default_locale' => 'en']);
|
||||
$target->translations()->where('locale', 'en')->update(['title' => 'Target']);
|
||||
|
||||
$source = Document::factory()->create(['default_locale' => 'en']);
|
||||
$source->translations()->where('locale', 'en')->update([
|
||||
'content' => 'See [[Target]] for details.',
|
||||
]);
|
||||
|
||||
$source->fresh('translations')->syncLinks();
|
||||
|
||||
$links = $source->fresh()->outgoingLinks;
|
||||
$this->assertCount(1, $links);
|
||||
$this->assertSame($target->id, $links->first()->target_document_id);
|
||||
$this->assertSame('Target', $links->first()->target_title);
|
||||
}
|
||||
|
||||
public function test_sync_links_records_unresolved_links_with_null_target(): void
|
||||
{
|
||||
$source = Document::factory()->create();
|
||||
$source->translations()->first()->update([
|
||||
'content' => 'Goes to [[NoSuchPage]].',
|
||||
]);
|
||||
|
||||
$source->fresh('translations')->syncLinks();
|
||||
|
||||
$links = $source->fresh()->outgoingLinks;
|
||||
$this->assertCount(1, $links);
|
||||
$this->assertNull($links->first()->target_document_id);
|
||||
}
|
||||
|
||||
public function test_process_links_replaces_wiki_link_with_anchor_keeping_label(): void
|
||||
{
|
||||
$target = Document::factory()->create(['default_locale' => 'en', 'slug' => 'target-doc']);
|
||||
$target->translations()->where('locale', 'en')->update(['title' => 'Target']);
|
||||
\Illuminate\Support\Facades\App::setLocale('ja');
|
||||
\App\Models\DocumentTranslation::factory()->create([
|
||||
'document_id' => $target->id,
|
||||
'locale' => 'ja',
|
||||
'title' => 'ターゲット',
|
||||
]);
|
||||
|
||||
$source = Document::factory()->create();
|
||||
$source->translations()->first()->update([
|
||||
'rendered_html' => '<p>See [[Target]].</p>',
|
||||
]);
|
||||
|
||||
$html = $source->fresh()->processLinks();
|
||||
|
||||
$this->assertStringContainsString('href="' . route('documents.show', 'target-doc') . '"', $html);
|
||||
$this->assertStringContainsString('>Target<', $html); // label preserved
|
||||
$this->assertStringContainsString('class="wiki-link"', $html);
|
||||
}
|
||||
|
||||
public function test_process_links_marks_unresolved_links_as_new(): void
|
||||
{
|
||||
$source = Document::factory()->create();
|
||||
$source->translations()->first()->update([
|
||||
'rendered_html' => '<p>Click [[Ghost]].</p>',
|
||||
]);
|
||||
|
||||
$html = $source->fresh()->processLinks();
|
||||
$this->assertStringContainsString('wiki-link-new', $html);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user