diff --git a/plugins/multisrc/readnovelfull/template.ts b/plugins/multisrc/readnovelfull/template.ts index b34b605aa..3f3c5f247 100644 --- a/plugins/multisrc/readnovelfull/template.ts +++ b/plugins/multisrc/readnovelfull/template.ts @@ -621,50 +621,91 @@ export class ReadNovelFullPlugin implements Plugin.PluginBase { onend: () => { if (infoParts.length) { - infoParts - .join('') - .split('\n') - .map(line => line.trim()) - .filter(line => line.includes(':')) - .forEach(line => { - const parts = line.split(':'); - const detailName = parts[0].trim().toLowerCase(); - const detail = parts[1] + // Split into lines by the '\n' tokens, keeping each line's + // text nodes separate: joining them first would glue + // multi-element values (e.g. genre chips) into one blob. + const lines: string[][] = []; + let currentLine: string[] = []; + for (const part of infoParts) { + if (part === '\n') { + lines.push(currentLine); + currentLine = []; + } else { + currentLine.push(part); + } + } + if (currentLine.length) lines.push(currentLine); + + lines.forEach(tokens => { + const line = tokens.join('').trim(); + if (!line.includes(':')) return; + const colonIdx = line.indexOf(':'); + const detailName = line.slice(0, colonIdx).trim().toLowerCase(); + const rest = line.slice(colonIdx + 1); + + let detail: string; + if (detailName === 'genre') { + // Genre chips arrive as separate text nodes; keep them + // as separate items instead of gluing them together. + const labelIdx = tokens.findIndex(t => t.includes(':')); + const labelToken = tokens[labelIdx] ?? ''; + const labelRest = labelToken + .slice(labelToken.indexOf(':') + 1) + .trim(); + const items = [ + ...(labelRest + ? labelRest + .split(',') + .map(g => g.trim()) + .filter(Boolean) + : []), + ...tokens + .slice(labelIdx + 1) + .map(t => t.trim()) + .filter(Boolean), + ]; + detail = items.join(', '); + } else { + detail = rest .split(',') .map(g => g.trim()) .join(', '); + } - switch (detailName) { - case 'author': - novel.author = detail; - break; - case 'genre': - novel.genres = detail; - break; - case 'status': - { - const map: Record = { - ongoing: NovelStatus.Ongoing, - hiatus: NovelStatus.OnHiatus, - dropped: NovelStatus.Cancelled, - cancelled: NovelStatus.Cancelled, - completed: NovelStatus.Completed, - }; - novel.status = - map[detail.toLowerCase()] ?? NovelStatus.Unknown; - } - break; - default: - return; - } - }); + switch (detailName) { + case 'author': + novel.author = detail; + break; + case 'genre': + novel.genres = detail; + break; + case 'status': + { + const map: Record = { + ongoing: NovelStatus.Ongoing, + hiatus: NovelStatus.OnHiatus, + dropped: NovelStatus.Cancelled, + cancelled: NovelStatus.Cancelled, + completed: NovelStatus.Completed, + }; + novel.status = + map[detail.toLowerCase()] ?? NovelStatus.Unknown; + } + break; + default: + return; + } + }); if (!novelId) { const idMatch = novelPath.match(/\d+/); novelId = idMatch ? idMatch[0] : null; } } else { - novel.genres = genreArray.join('').trim(); + novel.genres = genreArray + .map(g => g.trim()) + .filter(Boolean) + .join(', '); novel.author = authorParts.join('').trim(); novel.status = statusParts .join('')